diff --git a/data/genres/business-and-personal-finance.png b/data/genres/business-and-personal-finance.png
new file mode 100644
index 0000000..789aadb
Binary files /dev/null and b/data/genres/business-and-personal-finance.png differ
diff --git a/data/genres/foreign-languages.png b/data/genres/foreign-languages.png
new file mode 100644
index 0000000..5499f4e
Binary files /dev/null and b/data/genres/foreign-languages.png differ
diff --git a/data/genres/mystery-and-thriller.png b/data/genres/mystery-and-thriller.png
new file mode 100644
index 0000000..d42cded
Binary files /dev/null and b/data/genres/mystery-and-thriller.png differ
diff --git a/data/genres/sci-fi.png b/data/genres/sci-fi.png
new file mode 100644
index 0000000..959b6c8
Binary files /dev/null and b/data/genres/sci-fi.png differ
diff --git a/data/genres/travel.png b/data/genres/travel.png
new file mode 100644
index 0000000..41ba389
Binary files /dev/null and b/data/genres/travel.png differ
diff --git a/data/vectors/business-and-personal-finance.svg b/data/vectors/business-and-personal-finance.svg
new file mode 100644
index 0000000..7bb925c
--- /dev/null
+++ b/data/vectors/business-and-personal-finance.svg
@@ -0,0 +1,181 @@
+
+
diff --git a/data/vectors/foreign-languages.svg b/data/vectors/foreign-languages.svg
new file mode 100644
index 0000000..c423fb9
--- /dev/null
+++ b/data/vectors/foreign-languages.svg
@@ -0,0 +1,153 @@
+
+
diff --git a/data/vectors/mystery-and-thriller.svg b/data/vectors/mystery-and-thriller.svg
new file mode 100644
index 0000000..9b7dad0
--- /dev/null
+++ b/data/vectors/mystery-and-thriller.svg
@@ -0,0 +1,572 @@
+
+
diff --git a/data/vectors/sci-fi.svg b/data/vectors/sci-fi.svg
new file mode 100644
index 0000000..5b4161e
--- /dev/null
+++ b/data/vectors/sci-fi.svg
@@ -0,0 +1,74 @@
+
+
diff --git a/data/vectors/travel.svg b/data/vectors/travel.svg
new file mode 100644
index 0000000..d70a1bc
--- /dev/null
+++ b/data/vectors/travel.svg
@@ -0,0 +1,133 @@
+
+
diff --git a/src/components/koto-action-bar.c b/src/components/action-bar.c
similarity index 95%
rename from src/components/koto-action-bar.c
rename to src/components/action-bar.c
index 3270d8a..698777c 100644
--- a/src/components/koto-action-bar.c
+++ b/src/components/action-bar.c
@@ -1,4 +1,4 @@
-/* koto-action-bar.c
+/* action-bar.c
*
* Copyright 2021 Joshua Strobl
*
@@ -17,7 +17,6 @@
#include
#include
-#include "koto-action-bar.h"
#include "../config/config.h"
#include "../db/cartographer.h"
#include "../indexer/album-playlist-funcs.h"
@@ -25,9 +24,10 @@
#include "../playlist/add-remove-track-popover.h"
#include "../playlist/current.h"
#include "../playback/engine.h"
-#include "../koto-button.h"
#include "../koto-utils.h"
#include "../koto-window.h"
+#include "action-bar.h"
+#include "button.h"
extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup;
extern KotoCartographer * koto_maps;
@@ -263,12 +263,12 @@ void koto_action_bar_handle_play_track_button_clicked(
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->current_album_uuid); // Get the Album
if (KOTO_IS_ALBUM(album)) { // Have an Album
- playlist = koto_album_create_playlist(album); // Create our playlist dynamically for the Album
+ playlist = koto_album_get_playlist(album); // Create our playlist dynamically for the Album
}
}
if (KOTO_IS_PLAYLIST(playlist)) { // Is a playlist
- koto_current_playlist_set_playlist(current_playlist, playlist, FALSE); // Update our playlist to the one associated with the track we are playing
+ koto_current_playlist_set_playlist(current_playlist, playlist, FALSE, FALSE); // Update our playlist to the one associated with the track we are playing
koto_playlist_set_track_as_current(playlist, koto_track_get_uuid(track)); // Get this track as the current track in the position
}
@@ -295,7 +295,7 @@ void koto_action_bar_handle_remove_from_playlist_button_clicked(
goto doclose;
}
- if (!koto_utils_is_string_valid(self->current_playlist_uuid)) { // Not valid UUID
+ if (!koto_utils_string_is_valid(self->current_playlist_uuid)) { // Not valid UUID
goto doclose;
}
@@ -325,11 +325,11 @@ void koto_action_bar_set_tracks_in_album_selection(
return;
}
- if (koto_utils_is_string_valid(self->current_album_uuid)) { // Album UUID currently set
+ if (koto_utils_string_is_valid(self->current_album_uuid)) { // Album UUID currently set
g_free(self->current_album_uuid);
}
- if (koto_utils_is_string_valid(self->current_playlist_uuid)) { // Playlist UUID currently set
+ if (koto_utils_string_is_valid(self->current_playlist_uuid)) { // Playlist UUID currently set
g_free(self->current_playlist_uuid);
}
@@ -359,11 +359,11 @@ void koto_action_bar_set_tracks_in_playlist_selection(
return;
}
- if (koto_utils_is_string_valid(self->current_album_uuid)) { // Album UUID currently set
+ if (koto_utils_string_is_valid(self->current_album_uuid)) { // Album UUID currently set
g_free(self->current_album_uuid);
}
- if (koto_utils_is_string_valid(self->current_playlist_uuid)) { // Playlist UUID currently set
+ if (koto_utils_string_is_valid(self->current_playlist_uuid)) { // Playlist UUID currently set
g_free(self->current_playlist_uuid);
}
diff --git a/src/components/koto-action-bar.h b/src/components/action-bar.h
similarity index 99%
rename from src/components/koto-action-bar.h
rename to src/components/action-bar.h
index d8c4572..d52281b 100644
--- a/src/components/koto-action-bar.h
+++ b/src/components/action-bar.h
@@ -1,4 +1,4 @@
-/* koto-action-bar.h
+/* action-bar.h
*
* Copyright 2021 Joshua Strobl
*
diff --git a/src/components/album-info.c b/src/components/album-info.c
new file mode 100644
index 0000000..94f0eab
--- /dev/null
+++ b/src/components/album-info.c
@@ -0,0 +1,289 @@
+/* album-info.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 "../config/config.h"
+#include "../db/cartographer.h"
+#include "../indexer/structs.h"
+#include "../koto-utils.h"
+#include "album-info.h"
+
+extern KotoCartographer * koto_maps;
+extern KotoConfig * config;
+
+enum {
+ PROP_0,
+ PROP_TYPE,
+ N_PROPERTIES
+};
+
+static GParamSpec * props[N_PROPERTIES] = {
+ NULL,
+};
+
+struct _KotoAlbumInfo {
+ GtkBox parent_instance;
+ KotoAlbumInfoType type;
+
+ KotoAlbum * album;
+
+ GtkWidget * name_year_box;
+ GtkWidget * genres_tags_list;
+
+ GtkWidget * name_label;
+ GtkWidget * year_badge;
+ GtkWidget * narrator_label;
+
+ GtkWidget * description_label;
+};
+
+struct _KotoAlbumInfoClass {
+ GtkBoxClass parent_class;
+};
+
+G_DEFINE_TYPE(KotoAlbumInfo, koto_album_info, GTK_TYPE_BOX);
+
+static void koto_album_info_set_property(
+ GObject * obj,
+ guint prop_id,
+ const GValue * val,
+ GParamSpec * spec
+);
+
+static void koto_album_info_class_init(KotoAlbumInfoClass * c) {
+ GObjectClass * gobject_class;
+ gobject_class = G_OBJECT_CLASS(c);
+ gobject_class->set_property = koto_album_info_set_property;
+
+ props[PROP_TYPE] = g_param_spec_string(
+ "type",
+ "Type of AlbumInfo component",
+ "Type of AlbumInfo component",
+ "album",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
+ );
+
+ g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
+}
+
+static void koto_album_info_init(KotoAlbumInfo * self) {
+ gtk_widget_add_css_class(GTK_WIDGET(self), "album-info");
+ self->type = KOTO_ALBUM_INFO_TYPE_ALBUM; // Default to Album here
+
+ self->name_year_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create out name box for the name and year
+ gtk_widget_add_css_class(self->name_year_box, "album-title-year-combo");
+ self->name_label = gtk_label_new(NULL); // Create our name label
+ gtk_widget_set_halign(self->name_label, GTK_ALIGN_START);
+ gtk_widget_set_valign(self->name_label, GTK_ALIGN_START);
+ gtk_widget_add_css_class(self->name_label, "album-title");
+
+ self->year_badge = gtk_label_new(NULL); // Create our year badge
+ gtk_widget_add_css_class(self->year_badge, "album-year");
+ gtk_widget_add_css_class(self->year_badge, "label-badge");
+ gtk_widget_set_valign(self->year_badge, GTK_ALIGN_CENTER); // Center vertically align the year badge
+
+ self->narrator_label = gtk_label_new(NULL); // Create our narrator label
+ gtk_widget_add_css_class(self->narrator_label, "album-narrator");
+ gtk_widget_set_halign(self->narrator_label, GTK_ALIGN_START);
+
+ self->description_label = gtk_label_new(NULL); // Create our description label
+ gtk_widget_add_css_class(self->description_label, "album-description");
+ gtk_widget_set_halign(self->description_label, GTK_ALIGN_START);
+
+ gtk_box_append(GTK_BOX(self->name_year_box), self->name_label); // Add the name label to the name + year box
+ gtk_box_append(GTK_BOX(self->name_year_box), self->year_badge); // Add the year badge to the name + year box
+
+ gtk_box_append(GTK_BOX(self), self->name_year_box);
+ gtk_box_append(GTK_BOX(self), self->narrator_label);
+ gtk_box_append(GTK_BOX(self), self->description_label);
+
+ g_signal_connect(config, "notify::ui-info-show-description", G_CALLBACK(koto_album_info_apply_configuration_state), self);
+ g_signal_connect(config, "notify::ui-info-show-genres", G_CALLBACK(koto_album_info_apply_configuration_state), self);
+ g_signal_connect(config, "notify::ui-info-show-narrator", G_CALLBACK(koto_album_info_apply_configuration_state), self);
+ g_signal_connect(config, "notify::ui-info-show-year", G_CALLBACK(koto_album_info_apply_configuration_state), self);
+}
+
+static void koto_album_info_set_property(
+ GObject * obj,
+ guint prop_id,
+ const GValue * val,
+ GParamSpec * spec
+) {
+ KotoAlbumInfo * self = KOTO_ALBUM_INFO(obj);
+
+ switch (prop_id) {
+ case PROP_TYPE:
+ koto_album_info_set_type(self, g_value_get_string(val));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
+ break;
+ }
+}
+
+void koto_album_info_apply_configuration_state(
+ KotoConfig * c,
+ guint prop_id,
+ KotoAlbumInfo * self
+) {
+ (void) c;
+ (void) prop_id;
+
+ gboolean show_description = TRUE;
+ gboolean show_genres = TRUE;
+ gboolean show_narrator = TRUE;
+ gboolean show_year = TRUE;
+
+ g_object_get(
+ config,
+ "ui-album-info-show-description",
+ &show_description,
+ "ui-album-info-show-genres",
+ &show_genres,
+ "ui-album-info-show-narrator",
+ &show_narrator,
+ "ui-album-info-show-year",
+ &show_year,
+ NULL
+ );
+
+ if (self->type != KOTO_ALBUM_INFO_TYPE_AUDIOBOOK) { // If the type is NOT an audiobook
+ show_narrator = FALSE; // Narrator should never be shown. Only really applicable to audiobook
+ }
+
+ if (self->type == KOTO_ALBUM_INFO_TYPE_PODCAST) { // If the type is podcast
+ show_year = FALSE; // Year isn't really applicable to podcast, at least not the "global" year
+ }
+
+ if (koto_utils_string_is_valid(koto_album_get_description(self->album)) && show_description) { // Have description to show in the first place, and should show it
+ gtk_widget_show(self->description_label);
+ } else { // Don't have content, just hide it
+ gtk_widget_hide(self->description_label);
+ }
+
+ if (!KOTO_IS_ALBUM(self->album)) { // Don't have an album defined
+ return;
+ }
+
+ if (GTK_IS_FLOW_BOX(self->genres_tags_list)) {
+ if ((g_list_length(koto_album_get_genres(self->album)) > 0) && show_genres) { // Have genres to show in the first place, and should show it
+ gtk_widget_show(self->genres_tags_list);
+ } else {
+ gtk_widget_hide(self->genres_tags_list);
+ }
+ }
+
+ if (koto_utils_string_is_valid(koto_album_get_narrator(self->album)) && show_narrator) { // Have narrator and should show it
+ gtk_widget_show(self->narrator_label);
+ } else {
+ gtk_widget_hide(self->narrator_label);
+ }
+
+ if ((koto_album_get_year(self->album) != 0) && show_year) { // Have year and should show it
+ gtk_widget_show(self->year_badge);
+ } else {
+ gtk_widget_hide(self->year_badge);
+ }
+}
+
+void koto_album_info_set_album_uuid(
+ KotoAlbumInfo * self,
+ gchar * album_uuid
+) {
+ if (!KOTO_IS_ALBUM_INFO(self)) {
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(album_uuid)) {
+ return;
+ }
+
+ KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); // Get the album
+
+ if (!KOTO_IS_ALBUM(album)) { // Is not an album
+ return;
+ }
+
+ self->album = album;
+
+ gtk_label_set_text(GTK_LABEL(self->name_label), koto_album_get_name(self->album)); // Set the name label text to the album
+ gtk_label_set_text(GTK_LABEL(self->year_badge), g_strdup_printf("%li", koto_album_get_year(self->album))); // Set the year label text to any year for the album
+
+ gchar * narrator = koto_album_get_narrator(self->album);
+
+ if (koto_utils_string_is_valid(narrator)) { // Have a narrator
+ gtk_label_set_text(GTK_LABEL(self->narrator_label), g_strdup_printf("Narrated by %s", koto_album_get_narrator(self->album))); // Set narrated by string
+ }
+
+ gchar * description = koto_album_get_description(self->album);
+
+ if (koto_utils_string_is_valid(description)) { // Have a description
+ gtk_label_set_markup(GTK_LABEL(self->description_label), description); // Set the markup so we treat it more like HTML and let pango do its thing
+ }
+
+ if (GTK_IS_BOX(self->genres_tags_list)) { // Genres Flow
+ gtk_box_remove(GTK_BOX(self), self->genres_tags_list); // Remove the genres flow from the box
+ }
+
+ self->genres_tags_list = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create our flow box for the genres
+ gtk_widget_add_css_class(self->genres_tags_list, "genres-tag-list");
+
+ GList * album_genres = koto_album_get_genres(self->album);
+ if (g_list_length(album_genres) != 0) { // Have genres
+ GList * cur_list;
+ for (cur_list = album_genres; cur_list != NULL; cur_list = cur_list->next) { // Iterate over each genre
+ GtkWidget * genre_label = gtk_label_new(koto_utils_string_title(cur_list->data));
+ gtk_widget_add_css_class(genre_label, "label-badge"); // Add label badge styling
+ gtk_box_append(GTK_BOX(self->genres_tags_list), genre_label); // Append to the genre tags list
+ }
+ } else {
+ gtk_widget_hide(self->genres_tags_list); // Hide the genres tag list
+ }
+
+ gtk_box_insert_child_after(GTK_BOX(self), self->genres_tags_list, self->name_year_box); // Add after the name+year flowbox
+
+ koto_album_info_apply_configuration_state(NULL, 0, self); // Apply our configuration state immediately so we know what to show and hide for this album based on the config
+}
+
+void koto_album_info_set_type(
+ KotoAlbumInfo * self,
+ const gchar * type
+) {
+ if (!KOTO_IS_ALBUM_INFO(self)) {
+ return;
+ }
+
+ if (g_strcmp0(type, "audiobook") == 0) { // If this is an audiobook
+ self->type = KOTO_ALBUM_INFO_TYPE_AUDIOBOOK;
+ } else if (g_strcmp0(type, "podcast") == 0) { // If this is a podcast
+ self->type = KOTO_ALBUM_INFO_TYPE_PODCAST;
+ } else {
+ self->type = KOTO_ALBUM_INFO_TYPE_ALBUM;
+ }
+}
+
+KotoAlbumInfo * koto_album_info_new(gchar * type) {
+ return g_object_new(
+ KOTO_TYPE_ALBUM_INFO,
+ "orientation",
+ GTK_ORIENTATION_VERTICAL,
+ "type",
+ type,
+ NULL
+ );
+}
\ No newline at end of file
diff --git a/src/components/album-info.h b/src/components/album-info.h
new file mode 100644
index 0000000..fe2e86c
--- /dev/null
+++ b/src/components/album-info.h
@@ -0,0 +1,52 @@
+/* album-info.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
+#include
+#include "../config/config.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+ KOTO_ALBUM_INFO_TYPE_ALBUM,
+ KOTO_ALBUM_INFO_TYPE_AUDIOBOOK,
+ KOTO_ALBUM_INFO_TYPE_PODCAST
+} KotoAlbumInfoType;
+
+#define KOTO_TYPE_ALBUM_INFO (koto_album_info_get_type())
+G_DECLARE_FINAL_TYPE(KotoAlbumInfo, koto_album_info, KOTO, ALBUM_INFO, GtkBox);
+#define KOTO_IS_ALBUM_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ALBUM_INFO))
+
+void koto_album_info_apply_configuration_state(
+ KotoConfig * c,
+ guint prop_id,
+ KotoAlbumInfo * self
+);
+
+void koto_album_info_set_album_uuid(
+ KotoAlbumInfo * self,
+ gchar * album_uuid
+);
+
+void koto_album_info_set_type(
+ KotoAlbumInfo * self,
+ const gchar * type
+);
+
+KotoAlbumInfo * koto_album_info_new(gchar * type);
\ No newline at end of file
diff --git a/src/koto-button.c b/src/components/button.c
similarity index 87%
rename from src/koto-button.c
rename to src/components/button.c
index 5a40f04..f04f545 100644
--- a/src/koto-button.c
+++ b/src/components/button.c
@@ -16,9 +16,12 @@
*/
#include
-#include "koto-button.h"
#include "config/config.h"
-#include "koto-utils.h"
+#include "button.h"
+#include "../koto-window.h"
+#include "../koto-utils.h"
+
+extern KotoWindow * main_window;
struct _PixbufSize {
guint size;
@@ -70,6 +73,8 @@ struct _KotoButton {
GtkBox parent_instance;
guint pix_size;
+ gpointer arbitrary_data;
+
GtkWidget * button_pic;
GtkWidget * badge_label;
GtkWidget * button_label;
@@ -313,6 +318,37 @@ void koto_button_flip(KotoButton * self) {
koto_button_show_image(self, !self->currently_showing_alt);
}
+gpointer koto_button_get_data(KotoButton * self) {
+ return self->arbitrary_data;
+}
+
+void koto_button_global_page_nav_callback(
+ GtkGestureClick * gesture,
+ int n_press,
+ double x,
+ double y,
+ gpointer user_data
+) {
+ (void) gesture;
+ (void) n_press;
+ (void) x;
+ (void) y;
+
+ KotoButton * btn = KOTO_BUTTON(user_data); // Cast our user data as a button
+
+ if (!KOTO_IS_BUTTON(btn)) { // Not a button
+ return;
+ }
+
+ gchar * btn_nav_uuid = (gchar*) koto_button_get_data(btn); // Get the data
+
+ if (!koto_utils_string_is_valid(btn_nav_uuid)) { // Not a valid string
+ return;
+ }
+
+ koto_window_go_to_page(main_window, btn_nav_uuid);
+}
+
void koto_button_handle_mouse_enter(
GtkEventControllerMotion * controller,
double x,
@@ -357,6 +393,17 @@ void koto_button_hide_image(KotoButton * self) {
}
}
+void koto_button_set_data(
+ KotoButton * self,
+ gpointer data
+) {
+ if (!KOTO_IS_BUTTON(self)) { // Not a button
+ return;
+ }
+
+ self->arbitrary_data = data;
+}
+
void koto_button_set_pseudoactive_styling(KotoButton * self) {
if (!KOTO_IS_BUTTON(self)) { // Not a button
return;
@@ -408,14 +455,19 @@ void koto_button_set_file_path(
return;
}
- if (!koto_utils_is_string_valid(file_path)) { // file path is invalid
+ if (!koto_utils_string_is_valid(file_path)) { // file path is invalid
return;
}
- if (koto_utils_is_string_valid(self->image_file_path)) { // image file path is valid
+ if (g_strcmp0(self->image_file_path, file_path) == 0) { // Request setting as same file
+ return;
+ }
+
+ if (koto_utils_string_is_valid(self->image_file_path)) { // image file path is valid
g_free(self->image_file_path);
}
+ self->use_from_file = TRUE;
self->image_file_path = g_strdup(file_path);
koto_button_show_image(self, FALSE);
}
@@ -429,7 +481,7 @@ void koto_button_set_icon_name(
return;
}
- if (!koto_utils_is_string_valid(icon_name)) { // Not a valid icon
+ if (!koto_utils_string_is_valid(icon_name)) { // Not a valid icon
return;
}
@@ -517,18 +569,18 @@ void koto_button_set_text(
return;
}
- if (!koto_utils_is_string_valid(text)) { // Invalid text
+ if (!koto_utils_string_is_valid(text)) { // Invalid text
return;
}
- if (koto_utils_is_string_valid(self->text)) { // Text defined
+ if (koto_utils_string_is_valid(self->text)) { // Text defined
g_free(self->text); // Free existing text
}
self->text = g_strdup(text);
if (GTK_IS_LABEL(self->button_label)) { // If we have a button label
- if (koto_utils_is_string_valid(self->text)) { // Have text set
+ if (koto_utils_string_is_valid(self->text)) { // Have text set
gtk_label_set_text(GTK_LABEL(self->button_label), self->text);
gtk_widget_show(self->button_label); // Show the label
} else { // Have a label but no longer text
@@ -536,8 +588,9 @@ void koto_button_set_text(
g_free(self->button_label);
}
} else { // If we do not have a button label
- if (koto_utils_is_string_valid(self->text)) { // If we have text
+ if (koto_utils_string_is_valid(self->text)) { // If we have text
self->button_label = gtk_label_new(self->text); // Create our label
+ gtk_widget_set_hexpand(self->button_label, TRUE);
gtk_label_set_xalign(GTK_LABEL(self->button_label), 0);
if (GTK_IS_IMAGE(self->button_pic)) { // If we have an image
@@ -551,6 +604,45 @@ void koto_button_set_text(
g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_TEXT]);
}
+void koto_button_set_text_justify(
+ KotoButton * self,
+ GtkJustification j
+) {
+ if (!KOTO_IS_BUTTON(self)) {
+ return;
+ }
+
+ if (!GTK_IS_LABEL(self->button_label)) { // If we do not have a button label
+ return;
+ }
+
+ if (j == GTK_JUSTIFY_CENTER) { // Center text
+ gtk_label_set_xalign(GTK_LABEL(self->button_label), 0.5);
+ } else if (j == GTK_JUSTIFY_RIGHT) { // Right align
+ gtk_label_set_xalign(GTK_LABEL(self->button_label), 1.0);
+ } else {
+ gtk_label_set_xalign(GTK_LABEL(self->button_label), 0);
+ }
+
+ gtk_label_set_justify(GTK_LABEL(self->button_label), j);
+}
+
+void koto_button_set_text_wrap(
+ KotoButton * self,
+ gboolean wrap
+) {
+ if (!KOTO_IS_BUTTON(self)) {
+ return;
+ }
+
+ if (!GTK_IS_LABEL(self->button_label)) { // If we do not have a button label
+ return;
+ }
+
+ gtk_label_set_single_line_mode(GTK_LABEL(self->button_label), !wrap);
+ gtk_label_set_wrap(GTK_LABEL(self->button_label), wrap);
+}
+
void koto_button_show_image(
KotoButton * self,
gboolean use_alt
@@ -560,7 +652,7 @@ void koto_button_show_image(
}
if (self->use_from_file) { // Use from a file instead of icon name
- if (!koto_utils_is_string_valid(self->image_file_path)) { // Not set
+ if (!koto_utils_string_is_valid(self->image_file_path)) { // Not set
return;
}
diff --git a/src/koto-button.h b/src/components/button.h
similarity index 88%
rename from src/koto-button.h
rename to src/components/button.h
index 88825ba..f41359b 100644
--- a/src/koto-button.h
+++ b/src/components/button.h
@@ -1,4 +1,4 @@
-/* koto-button.h
+/* button.h
*
* Copyright 2021 Joshua Strobl
*
@@ -75,6 +75,16 @@ void koto_button_add_click_handler(
void koto_button_flip(KotoButton * self);
+gpointer koto_button_get_data(KotoButton * self);
+
+void koto_button_global_page_nav_callback(
+ GtkGestureClick * gesture,
+ int n_press,
+ double x,
+ double y,
+ gpointer user_data
+);
+
void koto_button_handle_mouse_enter(
GtkEventControllerMotion * controller,
double x,
@@ -89,6 +99,11 @@ void koto_button_handle_mouse_leave(
void koto_button_hide_image(KotoButton * self);
+void koto_button_set_data(
+ KotoButton * self,
+ gpointer data
+);
+
void koto_button_set_pseudoactive_styling(KotoButton * self);
void koto_button_set_badge_text(
@@ -127,6 +142,16 @@ void koto_button_set_text(
gchar * text
);
+void koto_button_set_text_justify(
+ KotoButton * self,
+ GtkJustification j
+);
+
+void koto_button_set_text_wrap(
+ KotoButton * self,
+ gboolean wrap
+);
+
void koto_button_show_image(
KotoButton * self,
gboolean use_alt
diff --git a/src/components/koto-cover-art-button.c b/src/components/cover-art-button.c
similarity index 94%
rename from src/components/koto-cover-art-button.c
rename to src/components/cover-art-button.c
index 3d872b9..e94c2fd 100644
--- a/src/components/koto-cover-art-button.c
+++ b/src/components/cover-art-button.c
@@ -1,4 +1,4 @@
-/* koto-cover-art-button.c
+/* cover-art-button.c
*
* Copyright 2021 Joshua Strobl
*
@@ -17,9 +17,9 @@
#include
#include
-#include "koto-cover-art-button.h"
-#include "../koto-button.h"
#include "../koto-utils.h"
+#include "button.h"
+#include "cover-art-button.h"
struct _KotoCoverArtButton {
GObject parent_instance;
@@ -29,6 +29,8 @@ struct _KotoCoverArtButton {
GtkWidget * revealer;
KotoButton * play_button;
+ gchar * art_path;
+
guint height;
guint width;
};
@@ -119,6 +121,8 @@ static void koto_cover_art_button_init(KotoCoverArtButton * self) {
g_signal_connect(motion_controller, "enter", G_CALLBACK(koto_cover_art_button_show_overlay_controls), self);
g_signal_connect(motion_controller, "leave", G_CALLBACK(koto_cover_art_button_hide_overlay_controls), self);
gtk_widget_add_controller(self->main, motion_controller);
+
+ self->art_path = NULL;
}
static void koto_cover_art_button_get_property(
@@ -194,13 +198,16 @@ void koto_cover_art_button_set_art_path(
return;
}
- gboolean defined_artwork = koto_utils_is_string_valid(art_path);
+ gboolean defined_artwork = koto_utils_string_is_valid(art_path);
if (GTK_IS_IMAGE(self->art)) { // Already have an image
if (!defined_artwork) { // No art path or empty string
gtk_image_set_from_icon_name(GTK_IMAGE(self->art), "audio-x-generic-symbolic");
} else { // Have an art path
- gtk_image_set_from_file(GTK_IMAGE(self->art), g_strdup(art_path)); // Set from the file
+ if (g_strcmp0(self->art_path, art_path) != 0) {
+ self->art_path = g_strdup(art_path); // Set our art path
+ gtk_image_set_from_file(GTK_IMAGE(self->art), self->art_path); // Set from the file
+ }
}
} else { // If we don't have an image
self->art = koto_utils_create_image_from_filepath(defined_artwork ? g_strdup(art_path) : NULL, "audio-x-generic-symbolic", self->width, self->height);
diff --git a/src/components/koto-cover-art-button.h b/src/components/cover-art-button.h
similarity index 96%
rename from src/components/koto-cover-art-button.h
rename to src/components/cover-art-button.h
index 9d9b843..d1f7d44 100644
--- a/src/components/koto-cover-art-button.h
+++ b/src/components/cover-art-button.h
@@ -1,4 +1,4 @@
-/* koto-cover-art-button.h
+/* cover-art-button.h
*
* Copyright 2021 Joshua Strobl
*
@@ -17,7 +17,7 @@
#include
#include
-#include "../koto-button.h"
+#include "button.h"
G_BEGIN_DECLS
diff --git a/src/koto-track-item.c b/src/components/track-item.c
similarity index 97%
rename from src/koto-track-item.c
rename to src/components/track-item.c
index ca42dfe..368e79f 100644
--- a/src/koto-track-item.c
+++ b/src/components/track-item.c
@@ -1,4 +1,4 @@
-/* koto-track-item.c
+/* track-item.c
*
* Copyright 2021 Joshua Strobl
*
@@ -18,8 +18,8 @@
#include
#include "indexer/structs.h"
#include "playlist/add-remove-track-popover.h"
-#include "koto-button.h"
-#include "koto-track-item.h"
+#include "button.h"
+#include "track-item.h"
extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup;
diff --git a/src/koto-track-item.h b/src/components/track-item.h
similarity index 98%
rename from src/koto-track-item.h
rename to src/components/track-item.h
index 757e307..762cf44 100644
--- a/src/koto-track-item.h
+++ b/src/components/track-item.h
@@ -1,4 +1,4 @@
-/* koto-track-item.h
+/* track-item.h
*
* Copyright 2021 Joshua Strobl
*
diff --git a/src/components/track-table.c b/src/components/track-table.c
index 0d3cd7f..0907c08 100644
--- a/src/components/track-table.c
+++ b/src/components/track-table.c
@@ -21,10 +21,10 @@
#include "../playback/engine.h"
#include "../playlist/current.h"
#include "../playlist/playlist.h"
-#include "../koto-button.h"
#include "../koto-utils.h"
#include "../koto-window.h"
-#include "koto-action-bar.h"
+#include "action-bar.h"
+#include "button.h"
#include "track-table.h"
extern KotoActionBar * action_bar;
@@ -122,14 +122,12 @@ void koto_track_table_bind_track_item(
return;
}
- gchar * track_name = NULL;
+ gchar * track_name = koto_track_get_name(track);
gchar * album_uuid = NULL;
gchar * artist_uuid = NULL;
g_object_get(
track,
- "parsed-name",
- &track_name,
"album-uuid",
&album_uuid,
"artist-uuid",
@@ -142,7 +140,7 @@ void koto_track_table_bind_track_item(
gtk_label_set_label(GTK_LABEL(track_position_label), g_strdup_printf("%u", track_position)); // Set the track position
gtk_label_set_label(GTK_LABEL(track_name_label), track_name); // Set our track name
- if (koto_utils_is_string_valid(album_uuid)) { // Is associated with an album
+ if (koto_utils_string_is_valid(album_uuid)) { // Is associated with an album
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
if (KOTO_IS_ALBUM(album)) {
@@ -150,10 +148,12 @@ void koto_track_table_bind_track_item(
}
}
- KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
+ if (koto_utils_string_is_valid(artist_uuid)) { // Is associated with an artist
+ KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
- if (KOTO_IS_ARTIST(artist)) {
- gtk_label_set_label(GTK_LABEL(track_artist_label), koto_artist_get_name(artist)); // Get the name of the artist and set it to the label
+ if (KOTO_IS_ARTIST(artist)) {
+ gtk_label_set_label(GTK_LABEL(track_artist_label), koto_artist_get_name(artist)); // Get the name of the artist and set it to the label
+ }
}
GList * data = NULL;
@@ -229,7 +229,7 @@ void koto_track_table_handle_track_album_clicked (
gtk_widget_add_css_class(GTK_WIDGET(self->track_album_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
- koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM);
+ koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM);
}
void koto_track_table_handle_track_artist_clicked(
@@ -247,7 +247,7 @@ void koto_track_table_handle_track_artist_clicked(
gtk_widget_add_css_class(GTK_WIDGET(self->track_artist_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
- koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST);
+ koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST);
}
void koto_track_table_item_handle_clicked(
@@ -276,7 +276,7 @@ void koto_track_table_item_handle_clicked(
KotoTrackTable * self = g_list_nth_data(data, 0);
gchar * track_uuid = g_list_nth_data(data, 1);
- if (!koto_utils_is_string_valid(track_uuid)) { // Not a valid string
+ if (!koto_utils_string_is_valid(track_uuid)) { // Not a valid string
return;
}
@@ -284,7 +284,7 @@ void koto_track_table_item_handle_clicked(
gtk_widget_grab_focus(GTK_WIDGET(main_window)); // Focus on the window
koto_action_bar_toggle_reveal(action_bar, FALSE);
koto_action_bar_close(action_bar); // Close the action bar
- koto_current_playlist_set_playlist(current_playlist, self->playlist, FALSE); // Set the current playlist to the artist's playlist but do not play immediately
+ koto_current_playlist_set_playlist(current_playlist, self->playlist, FALSE, FALSE); // Set the current playlist to the artist's playlist but do not play immediately
koto_playlist_set_track_as_current(self->playlist, track_uuid); // Set this track as the current one for the playlist
koto_playback_engine_set_track_by_uuid(playback_engine, track_uuid, FALSE); // Tell our playback engine to start playing at this track
}
@@ -304,7 +304,7 @@ void koto_track_table_handle_track_name_clicked(
gtk_widget_add_css_class(GTK_WIDGET(self->track_title_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
- koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME);
+ koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME);
}
void koto_track_table_handle_track_num_clicked(
@@ -320,13 +320,13 @@ void koto_track_table_handle_track_num_clicked(
(void) y;
KotoTrackTable * self = user_data;
- KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist);
+ KotoPreferredPlaylistSortType current_model = koto_playlist_get_current_model(self->playlist);
- if (current_model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) { // Set to newest currently
- koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST); // Sort reversed (oldest)
+ if (current_model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT) { // Set to newest currently
+ koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST); // Sort reversed (oldest)
koto_button_show_image(self->track_num_button, TRUE); // Use inverted value (pan-up-symbolic)
} else {
- koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // Sort newest
+ koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT); // Sort newest
koto_button_show_image(self->track_num_button, FALSE); // Use pan down default
}
}
@@ -385,7 +385,7 @@ void koto_track_table_handle_tracks_selected(
void koto_track_table_set_model(
KotoTrackTable * self,
- KotoPreferredModelType model
+ KotoPreferredPlaylistSortType model
) {
if (!KOTO_IS_TRACK_TABLE(self)) {
return;
@@ -394,15 +394,15 @@ void koto_track_table_set_model(
koto_playlist_apply_model(self->playlist, model); // Apply our new model
self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel
- if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
+ if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active");
}
- if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
+ if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active");
}
- if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
+ if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active");
}
@@ -414,7 +414,7 @@ void koto_track_table_set_model(
void koto_track_table_set_playlist_model (
KotoTrackTable * self,
- KotoPreferredModelType model
+ KotoPreferredPlaylistSortType model
) {
if (!KOTO_IS_TRACK_TABLE(self)) { // Not a track table
return;
@@ -427,15 +427,15 @@ void koto_track_table_set_playlist_model (
koto_playlist_apply_model(self->playlist, model); // Apply our new model
self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel
- if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
+ if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active");
}
- if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
+ if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active");
}
- if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
+ if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active");
}
@@ -444,9 +444,9 @@ void koto_track_table_set_playlist_model (
gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model
- KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist); // Get the current model
+ KotoPreferredPlaylistSortType current_model = koto_playlist_get_current_model(self->playlist); // Get the current model
- if (current_model == KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST) {
+ if (current_model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST) {
koto_button_show_image(self->track_num_button, TRUE); // Immediately use pan-up-symbolic
}
}
@@ -464,7 +464,7 @@ void koto_track_table_set_playlist(
}
self->playlist = playlist;
- koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // TODO: Enable this to be changed
+ koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT); // TODO: Enable this to be changed
}
void koto_track_table_setup_track_item(
diff --git a/src/components/track-table.h b/src/components/track-table.h
index 287d9b4..613cbf3 100644
--- a/src/components/track-table.h
+++ b/src/components/track-table.h
@@ -20,7 +20,7 @@
#include
#include
#include "../playlist/playlist.h"
-#include "koto-action-bar.h"
+#include "action-bar.h"
G_BEGIN_DECLS
@@ -98,12 +98,12 @@ void koto_track_table_handle_tracks_selected(
void koto_track_table_set_model(
KotoTrackTable * self,
- KotoPreferredModelType model
+ KotoPreferredPlaylistSortType model
);
void koto_track_table_set_playlist_model(
KotoTrackTable * self,
- KotoPreferredModelType model
+ KotoPreferredPlaylistSortType model
);
void koto_track_table_set_playlist(
diff --git a/src/config/config.c b/src/config/config.c
index 21624b8..d97403f 100644
--- a/src/config/config.c
+++ b/src/config/config.c
@@ -37,6 +37,11 @@ enum {
PROP_PLAYBACK_CONTINUE_ON_PLAYLIST,
PROP_PLAYBACK_LAST_USED_VOLUME,
PROP_PLAYBACK_MAINTAIN_SHUFFLE,
+ PROP_PREFERRED_ALBUM_SORT_TYPE,
+ PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION,
+ PROP_UI_ALBUM_INFO_SHOW_GENRES,
+ PROP_UI_ALBUM_INFO_SHOW_NARRATOR,
+ PROP_UI_ALBUM_INFO_SHOW_YEAR,
PROP_UI_THEME_DESIRED,
PROP_UI_THEME_OVERRIDE,
N_PROPS,
@@ -69,8 +74,17 @@ struct _KotoConfig {
gdouble playback_last_used_volume;
gboolean playback_maintain_shuffle;
+ /* Misc Prefs */
+
+ KotoPreferredAlbumSortType preferred_album_sort_type;
+
/* UI Settings */
+ gboolean ui_album_info_show_description;
+ gboolean ui_album_info_show_genres;
+ gboolean ui_album_info_show_narrator;
+ gboolean ui_album_info_show_year;
+
gchar * ui_theme_desired;
gboolean ui_theme_override;
};
@@ -132,6 +146,46 @@ static void koto_config_class_init(KotoConfigClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
+ config_props[PROP_PREFERRED_ALBUM_SORT_TYPE] = g_param_spec_string(
+ "artist-preferred-album-sort-type",
+ "Preferred album sort type (chronological or alphabetical-only)",
+ "Preferred album sort type (chronological or alphabetical-only)",
+ "default",
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
+ config_props[PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION] = g_param_spec_boolean(
+ "ui-album-info-show-description",
+ "Show Description in Album Info",
+ "Show Description in Album Info",
+ TRUE,
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
+ config_props[PROP_UI_ALBUM_INFO_SHOW_GENRES] = g_param_spec_boolean(
+ "ui-album-info-show-genres",
+ "Show Genres in Album Info",
+ "Show Genres in Album Info",
+ TRUE,
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
+ config_props[PROP_UI_ALBUM_INFO_SHOW_NARRATOR] = g_param_spec_boolean(
+ "ui-album-info-show-narrator",
+ "Show Narrator in Album Info",
+ "Show Narrator in Album Info",
+ TRUE,
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
+ config_props[PROP_UI_ALBUM_INFO_SHOW_YEAR] = g_param_spec_boolean(
+ "ui-album-info-show-year",
+ "Show Year in Album Info",
+ "Show Year in Album Info",
+ TRUE,
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
config_props[PROP_UI_THEME_DESIRED] = g_param_spec_string(
"ui-theme-desired",
"Desired Theme",
@@ -181,6 +235,18 @@ static void koto_config_get_property(
case PROP_PLAYBACK_MAINTAIN_SHUFFLE:
g_value_set_boolean(val, self->playback_maintain_shuffle);
break;
+ case PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION:
+ g_value_set_boolean(val, self->ui_album_info_show_description);
+ break;
+ case PROP_UI_ALBUM_INFO_SHOW_GENRES:
+ g_value_set_boolean(val, self->ui_album_info_show_genres);
+ break;
+ case PROP_UI_ALBUM_INFO_SHOW_NARRATOR:
+ g_value_set_boolean(val, self->ui_album_info_show_narrator);
+ break;
+ case PROP_UI_ALBUM_INFO_SHOW_YEAR:
+ g_value_set_boolean(val, self->ui_album_info_show_year);
+ break;
case PROP_UI_THEME_DESIRED:
g_value_set_string(val, g_strdup(self->ui_theme_desired));
break;
@@ -211,6 +277,21 @@ static void koto_config_set_property(
case PROP_PLAYBACK_MAINTAIN_SHUFFLE:
self->playback_maintain_shuffle = g_value_get_boolean(val);
break;
+ case PROP_PREFERRED_ALBUM_SORT_TYPE:
+ self->preferred_album_sort_type = g_strcmp0(g_value_get_string(val), "alphabetical") ? KOTO_PREFERRED_ALBUM_ALWAYS_ALPHABETICAL : KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT;
+ break;
+ case PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION:
+ self->ui_album_info_show_description = g_value_get_boolean(val);
+ break;
+ case PROP_UI_ALBUM_INFO_SHOW_GENRES:
+ self->ui_album_info_show_genres = g_value_get_boolean(val);
+ break;
+ case PROP_UI_ALBUM_INFO_SHOW_NARRATOR:
+ self->ui_album_info_show_narrator = g_value_get_boolean(val);
+ break;
+ case PROP_UI_ALBUM_INFO_SHOW_YEAR:
+ self->ui_album_info_show_year = g_value_get_boolean(val);
+ break;
case PROP_UI_THEME_DESIRED:
self->ui_theme_desired = g_strdup(g_value_get_string(val));
break;
@@ -238,7 +319,7 @@ toml_table_t * koto_config_get_library(
gchar * lib_uuid = (uuid_datum.ok) ? (gchar*) uuid_datum.u.s : NULL;
- if (koto_utils_is_string_valid(lib_uuid) && (g_strcmp0(library_uuid, lib_uuid) == 0)) { // Is a valid string and the libraries match
+ if (koto_utils_string_is_valid(lib_uuid) && (g_strcmp0(library_uuid, lib_uuid) == 0)) { // Is a valid string and the libraries match
return library;
}
}
@@ -253,7 +334,7 @@ void koto_config_load(
KotoConfig * self,
gchar * path
) {
- if (!koto_utils_is_string_valid(path)) { // Path is not valid
+ if (!koto_utils_string_is_valid(path)) { // Path is not valid
return;
}
@@ -374,6 +455,30 @@ void koto_config_load(
toml_table_t * ui_section = toml_table_in(conf, "ui");
if (ui_section) { // Have UI section
+ toml_datum_t album_info_show_description = toml_bool_in(ui_section, "album-info-show-description");
+
+ if (album_info_show_description.ok && (album_info_show_description.u.b != self->ui_album_info_show_description)) { // Changed if we are disabling description
+ g_object_set(self, "ui-album-info-show-description", album_info_show_description.u.b, NULL);
+ }
+
+ toml_datum_t album_info_show_genres = toml_bool_in(ui_section, "album-info-show-genres");
+
+ if (album_info_show_genres.ok && (album_info_show_genres.u.b != self->ui_album_info_show_genres)) { // Changed if we are disabling description
+ g_object_set(self, "ui-album-info-show-genres", album_info_show_genres.u.b, NULL);
+ }
+
+ toml_datum_t album_info_show_narrator = toml_bool_in(ui_section, "album-info-show-narrator");
+
+ if (album_info_show_narrator.ok && (album_info_show_narrator.u.b != self->ui_album_info_show_narrator)) { // Changed if we are disabling description
+ g_object_set(self, "ui-album-info-show-narrator", album_info_show_description.u.b, NULL);
+ }
+
+ toml_datum_t album_info_show_year = toml_bool_in(ui_section, "album-info-show-year");
+
+ if (album_info_show_year.ok && (album_info_show_year.u.b != self->ui_album_info_show_year)) { // Changed if we are disabling description
+ g_object_set(self, "ui-album-info-show-year", album_info_show_year.u.b, NULL);
+ }
+
toml_datum_t name = toml_string_in(ui_section, "theme-desired");
if (name.ok && (g_strcmp0(name.u.s, self->ui_theme_desired) != 0)) { // Have a name specified and they are different
@@ -472,6 +577,10 @@ void koto_config_monitor_handle_changed(
}
}
+KotoPreferredAlbumSortType koto_config_get_preferred_album_sort_type(KotoConfig * self) {
+ return self->preferred_album_sort_type;
+}
+
/**
* Refresh will handle any FS notify change on our Koto config file and call load
**/
@@ -568,7 +677,7 @@ void koto_config_save(KotoConfig * self) {
}
gchar * key_name = g_strdup(current_section_keyname->data);
- gchar * key_name_replaced = koto_utils_replace_string_all(key_name, g_strdup_printf("%s-", (gchar*) section_name), ""); // Remove SECTIONNAME-
+ gchar * key_name_replaced = koto_utils_string_replace_all(key_name, g_strdup_printf("%s-", (gchar*) section_name), ""); // Remove SECTIONNAME-
const gchar * line = g_strdup_printf("\t%s = %s", key_name_replaced, prop_val);
diff --git a/src/config/config.h b/src/config/config.h
index 2c68b18..5aa5f02 100644
--- a/src/config/config.h
+++ b/src/config/config.h
@@ -18,6 +18,7 @@
#pragma once
#include
#include
+#include "../indexer/misc-types.h"
G_BEGIN_DECLS
@@ -45,6 +46,8 @@ void koto_config_monitor_handle_changed(
gpointer user_data
);
+KotoPreferredAlbumSortType koto_config_get_preferred_album_sort_type(KotoConfig * self);
+
void koto_config_refresh(KotoConfig * self);
void koto_config_save(KotoConfig * self);
diff --git a/src/db/cartographer.c b/src/db/cartographer.c
index 87421dc..829a5c6 100644
--- a/src/db/cartographer.c
+++ b/src/db/cartographer.c
@@ -259,7 +259,7 @@ void koto_cartographer_add_album(
gchar * album_uuid = koto_album_get_uuid(album); // Get the album UUID
- if (!koto_utils_is_string_valid(album_uuid) || koto_cartographer_has_album_by_uuid(self, album_uuid)) { // Have the album or invalid UUID
+ if (!koto_utils_string_is_valid(album_uuid) || koto_cartographer_has_album_by_uuid(self, album_uuid)) { // Have the album or invalid UUID
return;
}
@@ -287,7 +287,7 @@ void koto_cartographer_add_artist(
gchar * artist_uuid = koto_artist_get_uuid(artist);
- if (!koto_utils_is_string_valid(artist_uuid) || koto_cartographer_has_artist_by_uuid(self, artist_uuid)) { // Have the artist or invalid UUID
+ if (!koto_utils_string_is_valid(artist_uuid) || koto_cartographer_has_artist_by_uuid(self, artist_uuid)) { // Have the artist or invalid UUID
return;
}
@@ -316,7 +316,7 @@ void koto_cartographer_add_library(
gchar * library_uuid = koto_library_get_uuid(library);
- if (!koto_utils_is_string_valid(library_uuid) || koto_cartographer_has_library_by_uuid(self, library_uuid)) { // Have the library or invalid UUID
+ if (!koto_utils_string_is_valid(library_uuid) || koto_cartographer_has_library_by_uuid(self, library_uuid)) { // Have the library or invalid UUID
return;
}
@@ -387,7 +387,7 @@ void koto_cartographer_add_track(
gchar * track_uuid = koto_track_get_uuid(track);
- if (!koto_utils_is_string_valid(track_uuid) || koto_cartographer_has_track_by_uuid(self, track_uuid)) { // Have the track or invalid UUID
+ if (!koto_utils_string_is_valid(track_uuid) || koto_cartographer_has_track_by_uuid(self, track_uuid)) { // Have the track or invalid UUID
return;
}
@@ -424,7 +424,7 @@ KotoArtist * koto_cartographer_get_artist_by_name(
return NULL;
}
- if (!koto_utils_is_string_valid(artist_name)) { // Not a valid name
+ if (!koto_utils_string_is_valid(artist_name)) { // Not a valid name
return NULL;
}
@@ -439,7 +439,7 @@ KotoArtist * koto_cartographer_get_artist_by_uuid(
return NULL;
}
- if (!koto_utils_is_string_valid(artist_uuid)) {
+ if (!koto_utils_string_is_valid(artist_uuid)) {
return NULL;
}
@@ -454,7 +454,7 @@ KotoLibrary * koto_cartographer_get_library_by_uuid(
return NULL;
}
- if (!koto_utils_is_string_valid(library_uuid)) { // Not a valid string
+ if (!koto_utils_string_is_valid(library_uuid)) { // Not a valid string
return NULL;
}
@@ -469,7 +469,7 @@ KotoLibrary * koto_cartographer_get_library_containing_path(
return NULL;
}
- if (!koto_utils_is_string_valid(relative_path)) { // Not a valid string
+ if (!koto_utils_string_is_valid(relative_path)) { // Not a valid string
return NULL;
}
@@ -503,7 +503,7 @@ GList * koto_cartographer_get_libraries_for_storage_uuid(
GList * libraries = NULL; // Initialize our list
- if (!koto_utils_is_string_valid(storage_uuid)) { // Not a valid storage UUID string
+ if (!koto_utils_string_is_valid(storage_uuid)) { // Not a valid storage UUID string
return libraries;
}
@@ -544,6 +544,10 @@ KotoTrack * koto_cartographer_get_track_by_uuid(
return NULL;
}
+ if (!koto_utils_string_is_valid(track_uuid)) {
+ return NULL;
+ }
+
return g_hash_table_lookup(self->tracks, track_uuid);
}
@@ -555,7 +559,7 @@ KotoTrack * koto_cartographer_get_track_by_uniqueish_key(
return NULL;
}
- if (!koto_utils_is_string_valid(key)) {
+ if (!koto_utils_string_is_valid(key)) {
return NULL;
}
@@ -588,7 +592,7 @@ gboolean koto_cartographer_has_album_by_uuid(
return FALSE;
}
- if (!koto_utils_is_string_valid(album_uuid)) { // Not a valid UUID
+ if (!koto_utils_string_is_valid(album_uuid)) { // Not a valid UUID
return FALSE;
}
@@ -618,7 +622,7 @@ gboolean koto_cartographer_has_artist_by_uuid(
return FALSE;
}
- if (!koto_utils_is_string_valid(artist_uuid)) { // Not a valid UUID
+ if (!koto_utils_string_is_valid(artist_uuid)) { // Not a valid UUID
return FALSE;
}
@@ -649,7 +653,7 @@ gboolean koto_cartographer_has_library_by_uuid(
return FALSE;
}
- if (!koto_utils_is_string_valid(library_uuid)) { // Not a valid UUID
+ if (!koto_utils_string_is_valid(library_uuid)) { // Not a valid UUID
return FALSE;
}
@@ -679,7 +683,7 @@ gboolean koto_cartographer_has_playlist_by_uuid(
return FALSE;
}
- if (!koto_utils_is_string_valid(playlist_uuid)) { // Not a valid UUID
+ if (!koto_utils_string_is_valid(playlist_uuid)) { // Not a valid UUID
return FALSE;
}
@@ -709,7 +713,7 @@ gboolean koto_cartographer_has_track_by_uuid(
return FALSE;
}
- if (!koto_utils_is_string_valid(track_uuid)) { // Not a valid UUID
+ if (!koto_utils_string_is_valid(track_uuid)) { // Not a valid UUID
return FALSE;
}
@@ -739,7 +743,7 @@ void koto_cartographer_remove_album_by_uuid(
return;
}
- if (!koto_utils_is_string_valid(album_uuid)) {
+ if (!koto_utils_string_is_valid(album_uuid)) {
return;
}
@@ -792,7 +796,7 @@ void koto_cartographer_remove_artist_by_uuid(
return;
}
- if (!koto_utils_is_string_valid(artist_uuid)) { // Artist UUID not valid
+ if (!koto_utils_string_is_valid(artist_uuid)) { // Artist UUID not valid
return;
}
@@ -842,7 +846,7 @@ void koto_cartographer_remove_playlist_by_uuid(
return;
}
- if (!koto_utils_is_string_valid(playlist_uuid)) { // Not a valid playlist UUID string
+ if (!koto_utils_string_is_valid(playlist_uuid)) { // Not a valid playlist UUID string
return;
}
@@ -883,7 +887,7 @@ void koto_cartographer_remove_track_by_uuid(
return;
}
- if (!koto_utils_is_string_valid(track_uuid)) {
+ if (!koto_utils_string_is_valid(track_uuid)) {
return;
}
diff --git a/src/db/db.c b/src/db/db.c
index 1c40b82..045d067 100644
--- a/src/db/db.c
+++ b/src/db/db.c
@@ -39,13 +39,13 @@ 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, genres string, 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, description string, narrator string, art_path string, genres string, year int, 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);"
- "CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int);"
- "CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, current int, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
+ "CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int, album_id string, track_id string, playback_position_of_track int);"
+ "CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
return (new_transaction(tables_creation_queries, "Failed to create required tables", TRUE) == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL;
}
diff --git a/src/db/loaders.c b/src/db/loaders.c
index f12855f..ebbee80 100644
--- a/src/db/loaders.c
+++ b/src/db/loaders.c
@@ -18,6 +18,7 @@
#include "cartographer.h"
#include "db.h"
#include "loaders.h"
+#include "../indexer/album-playlist-funcs.h"
#include "../indexer/structs.h"
#include "../koto-utils.h"
@@ -34,8 +35,8 @@ int process_artists(
(void) num_columns;
(void) column_names; // Don't need any of the params
- gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID
- gchar * artist_name = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is artist name
+ gchar * artist_uuid = g_strdup(koto_utils_string_unquote(fields[0])); // First column is UUID
+ gchar * artist_name = g_strdup(koto_utils_string_unquote(fields[1])); // Second column is artist name
KotoArtist * artist = koto_artist_new_with_uuid(artist_uuid); // Create our artist with the UUID
@@ -63,7 +64,7 @@ int process_artists(
koto_artist_set_as_finalized(artist); // Indicate it is finalized
- int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE artist_id=\"%s\"", artist_uuid), process_tracks, NULL, NULL); // Process our tracks by artist uuid
+ int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE artist_id=\"%s\" AND album_id=''", artist_uuid), process_tracks, NULL, NULL); // Load all tracks for an artist that are NOT in an album (e.g. artists without albums)
if (tracks_rc != SQLITE_OK) { // Failed to get our tracks
g_critical("Failed to read our tracks: %s", sqlite3_errmsg(koto_db));
@@ -87,8 +88,8 @@ int process_artist_paths(
KotoArtist * artist = (KotoArtist*) data;
- gchar * library_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
- gchar * relative_path = g_strdup(koto_utils_unquote_string(fields[2]));
+ gchar * library_uuid = g_strdup(koto_utils_string_unquote(fields[0]));
+ gchar * relative_path = g_strdup(koto_utils_string_unquote(fields[2]));
KotoLibrary * lib = koto_cartographer_get_library_by_uuid(koto_maps, library_uuid); // Get the library for this artist
@@ -112,11 +113,14 @@ int process_albums(
KotoArtist * artist = (KotoArtist*) data;
- gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
- 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;
+ gchar * album_uuid = g_strdup(koto_utils_string_unquote(fields[0]));
+ gchar * artist_uuid = g_strdup(koto_utils_string_unquote(fields[1]));
+ gchar * album_name = g_strdup(koto_utils_string_unquote(fields[2]));
+ gchar * album_description = (fields[3] != NULL) ? g_strdup(koto_utils_string_unquote(fields[3])) : NULL;
+ gchar * album_narrator = (fields[4] != NULL) ? g_strdup(koto_utils_string_unquote(fields[4])) : NULL;
+ gchar * album_art = (fields[5] != NULL) ? g_strdup(koto_utils_string_unquote(fields[5])) : NULL;
+ gchar * album_genres = (fields[6] != NULL) ? g_strdup(koto_utils_string_unquote(fields[6])) : NULL;
+ guint64 * album_year = (guint64*) g_ascii_strtoull(fields[7], NULL, 10);
KotoAlbum * album = koto_album_new_with_uuid(artist, album_uuid); // Create our album
@@ -124,16 +128,31 @@ int process_albums(
album,
"name",
album_name, // Set name
+ "description",
+ album_description,
+ "narrator",
+ album_narrator,
"art-path",
album_art, // Set art path if any
"preparsed-genres",
album_genres,
+ "year",
+ album_year,
NULL
);
koto_cartographer_add_album(koto_maps, album); // Add the album to our global cartographer
koto_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, NULL, NULL); // Process all the tracks for this specific album
+
+ if (tracks_rc != SQLITE_OK) { // Failed to get our tracks
+ g_critical("Failed to read our tracks: %s", sqlite3_errmsg(koto_db));
+ return 1;
+ }
+
+ koto_album_mark_as_finalized(album); // Mark the album as finalized now that all tracks have been loaded, allowing our internal album playlist to re-sort itself
+
g_free(album_uuid);
g_free(artist_uuid);
g_free(album_name);
@@ -156,26 +175,69 @@ int process_playlists(
(void) num_columns;
(void) column_names; // Don't need any of the params
- gchar * playlist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID
- gchar * playlist_name = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is playlist name
- gchar * playlist_art_path = g_strdup(koto_utils_unquote_string(fields[2])); // Third column is any art path
+ gchar * playlist_uuid = g_strdup(koto_utils_string_unquote(fields[0])); // First column is UUID
+ gchar * playlist_name = g_strdup(koto_utils_string_unquote(fields[1])); // Second column is playlist name
+ gchar * playlist_art_path = g_strdup(koto_utils_string_unquote(fields[2])); // Third column is any art path
+ guint64 playlist_preferred_sort = g_ascii_strtoull(koto_utils_string_unquote(fields[3]), NULL, 10); // Fourth column is preferred model which is an int
+ gchar * playlist_album_id = g_strdup(koto_utils_string_unquote(fields[4])); // Fifth column is any album ID
+ gchar * playlist_current_track_id = g_strdup(koto_utils_string_unquote(fields[5])); // Sixth column is any track ID
+ guint64 playlist_track_current_playback_pos = g_ascii_strtoull(koto_utils_string_unquote(fields[6]), NULL, 10); // Seventh column is any playback position for the track
- KotoPlaylist * playlist = koto_playlist_new_with_uuid(playlist_uuid); // Create a playlist using the existing UUID
+ KotoPreferredPlaylistSortType sort_type = (KotoPreferredPlaylistSortType) playlist_preferred_sort;
- koto_playlist_set_name(playlist, playlist_name); // Add the playlist name
- koto_playlist_set_artwork(playlist, playlist_art_path); // Add the playlist art path
+ KotoPlaylist * playlist = NULL;
- koto_cartographer_add_playlist(koto_maps, playlist); // Add to cartographer
+ gboolean for_album = FALSE;
- int playlist_tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM playlist_tracks WHERE playlist_id=\"%s\" ORDER BY position ASC", playlist_uuid), process_playlists_tracks, playlist, NULL); // Process our playlist tracks
+ if (koto_utils_string_is_valid(playlist_album_id)) { // Album UUID is set
+ KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, playlist_album_id); // Get the album based on the album ID set for the playlist
- if (playlist_tracks_rc != SQLITE_OK) { // Failed to get our playlist tracks
- g_critical("Failed to read our playlist tracks: %s", sqlite3_errmsg(koto_db));
- return 1;
+ if (KOTO_IS_ALBUM(album)) { // Is an album
+ playlist = koto_album_get_playlist(album); // Get the playlist
+ for_album = TRUE;
+ }
+ } else {
+ playlist = koto_playlist_new_with_uuid(playlist_uuid); // Create a playlist using the existing UUID
+ koto_playlist_apply_model(playlist, sort_type); // Set the model based on what is stored
+ koto_cartographer_add_playlist(koto_maps, playlist); // Add to cartographer
+ }
+
+ if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist yet, in this scenario it is likely the album no longer exists
+ goto free;
+ }
+
+ g_object_set(
+ playlist,
+ "name",
+ playlist_name,
+ "art-path",
+ playlist_art_path,
+ "ephemeral",
+ for_album,
+ NULL
+ );
+
+ if (!for_album) { // Isn't for an album
+ int playlist_tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM playlist_tracks WHERE playlist_id=\"%s\" ORDER BY position ASC", playlist_uuid), process_playlists_tracks, playlist, NULL); // Process our playlist tracks
+
+ if (playlist_tracks_rc != SQLITE_OK) { // Failed to get our playlist tracks
+ g_critical("Failed to read our playlist tracks: %s", sqlite3_errmsg(koto_db));
+ goto free;
+ }
+ }
+
+ if (koto_utils_string_is_valid(playlist_current_track_id)) { // If we have a track UUID (probably)
+ KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, playlist_current_track_id); // Get the track UUID
+
+ if (KOTO_IS_TRACK(track)) { // If this is a track
+ koto_track_set_playback_position(track, playlist_track_current_playback_pos); // Set the playback position of the track
+ koto_playlist_set_track_as_current(playlist, playlist_current_track_id); // Ensure we have this track set as the current one in the playlist
+ }
}
koto_playlist_mark_as_finalized(playlist); // Mark as finalized since loading should be complete
+free:
g_free(playlist_uuid);
g_free(playlist_name);
g_free(playlist_art_path);
@@ -193,9 +255,8 @@ int process_playlists_tracks(
(void) num_columns;
(void) column_names; // Don't need these
- gchar * playlist_uuid = g_strdup(koto_utils_unquote_string(fields[1]));
- gchar * track_uuid = g_strdup(koto_utils_unquote_string(fields[2]));
- gboolean current = g_strcmp0(koto_utils_unquote_string(fields[3]), "0");
+ gchar * playlist_uuid = g_strdup(koto_utils_string_unquote(fields[1]));
+ gchar * track_uuid = g_strdup(koto_utils_string_unquote(fields[2]));
KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, playlist_uuid); // Get the playlist
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track
@@ -204,7 +265,7 @@ int process_playlists_tracks(
goto freeforret;
}
- koto_playlist_add_track(playlist, track, current, FALSE); // Add the track to the playlist but don't re-commit to the table
+ koto_playlist_add_track(playlist, track, FALSE, FALSE); // Add the track to the playlist but don't re-commit to the table
freeforret:
g_free(playlist_uuid);
@@ -223,14 +284,22 @@ int process_tracks(
(void) num_columns;
(void) column_names; // Don't need these
- gchar * track_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
- gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[1]));
- gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[2]));
- gchar * name = g_strdup(koto_utils_unquote_string(fields[3]));
+ gchar * track_uuid = g_strdup(koto_utils_string_unquote(fields[0]));
+
+ KotoTrack * existing_track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid);
+
+ if (KOTO_IS_TRACK(existing_track)) { // Already have track
+ g_free(track_uuid);
+ return 0;
+ }
+
+ gchar * artist_uuid = g_strdup(koto_utils_string_unquote(fields[1]));
+ gchar * album_uuid = g_strdup(koto_utils_string_unquote(fields[2]));
+ gchar * name = g_strdup(koto_utils_string_unquote(fields[3]));
guint * disc_num = (guint*) g_ascii_strtoull(fields[4], 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]));
+ gchar * genres = g_strdup(koto_utils_string_unquote(fields[7]));
KotoTrack * track = koto_track_new_with_uuid(track_uuid); // Create our file
@@ -265,10 +334,12 @@ int process_tracks(
return 1;
}
+ koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary
+
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); // Get the artist
koto_artist_add_track(artist, track); // Add the track for the artist
- if (koto_utils_is_string_valid(album_uuid)) { // If we have an album UUID
+ if (koto_utils_string_is_valid(album_uuid)) { // If we have an album UUID
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); // Attempt to get album
if (KOTO_IS_ALBUM(album)) { // This is an album
@@ -293,13 +364,13 @@ int process_track_paths(
(void) num_columns;
(void) column_names; // Don't need these
- KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, koto_utils_unquote_string(fields[0]));
+ KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, koto_utils_string_unquote(fields[0]));
if (!KOTO_IS_LIBRARY(library)) { // Not a library
return 1;
}
- koto_track_set_path(track, library, koto_utils_unquote_string(fields[1]));
+ koto_track_set_path(track, library, koto_utils_string_unquote(fields[1]));
return 0;
}
diff --git a/src/indexer/album-playlist-funcs.h b/src/indexer/album-playlist-funcs.h
index 083f284..3a795cc 100644
--- a/src/indexer/album-playlist-funcs.h
+++ b/src/indexer/album-playlist-funcs.h
@@ -20,6 +20,6 @@
G_BEGIN_DECLS
-KotoPlaylist * koto_album_create_playlist(KotoAlbum * self);
+KotoPlaylist * koto_album_get_playlist(KotoAlbum * self);
G_END_DECLS
\ No newline at end of file
diff --git a/src/indexer/album.c b/src/indexer/album.c
index 06b9f73..0011601 100644
--- a/src/indexer/album.c
+++ b/src/indexer/album.c
@@ -36,10 +36,13 @@ enum {
PROP_0,
PROP_UUID,
PROP_DO_INITIAL_INDEX,
- PROP_ALBUM_NAME,
+ PROP_NAME,
PROP_ART_PATH,
PROP_ARTIST_UUID,
PROP_ALBUM_PREPARED_GENRES,
+ PROP_DESCRIPTION,
+ PROP_NARRATOR,
+ PROP_YEAR,
N_PROPERTIES
};
@@ -62,16 +65,20 @@ struct _KotoAlbum {
gchar * uuid;
gchar * name;
+ guint64 year;
+ gchar * description;
+ gchar * narrator;
gchar * art_path;
gchar * artist_uuid;
GList * genres;
- GList * tracks;
+ KotoPlaylist * playlist;
GHashTable * paths;
gboolean has_album_art;
gboolean do_initial_index;
+ gboolean finalized;
};
struct _KotoAlbumClass {
@@ -126,7 +133,7 @@ static void koto_album_class_init(KotoAlbumClass * c) {
G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
- props[PROP_ALBUM_NAME] = g_param_spec_string(
+ props[PROP_NAME] = g_param_spec_string(
"name",
"Name",
"Name of Album",
@@ -158,6 +165,32 @@ static void koto_album_class_init(KotoAlbumClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
+ props[PROP_DESCRIPTION] = g_param_spec_string(
+ "description",
+ "Description of Album, typically for an audiobook or podcast",
+ "Description of Album, typically for an audiobook or podcast",
+ NULL,
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
+ );
+
+ props[PROP_NARRATOR] = g_param_spec_string(
+ "narrator",
+ "Narrator of audiobook",
+ "Narrator of audiobook",
+ NULL,
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
+ );
+
+ props[PROP_YEAR] = g_param_spec_uint64(
+ "year",
+ "Year",
+ "Year of release",
+ 0,
+ G_MAXSHORT,
+ 0,
+ 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(
@@ -188,10 +221,19 @@ static void koto_album_class_init(KotoAlbumClass * c) {
}
static void koto_album_init(KotoAlbum * self) {
- self->has_album_art = FALSE;
+ self->description = NULL;
self->genres = NULL;
- self->tracks = NULL;
+ self->has_album_art = FALSE;
+ self->narrator = NULL;
self->paths = g_hash_table_new(g_str_hash, g_str_equal);
+
+ self->playlist = koto_playlist_new_with_uuid(NULL); // Create a playlist, with UUID set to NULL temporarily (until we know the album UUID)
+ koto_playlist_apply_model(self->playlist, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS); // Sort by track position
+ koto_playlist_set_album_uuid(self->playlist, self->uuid); // Set the playlist UUID to be the same as the album
+ g_object_set(self->playlist, "ephemeral", TRUE, NULL); // Set as ephemeral / temporary
+ koto_cartographer_add_playlist(koto_maps, self->playlist); // Add to cartographer
+
+ self->year = 0;
}
void koto_album_add_track(
@@ -206,14 +248,11 @@ void koto_album_add_track(
return;
}
- gchar * track_uuid = koto_track_get_uuid(track);
-
- if (g_list_index(self->tracks, track_uuid) != -1) { // Have added it already
+ if (koto_playlist_get_position_of_track(self->playlist, track) != -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;
@@ -234,6 +273,25 @@ void koto_album_add_track(
self->genres = g_list_append(self->genres, g_strdup(track_genre)); // Duplicate the genre and add it to our list
}
+ if (self->year == 0) { // Don't have a year set yet
+ guint64 track_year = koto_track_get_year(track); // Get the year from this track
+
+ if (track_year > 0) { // Have a probably valid year set
+ self->year = track_year; // Set our track
+ }
+ }
+
+ if (!koto_utils_string_is_valid(self->narrator)) { // No narrator set yet
+ gchar * track_narrator = koto_track_get_narrator(track); // Get the narrator for the track
+
+ if (koto_utils_string_is_valid(track_narrator)) { // If this track has a narrator
+ self->narrator = g_strdup(track_narrator);
+ g_free(track_narrator);
+ }
+ }
+
+ koto_playlist_add_track(self->playlist, track, FALSE, FALSE); // Add the track to our internal playlist
+
g_signal_emit(
self,
album_signals[SIGNAL_TRACK_ADDED],
@@ -250,14 +308,17 @@ void koto_album_commit(KotoAlbum * self) {
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, 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;",
+ "INSERT INTO albums(id, artist_id, name, description, narrator, art_path, genres, year)"
+ "VALUES('%s', '%s', quote(\"%s\"), quote(\"%s\"), quote(\"%s\"), quote(\"%s\"), '%s', %ld)"
+ "ON CONFLICT(id) DO UPDATE SET artist_id=excluded.artist_id, name=excluded.name, description=excluded.description, narrator=excluded.narrator, art_path=excluded.art_path, genres=excluded.genres, year=excluded.year;",
self->uuid,
self->artist_uuid,
- self->name,
- self->art_path,
- genres_string
+ koto_utils_string_get_valid(self->name),
+ koto_utils_string_get_valid(self->description),
+ koto_utils_string_get_valid(self->narrator),
+ koto_utils_string_get_valid(self->art_path),
+ koto_utils_string_get_valid(genres_string),
+ self->year
);
new_transaction(commit_op, "Failed to write our album to the database", FALSE);
@@ -284,42 +345,6 @@ void koto_album_commit(KotoAlbum * self) {
}
}
-KotoPlaylist * koto_album_create_playlist(KotoAlbum * self) {
- if (!KOTO_IS_ALBUM(self)) { // Not an album
- return NULL;
- }
-
- if (self->tracks == NULL) { // No files to add to the playlist
- return NULL;
- }
-
- KotoPlaylist * new_album_playlist = koto_playlist_new(); // Create a new playlist
-
- g_object_set(new_album_playlist, "ephemeral", TRUE, NULL); // Set as ephemeral / temporary
-
- // The following section effectively reverses our tracks, so the first is now last.
- // It then adds them in reverse order, since our playlist add function will prepend to our queue
- // This enables the preservation and defaulting of "newest" first everywhere else, while in albums preserving the rightful order of the album
- // e.g. first track (0) being added last is actually first in the playlist's tracks
- GList * reversed_tracks = g_list_copy(self->tracks); // Copy our tracks so we can reverse the order
-
- reversed_tracks = g_list_reverse(reversed_tracks); // Actually reverse it
-
- GList * t;
-
- for (t = reversed_tracks; t != NULL; t = t->next) { // For each of the tracks
- gchar * track_uuid = t->data;
- koto_playlist_add_track_by_uuid(new_album_playlist, track_uuid, FALSE, FALSE); // Add the UUID, skip commit to table since it is temporary
- }
-
- g_list_free(t);
- g_list_free(reversed_tracks);
-
- koto_playlist_apply_model(new_album_playlist, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // Ensure we are using our default model
-
- return new_album_playlist;
-}
-
void koto_album_find_album_art(KotoAlbum * self) {
if (self->has_album_art) { // If we already have album art
return;
@@ -376,14 +401,6 @@ 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,
@@ -399,7 +416,7 @@ static void koto_album_get_property(
case PROP_DO_INITIAL_INDEX:
g_value_set_boolean(val, self->do_initial_index);
break;
- case PROP_ALBUM_NAME:
+ case PROP_NAME:
g_value_set_string(val, self->name);
break;
case PROP_ART_PATH:
@@ -424,13 +441,12 @@ static void koto_album_set_property(
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]);
+ koto_album_set_uuid(self, g_value_get_string(val));
break;
case PROP_DO_INITIAL_INDEX:
self->do_initial_index = g_value_get_boolean(val);
break;
- case PROP_ALBUM_NAME: // Name of album
+ case PROP_NAME: // Name of album
koto_album_set_album_name(self, g_value_get_string(val));
break;
case PROP_ART_PATH: // Path to art
@@ -442,6 +458,15 @@ static void koto_album_set_property(
case PROP_ALBUM_PREPARED_GENRES:
koto_album_set_preparsed_genres(self, g_strdup(g_value_get_string(val)));
break;
+ case PROP_DESCRIPTION:
+ koto_album_set_description(self, g_value_get_string(val));
+ break;
+ case PROP_NARRATOR:
+ koto_album_set_narrator(self, g_value_get_string(val));
+ break;
+ case PROP_YEAR:
+ koto_album_set_year(self, g_value_get_uint64(val));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
@@ -453,7 +478,27 @@ gchar * koto_album_get_art(KotoAlbum * self) {
return g_strdup("");
}
- return g_strdup((self->has_album_art && koto_utils_is_string_valid(self->art_path)) ? self->art_path : "");
+ return g_strdup((self->has_album_art && koto_utils_string_is_valid(self->art_path)) ? self->art_path : "");
+}
+
+gchar * koto_album_get_artist_uuid(KotoAlbum * self) {
+ return KOTO_IS_ALBUM(self) ? self->artist_uuid : NULL;
+}
+
+gchar * koto_album_get_description(KotoAlbum * self) {
+ return KOTO_IS_ALBUM(self) ? self->description : NULL;
+}
+
+GList * koto_album_get_genres(KotoAlbum * self) {
+ return KOTO_IS_ALBUM(self) ? self->genres : NULL;
+}
+
+KotoLibraryType koto_album_get_lib_type(KotoAlbum * self) {
+ if (!KOTO_IS_ALBUM(self)) { // Not an album
+ return KOTO_LIBRARY_TYPE_UNKNOWN;
+ }
+
+ return koto_artist_get_lib_type(koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid)); // Get the lib type for the artist. If artist isn't valid, we just return UNKNOWN
}
gchar * koto_album_get_name(KotoAlbum * self) {
@@ -461,23 +506,15 @@ gchar * koto_album_get_name(KotoAlbum * self) {
return NULL;
}
- if (!koto_utils_is_string_valid(self->name)) { // Not set
+ if (!koto_utils_string_is_valid(self->name)) { // Not set
return NULL;
}
- return g_strdup(self->name); // Return duplicate of the name
+ return self->name; // Return name of the album
}
-gchar * koto_album_get_album_uuid(KotoAlbum * self) {
- if (!KOTO_IS_ALBUM(self)) { // Not an album
- return NULL;
- }
-
- if (!koto_utils_is_string_valid(self->uuid)) { // Not set
- return NULL;
- }
-
- return g_strdup(self->uuid); // Return a duplicate of the UUID
+gchar * koto_album_get_narrator(KotoAlbum * self) {
+ return KOTO_IS_ALBUM(self) ? self->narrator : NULL;
}
gchar * koto_album_get_path(KotoAlbum * self) {
@@ -492,7 +529,7 @@ gchar * koto_album_get_path(KotoAlbum * self) {
KotoLibrary * cur_library = libs->data; // Get this as a KotoLibrary
gchar * library_relative_path = g_hash_table_lookup(self->paths, koto_library_get_uuid(cur_library)); // Get any relative path in our paths based on the current UUID
- if (!koto_utils_is_string_valid(library_relative_path)) { // Not a valid path
+ if (!koto_utils_string_is_valid(library_relative_path)) { // Not a valid path
continue;
}
@@ -502,12 +539,26 @@ gchar * koto_album_get_path(KotoAlbum * self) {
return NULL;
}
-GList * koto_album_get_tracks(KotoAlbum * self) {
+KotoPlaylist * koto_album_get_playlist(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return NULL;
}
- return self->tracks; // Return the tracks
+ if (!KOTO_IS_PLAYLIST(self->playlist)) { // Not a playlist
+ return NULL;
+ }
+
+ return self->playlist;
+}
+
+GListStore * koto_album_get_store(KotoAlbum * self) {
+ KotoPlaylist * playlist = koto_album_get_playlist(self);
+
+ if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist
+ return NULL;
+ }
+
+ return koto_playlist_get_store(playlist); // Return the store from the playlist
}
gchar * koto_album_get_uuid(KotoAlbum * self) {
@@ -518,6 +569,89 @@ gchar * koto_album_get_uuid(KotoAlbum * self) {
return self->uuid; // Return the UUID
}
+guint64 koto_album_get_year(KotoAlbum * self) {
+ return KOTO_IS_ALBUM(self) ? self->year : 0;
+}
+
+void koto_album_mark_as_finalized(KotoAlbum * self) {
+ if (!KOTO_IS_ALBUM(self)) { // Not an album
+ return;
+ }
+
+ if (self->finalized) { // Already finalized
+ return;
+ }
+
+ self->finalized = TRUE;
+ koto_playlist_mark_as_finalized(self->playlist);
+ //koto_playlist_apply_model(self->playlist, self->model); // Resort our playlist
+}
+
+void koto_album_remove_track(
+ KotoAlbum * self,
+ KotoTrack * track
+) {
+ if (!KOTO_IS_ALBUM(self)) { // Not an album
+ return;
+ }
+
+ if (!KOTO_IS_TRACK(track)) { // Not a track
+ return;
+ }
+
+ koto_playlist_remove_track_by_uuid(
+ self->playlist,
+ koto_track_get_uuid(track)
+ );
+
+ g_signal_emit(
+ self,
+ album_signals[SIGNAL_TRACK_REMOVED],
+ 0,
+ track
+ );
+}
+
+void koto_album_set_album_name(
+ KotoAlbum * self,
+ const gchar * album_name
+) {
+ if (!KOTO_IS_ALBUM(self)) { // Not an album
+ return;
+ }
+
+ if (album_name == NULL) { // Not valid album name
+ return;
+ }
+
+ if (self->name != NULL) {
+ g_free(self->name);
+ }
+
+ self->name = g_strdup(album_name);
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NAME]);
+}
+
+void koto_album_set_artist_uuid(
+ KotoAlbum * self,
+ const gchar * artist_uuid
+) {
+ if (!KOTO_IS_ALBUM(self)) { // Not an album
+ return;
+ }
+
+ if (artist_uuid == NULL) {
+ return;
+ }
+
+ if (self->artist_uuid != NULL) {
+ g_free(self->artist_uuid);
+ }
+
+ self->artist_uuid = g_strdup(artist_uuid);
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_UUID]);
+}
+
void koto_album_set_album_art(
KotoAlbum * self,
const gchar * album_art
@@ -537,6 +671,56 @@ void koto_album_set_album_art(
self->art_path = g_strdup(album_art);
self->has_album_art = TRUE;
+
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ART_PATH]);
+}
+
+void koto_album_set_as_current_playlist(KotoAlbum * self) {
+ if (!KOTO_IS_ALBUM(self)) {
+ return;
+ }
+
+ if (!KOTO_IS_CURRENT_PLAYLIST(current_playlist)) {
+ return;
+ }
+
+ if (!KOTO_IS_PLAYLIST(self->playlist)) { // Don't have a playlist for the album for some reason
+ return;
+ }
+
+ koto_current_playlist_set_playlist(current_playlist, self->playlist, TRUE, FALSE); // Set our new current playlist and start playing immediately
+}
+
+void koto_album_set_description(
+ KotoAlbum * self,
+ const gchar * description
+) {
+ if (!KOTO_IS_ALBUM(self)) { // Not an album
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(description)) {
+ return;
+ }
+
+ self->description = g_strdup(description);
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DESCRIPTION]);
+}
+
+void koto_album_set_narrator(
+ KotoAlbum * self,
+ const gchar * narrator
+) {
+ if (!KOTO_IS_ALBUM(self)) { // Not an album
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(narrator)) {
+ return;
+ }
+
+ self->narrator = g_strdup(narrator);
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NARRATOR]);
}
void koto_album_set_path(
@@ -564,83 +748,6 @@ void koto_album_set_path(
self->do_initial_index = FALSE;
}
-void koto_album_remove_track(
- KotoAlbum * self,
- KotoTrack * track
-) {
- if (!KOTO_IS_ALBUM(self)) { // Not an album
- 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,
- album_signals[SIGNAL_TRACK_REMOVED],
- 0,
- track
- );
-}
-
-void koto_album_set_album_name(
- KotoAlbum * self,
- const gchar * album_name
-) {
- if (!KOTO_IS_ALBUM(self)) { // Not an album
- return;
- }
-
- if (album_name == NULL) { // Not valid album name
- return;
- }
-
- if (self->name != NULL) {
- g_free(self->name);
- }
-
- self->name = g_strdup(album_name);
-}
-
-void koto_album_set_artist_uuid(
- KotoAlbum * self,
- const gchar * artist_uuid
-) {
- if (!KOTO_IS_ALBUM(self)) { // Not an album
- return;
- }
-
- if (artist_uuid == NULL) {
- return;
- }
-
- if (self->artist_uuid != NULL) {
- g_free(self->artist_uuid);
- }
-
- self->artist_uuid = g_strdup(artist_uuid);
-}
-
-void koto_album_set_as_current_playlist(KotoAlbum * self) {
- if (!KOTO_IS_ALBUM(self)) {
- return;
- }
-
- if (!KOTO_IS_CURRENT_PLAYLIST(current_playlist)) {
- return;
- }
-
- KotoPlaylist * album_playlist = koto_album_create_playlist(self);
-
- if (!KOTO_IS_PLAYLIST(album_playlist)) {
- return;
- }
-
- koto_current_playlist_set_playlist(current_playlist, album_playlist, TRUE); // Set our new current playlist and start playing immediately
-}
-
void koto_album_set_preparsed_genres(
KotoAlbum * self,
gchar * genrelist
@@ -649,7 +756,7 @@ void koto_album_set_preparsed_genres(
return;
}
- if (!koto_utils_is_string_valid(genrelist)) { // If it is an empty string
+ if (!koto_utils_string_is_valid(genrelist)) { // If it is an empty string
return;
}
@@ -665,8 +772,49 @@ void koto_album_set_preparsed_genres(
self->genres = preparsed_genres_list;
};
+void koto_album_set_uuid(
+ KotoAlbum * self,
+ const gchar * uuid
+) {
+ if (!KOTO_IS_ALBUM(self)) { // Not an album
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(uuid)) {
+ return;
+ }
+
+ self->uuid = g_strdup(uuid);
+ g_object_set(
+ self->playlist,
+ "album-uuid",
+ self->uuid,
+ "uuid",
+ self->uuid, // Ensure the playlist has the same UUID as the album
+ NULL
+ );
+
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]);
+}
+
+void koto_album_set_year(
+ KotoAlbum * self,
+ guint64 year
+) {
+ if (!KOTO_IS_ALBUM(self)) { // Not an album
+ return;
+ }
+
+ if (year <= 0) {
+ return;
+ }
+
+ self->year = year;
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_YEAR]);
+}
+
KotoAlbum * koto_album_new(gchar * artist_uuid) {
- if (!koto_utils_is_string_valid(artist_uuid)) { // Invalid artist UUID provided
+ if (!koto_utils_string_is_valid(artist_uuid)) { // Invalid artist UUID provided
return NULL;
}
@@ -688,14 +836,12 @@ KotoAlbum * koto_album_new_with_uuid(
KotoArtist * artist,
const gchar * uuid
) {
- gchar * artist_uuid = NULL;
-
- g_object_get(artist, "uuid", &artist_uuid, NULL);
+ gchar * artist_uuid = koto_artist_get_uuid(artist);
return g_object_new(
KOTO_TYPE_ALBUM,
"artist-uuid",
- artist,
+ artist_uuid,
"uuid",
g_strdup(uuid),
"do-initial-index",
diff --git a/src/indexer/artist.c b/src/indexer/artist.c
index 47def9f..0bf30e4 100644
--- a/src/indexer/artist.c
+++ b/src/indexer/artist.c
@@ -17,6 +17,7 @@
#include
#include
+#include "../config/config.h"
#include "../db/db.h"
#include "../db/cartographer.h"
#include "../playlist/playlist.h"
@@ -26,6 +27,7 @@
#include "track-helpers.h"
extern KotoCartographer * koto_maps;
+extern KotoConfig * config;
enum {
PROP_0,
@@ -60,10 +62,12 @@ struct _KotoArtist {
gboolean finalized;
gboolean has_artist_art;
gchar * artist_name;
- GList * albums;
GList * tracks;
GHashTable * paths;
KotoLibraryType type;
+
+ GQueue * albums;
+ GListStore * albums_store;
};
struct _KotoArtistClass {
@@ -195,7 +199,8 @@ static void koto_artist_class_init(KotoArtistClass * c) {
}
static void koto_artist_init(KotoArtist * self) {
- self->albums = NULL; // Create a new GList
+ self->albums = g_queue_new(); // Create a new GQueue
+ self->albums_store = g_list_store_new(KOTO_TYPE_ALBUM); // Create our GListStore of type KotoAlbum
self->content_playlist = koto_playlist_new(); // Create our playlist
g_object_set(
@@ -223,7 +228,7 @@ void koto_artist_commit(KotoArtist * self) {
"VALUES ('%s', quote(\"%s\"), NULL)"
"ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;",
self->uuid,
- self->artist_name
+ koto_utils_string_get_valid(self->artist_name)
);
new_transaction(commit_op, "Failed to write our artist to the database", FALSE);
@@ -248,14 +253,6 @@ void koto_artist_commit(KotoArtist * self) {
}
}
-KotoPlaylist * koto_artist_get_playlist(KotoArtist * self) {
- if (!KOTO_IS_ARTIST(self)) {
- return NULL;
- }
-
- return self->content_playlist;
-}
-
static void koto_artist_get_property(
GObject * obj,
guint prop_id,
@@ -311,13 +308,21 @@ void koto_artist_add_album(
return;
}
- gchar * album_uuid = koto_album_get_uuid(album);
+ GList * found_albums = g_queue_find(self->albums, album); // Try finding the album
- if (g_list_index(self->albums, album_uuid) != -1) { // If we have already added the album
+ if (found_albums != NULL) { // Already has been added
+ g_list_free(found_albums);
return;
}
- self->albums = g_list_append(self->albums, album_uuid); // Push to our album's UUID to the end of the list
+ g_list_free(found_albums);
+
+ g_queue_push_tail(self->albums, album); // Add the album to end of albums GQueue
+ g_list_store_append(self->albums_store, album); // Add the album to th estore as well
+
+ if (self->finalized) { // Is already finalized
+ koto_artist_apply_model(self, koto_config_get_preferred_album_sort_type(config)); // Apply our model to sort our albums gqueue and store
+ }
g_signal_emit(
self,
@@ -358,12 +363,32 @@ void koto_artist_add_track(
);
}
-GList * koto_artist_get_albums(KotoArtist * self) {
+void koto_artist_apply_model(
+ KotoArtist * self,
+ KotoPreferredAlbumSortType model
+) {
+ if (!KOTO_IS_ARTIST(self)) {
+ return;
+ }
+
+ g_queue_sort(self->albums, koto_artist_model_sort_albums, &model);
+ g_list_store_sort(self->albums_store, koto_artist_model_sort_albums, &model);
+}
+
+GQueue * koto_artist_get_albums(KotoArtist * self) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return NULL;
}
- return g_list_copy(self->albums);
+ return self->albums;
+}
+
+GListStore * koto_artist_get_albums_store(KotoArtist * self) {
+ if (!KOTO_IS_ARTIST(self)) { // Not an artist
+ return NULL;
+ }
+
+ return self->albums_store;
}
KotoAlbum * koto_artist_get_album_by_name(
@@ -377,15 +402,15 @@ KotoAlbum * koto_artist_get_album_by_name(
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
+ for (cur_list_iter = self->albums->head; cur_list_iter != NULL; cur_list_iter = cur_list_iter->next) { // Iterate through our albums by their UUIDs
+ KotoAlbum * this_album = cur_list_iter->data;
- if (!KOTO_IS_ALBUM(album_for_uuid)) { // Not an album
+ if (!KOTO_IS_ALBUM(this_album)) { // 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;
+ if (g_strcmp0(koto_album_get_name(this_album), album_name) == 0) { // These album names match
+ album = this_album;
break;
}
}
@@ -398,7 +423,15 @@ gchar * koto_artist_get_name(KotoArtist * self) {
return g_strdup("");
}
- return g_strdup(koto_utils_is_string_valid(self->artist_name) ? self->artist_name : ""); // Return artist name if set
+ return g_strdup(koto_utils_string_is_valid(self->artist_name) ? self->artist_name : ""); // Return artist name if set
+}
+
+KotoPlaylist * koto_artist_get_playlist(KotoArtist * self) {
+ if (!KOTO_IS_ARTIST(self)) {
+ return NULL;
+ }
+
+ return self->content_playlist;
}
GList * koto_artist_get_tracks(KotoArtist * self) {
@@ -425,7 +458,12 @@ void koto_artist_remove_album(
return;
}
- self->albums = g_list_remove(self->albums, koto_album_get_uuid(album));
+ g_queue_remove(self->albums, album); // Remove the album
+
+ guint position = 0;
+ if (g_list_store_find(self->albums_store, album, &position)) { // Found the album in the list store
+ g_list_store_remove(self->albums_store, position); // Remove from the list store
+ }
g_signal_emit(
self,
@@ -468,11 +506,11 @@ void koto_artist_set_artist_name(
return;
}
- if (!koto_utils_is_string_valid(artist_name)) { // No artist name
+ if (!koto_utils_string_is_valid(artist_name)) { // No artist name
return;
}
- if (koto_utils_is_string_valid(self->artist_name)) { // Has artist name
+ if (koto_utils_string_is_valid(self->artist_name)) { // Has artist name
g_free(self->artist_name);
}
@@ -487,7 +525,7 @@ void koto_artist_set_as_finalized(KotoArtist * self) {
self->finalized = TRUE;
- if (g_list_length(self->albums) == 0) { // Have no albums
+ if (g_queue_get_length(self->albums) == 0) { // Have no albums
g_signal_emit_by_name(self, "has-no-albums");
}
}
@@ -515,6 +553,36 @@ void koto_artist_set_path(
self->type = koto_library_get_lib_type(lib); // Define our artist type as the type from the library
}
+gint koto_artist_model_sort_albums(
+ gconstpointer first_item,
+ gconstpointer second_item,
+ gpointer user_data
+) {
+ KotoPreferredAlbumSortType * preferred_model = (KotoPreferredAlbumSortType*) user_data;
+
+ KotoAlbum * first_album = KOTO_ALBUM(first_item);
+ KotoAlbum * second_album = KOTO_ALBUM(second_item);
+
+ if (KOTO_IS_ALBUM(first_album) && !KOTO_IS_ALBUM(second_album)) { // First album is valid, second isn't
+ return -1;
+ } else if (!KOTO_IS_ALBUM(first_album) && KOTO_IS_ALBUM(second_album)) { // Second album is valid, first isn't
+ return 1;
+ }
+
+ if (preferred_model == KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT) { // Sort chronological before alphabetical
+ guint64 fa_year = koto_album_get_year(first_album);
+ guint64 sa_year = koto_album_get_year(second_album);
+
+ if (fa_year > sa_year) { // First album is newer than second
+ return -1;
+ } else if (fa_year < sa_year) { // Second album is newer than first
+ return 1;
+ }
+ }
+
+ return g_utf8_collate(koto_album_get_name(first_album), koto_album_get_name(second_album));
+}
+
KotoArtist * koto_artist_new(gchar * artist_name) {
KotoArtist * artist = g_object_new(
KOTO_TYPE_ARTIST,
diff --git a/src/indexer/file-indexer.c b/src/indexer/file-indexer.c
index 4cfa0b1..9bd9e34 100644
--- a/src/indexer/file-indexer.c
+++ b/src/indexer/file-indexer.c
@@ -92,12 +92,12 @@ void index_folder(
gchar * artist_name = g_strdup(split[split_len - 3]);
g_strfreev(split);
- if (!koto_utils_is_string_valid(album_name)) {
+ if (!koto_utils_string_is_valid(album_name)) {
g_free(album_name);
continue;
}
- if (!koto_utils_is_string_valid(artist_name)) {
+ if (!koto_utils_string_is_valid(artist_name)) {
g_free(album_name);
g_free(artist_name);
continue;
@@ -143,14 +143,17 @@ void index_file(
return;
}
+ gboolean for_audiobook = (koto_library_get_lib_type(lib) == KOTO_LIBRARY_TYPE_AUDIOBOOK);
+
gchar * relative_path_to_file = koto_library_get_relative_path_to_file(lib, g_strdup(path)); // Strip out library path so we have a relative path to the file
+ gchar * file_basename = g_path_get_basename(relative_path_to_file);
+
gchar ** split_on_relative_slashes = g_strsplit(relative_path_to_file, G_DIR_SEPARATOR_S, -1); // Split based on separator (e.g. / )
guint slash_sep_count = g_strv_length(split_on_relative_slashes);
gchar * artist_author_podcast_name = g_strdup(split_on_relative_slashes[0]); // No matter what, artist should be first
gchar * album_or_audiobook_name = NULL;
- gchar * file_name = koto_track_helpers_get_name_for_file(path, artist_author_podcast_name); // Get the name of the file
- guint cd = (guint) 1;
+ guint cd = (guint) 0;
if (slash_sep_count >= 3) { // If this it is at least "artist" + "album" + "file" (or with CD)
album_or_audiobook_name = g_strdup(split_on_relative_slashes[1]); // Duplicate the second item as the album or audiobook name
@@ -159,22 +162,41 @@ void index_file(
// #region CD parsing logic
if ((slash_sep_count == 4)) { // If is at least "artist" + "album" + "cd" + "file"
- gchar * cd_str = g_strdup(g_strstrip(koto_utils_replace_string_all(g_utf8_strdown(split_on_relative_slashes[2], -1), "cd", ""))); // Replace a lowercased version of our CD ("cd") and trim any whitespace
+ gchar * cd_str = g_strdup(g_strstrip(koto_utils_string_replace_all(g_utf8_strdown(split_on_relative_slashes[2], -1), "cd", ""))); // Replace a lowercased version of our CD ("cd") and trim any whitespace
- cd = (guint) g_ascii_strtoull(cd_str, NULL, 10); // Attempt to convert
+ cd = (guint) g_ascii_strtoull(cd_str, NULL, 10); // Attempt to convert}
+ }
- if (cd == 0) { // Had an error during conversion
- cd = 1; // Set back to 1
- }
+ if (for_audiobook && (cd == 0)) { // No CD yet and is for an audiobook
+ cd = koto_track_helpers_get_cd_based_on_file_name(file_basename); // Base on file name
+ } else {
+ cd = 1;
}
// #endregion
+ gchar * file_name = NULL;
+
+ if (for_audiobook) { // If this is for an audiobook library
+ guint64 pos = koto_track_helpers_get_position_based_on_file_name(file_basename);
+
+ if (cd != 1) { // On a non "Part 1" audiobook
+ file_name = g_strdup_printf("%s - Part %u - %lu", album_or_audiobook_name, cd, pos);
+ } else { // Part 1 / CD 1
+ file_name = g_strdup_printf("%s - %lu", album_or_audiobook_name, pos);
+ }
+ }
+
+ if (!koto_utils_string_is_valid(file_name)) { // No valid file name yet
+ file_name = koto_track_helpers_get_name_for_file(path, artist_author_podcast_name); // Get the name of the file
+ }
+
g_strfreev(split_on_relative_slashes);
+ g_free(file_basename);
gchar * sorta_uniqueish_key = NULL;
- if (koto_utils_is_string_valid(album_or_audiobook_name)) { // Have audiobook or album name
+ if (koto_utils_string_is_valid(album_or_audiobook_name)) { // Have audiobook or album name
sorta_uniqueish_key = g_strdup_printf("%s-%s-%s", artist_author_podcast_name, album_or_audiobook_name, file_name);
} else { // No audiobook or album name
sorta_uniqueish_key = g_strdup_printf("%s-%s", artist_author_podcast_name, file_name);
@@ -193,7 +215,7 @@ void index_file(
KotoAlbum * album = NULL;
- if (koto_utils_is_string_valid(album_or_audiobook_name)) { // Have an album or audiobook name
+ if (koto_utils_string_is_valid(album_or_audiobook_name)) { // Have an album or audiobook name
KotoAlbum * possible_album = koto_artist_get_album_by_name(artist, album_or_audiobook_name);
album = KOTO_IS_ALBUM(possible_album) ? possible_album : NULL;
}
diff --git a/src/indexer/library.c b/src/indexer/library.c
index 97a1d67..1365e8d 100644
--- a/src/indexer/library.c
+++ b/src/indexer/library.c
@@ -245,7 +245,7 @@ gchar * koto_library_get_relative_path_to_file(
}
gchar * appended_slash_to_library_path = g_str_has_suffix(self->path, G_DIR_SEPARATOR_S) ? g_strdup(self->path) : g_strdup_printf("%s%s", g_strdup(self->path), G_DIR_SEPARATOR_S);
- gchar * cleaned_path = koto_utils_replace_string_all(full_path, appended_slash_to_library_path, ""); // Replace the full path
+ gchar * cleaned_path = koto_utils_string_replace_all(full_path, appended_slash_to_library_path, ""); // Replace the full path
g_free(appended_slash_to_library_path);
return cleaned_path;
@@ -299,11 +299,11 @@ void koto_library_set_name(
return;
}
- if (!koto_utils_is_string_valid(library_name)) { // Not a string
+ if (!koto_utils_string_is_valid(library_name)) { // Not a string
return;
}
- if (koto_utils_is_string_valid(self->name)) { // Name already set
+ if (koto_utils_string_is_valid(self->name)) { // Name already set
g_free(self->name); // Free the existing value
}
@@ -319,15 +319,15 @@ void koto_library_set_path(
return;
}
- if (!koto_utils_is_string_valid(path)) { // Not a valid string
+ if (!koto_utils_string_is_valid(path)) { // Not a valid string
return;
}
- if (koto_utils_is_string_valid(self->path)) {
+ if (koto_utils_string_is_valid(self->path)) {
g_free(self->path);
}
- self->relative_path = g_path_is_absolute(path) ? koto_utils_replace_string_all(path, self->mount_path, "") : path; // Ensure path is relative to our mount, even if the mount is really our own system partition
+ self->relative_path = g_path_is_absolute(path) ? koto_utils_string_replace_all(path, self->mount_path, "") : path; // Ensure path is relative to our mount, even if the mount is really our own system partition
self->path = g_build_path(G_DIR_SEPARATOR_S, self->mount_path, self->relative_path, NULL); // Ensure our path is to whatever the current path of the mount + relative path is
}
@@ -345,7 +345,7 @@ void koto_library_set_storage_uuid(
g_free(self->mount_path);
}
- if (!koto_utils_is_string_valid(storage_uuid)) { // Not a valid string, which actually is allowed for built-ins
+ if (!koto_utils_string_is_valid(storage_uuid)) { // Not a valid string, which actually is allowed for built-ins
self->mount = NULL;
self->mount_path = g_strdup_printf("%s%s", g_get_home_dir(), G_DIR_SEPARATOR_S); // Set mount path to user's home directory
self->storage_uuid = NULL;
@@ -378,11 +378,11 @@ gchar * koto_library_to_config_string(KotoLibrary * self) {
g_strv_builder_add(lib_builder, g_strdup_printf("\tdirectory=\"%s\"", self->relative_path)); // Add the directory
- if (koto_utils_is_string_valid(self->name)) { // Have a library name
+ if (koto_utils_string_is_valid(self->name)) { // Have a library name
g_strv_builder_add(lib_builder, g_strdup_printf("\tname=\"%s\"", self->name)); // Add the name
}
- if (koto_utils_is_string_valid(self->storage_uuid)) { // Have a storage UUID (not applicable to built-ins)
+ if (koto_utils_string_is_valid(self->storage_uuid)) { // Have a storage UUID (not applicable to built-ins)
g_strv_builder_add(lib_builder, g_strdup_printf("\tstorage_uuid=\"%s\"", self->storage_uuid)); // Add the storage UUID
}
diff --git a/src/indexer/misc-types.h b/src/indexer/misc-types.h
new file mode 100644
index 0000000..e3963e6
--- /dev/null
+++ b/src/indexer/misc-types.h
@@ -0,0 +1,22 @@
+/* misc-types.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
+typedef enum {
+ KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT, // Chronological is considered default
+ KOTO_PREFERRED_ALBUM_ALWAYS_ALPHABETICAL, // Prefer sorting alphabetically
+} KotoPreferredAlbumSortType;
\ No newline at end of file
diff --git a/src/indexer/structs.h b/src/indexer/structs.h
index c4b3951..786e4c7 100644
--- a/src/indexer/structs.h
+++ b/src/indexer/structs.h
@@ -16,9 +16,11 @@
*/
#pragma once
-#include
+#include
+#include
#include
#include
+#include "misc-types.h"
typedef enum {
KOTO_LIBRARY_TYPE_AUDIOBOOK = 1,
@@ -148,9 +150,16 @@ void koto_artist_add_track(
KotoTrack * track
);
+void koto_artist_apply_model(
+ KotoArtist * self,
+ KotoPreferredAlbumSortType model
+);
+
void koto_artist_commit(KotoArtist * self);
-GList * koto_artist_get_albums(KotoArtist * self);
+GQueue * koto_artist_get_albums(KotoArtist * self);
+
+GListStore * koto_artist_get_albums_store(KotoArtist * self);
KotoAlbum * koto_artist_get_album_by_name(
KotoArtist * self,
@@ -159,12 +168,20 @@ KotoAlbum * koto_artist_get_album_by_name(
gchar * koto_artist_get_name(KotoArtist * self);
+gchar * koto_artist_get_path(KotoArtist * self);
+
GList * koto_artist_get_tracks(KotoArtist * self);
gchar * koto_artist_get_uuid(KotoArtist * self);
KotoLibraryType koto_artist_get_lib_type(KotoArtist * self);
+gint koto_artist_model_sort_albums(
+ gconstpointer first_item,
+ gconstpointer second_item,
+ gpointer user_data
+);
+
void koto_artist_remove_album(
KotoArtist * self,
KotoAlbum * album
@@ -211,16 +228,28 @@ void koto_album_find_album_art(KotoAlbum * self);
gchar * koto_album_get_art(KotoAlbum * self);
+gchar * koto_album_get_artist_uuid(KotoAlbum * self);
+
+gchar * koto_album_get_description(KotoAlbum * self);
+
+GList * koto_album_get_genres(KotoAlbum * self);
+
+KotoLibraryType koto_album_get_lib_type(KotoAlbum * self);
+
gchar * koto_album_get_name(KotoAlbum * self);
-gchar * koto_album_get_album_uuid(KotoAlbum * self);
+gchar * koto_album_get_narrator(KotoAlbum * self);
gchar * koto_album_get_path(KotoAlbum * self);
-GList * koto_album_get_tracks(KotoAlbum * self);
+GListStore * koto_album_get_store(KotoAlbum * self);
gchar * koto_album_get_uuid(KotoAlbum * self);
+guint64 koto_album_get_year(KotoAlbum * self);
+
+void koto_album_mark_as_finalized(KotoAlbum * self);
+
void koto_album_remove_track(
KotoAlbum * self,
KotoTrack * track
@@ -241,8 +270,17 @@ void koto_album_set_artist_uuid(
const gchar * artist_uuid
);
+void koto_album_set_description(
+ KotoAlbum * self,
+ const char * description
+);
+
void koto_album_set_as_current_playlist(KotoAlbum * self);
+void koto_album_set_narrator(
+ KotoAlbum * self,
+ const char * narrator
+);
void koto_album_set_path(
KotoAlbum * self,
@@ -255,6 +293,16 @@ void koto_album_set_preparsed_genres(
gchar * genrelist
);
+void koto_album_set_uuid(
+ KotoAlbum * self,
+ const gchar * uuid
+);
+
+void koto_album_set_year(
+ KotoAlbum * self,
+ guint64 year
+);
+
/**
* File / Track Functions
**/
@@ -270,6 +318,8 @@ KotoTrack * koto_track_new_with_uuid(const gchar * uuid);
void koto_track_commit(KotoTrack * self);
+gchar * koto_track_get_description(KotoTrack * self);
+
guint koto_track_get_disc_number(KotoTrack * self);
guint64 koto_track_get_duration(KotoTrack * self);
@@ -282,12 +332,18 @@ gchar * koto_track_get_path(KotoTrack * self);
gchar * koto_track_get_name(KotoTrack * self);
+gchar * koto_track_get_narrator(KotoTrack * self);
+
+guint64 koto_track_get_playback_position(KotoTrack * self);
+
guint64 koto_track_get_position(KotoTrack * self);
gchar * koto_track_get_uniqueish_key(KotoTrack * self);
gchar * koto_track_get_uuid(KotoTrack * self);
+guint64 koto_track_get_year(KotoTrack * self);
+
void koto_track_remove_from_playlist(
KotoTrack * self,
gchar * playlist_uuid
@@ -300,13 +356,7 @@ void koto_track_set_album_uuid(
void koto_track_save_to_playlist(
KotoTrack * self,
- gchar * playlist_uuid,
- gint current
-);
-
-void koto_track_set_file_name(
- KotoTrack * self,
- gchar * new_file_name
+ gchar * playlist_uuid
);
void koto_track_set_cd(
@@ -314,16 +364,31 @@ void koto_track_set_cd(
guint cd
);
+void koto_track_set_description(
+ KotoTrack * self,
+ const gchar * description
+);
+
void koto_track_set_duration(
KotoTrack * self,
guint64 duration
);
+void koto_track_set_file_name(
+ KotoTrack * self,
+ gchar * new_file_name
+);
+
void koto_track_set_genres(
KotoTrack * self,
char * genrelist
);
+void koto_track_set_narrator(
+ KotoTrack * self,
+ const gchar * narrator
+);
+
void koto_track_set_parsed_name(
KotoTrack * self,
gchar * new_parsed_name
@@ -335,6 +400,11 @@ void koto_track_set_path(
gchar * fixed_path
);
+void koto_track_set_playback_position(
+ KotoTrack * self,
+ guint64 position
+);
+
void koto_track_set_position(
KotoTrack * self,
guint64 pos
@@ -345,6 +415,11 @@ void koto_track_set_preparsed_genres(
gchar * genrelist
);
+void koto_track_set_year(
+ KotoTrack * self,
+ guint64 year
+);
+
void koto_track_update_metadata(KotoTrack * self);
G_END_DECLS
diff --git a/src/indexer/track-helpers.c b/src/indexer/track-helpers.c
index 5a65599..076212e 100644
--- a/src/indexer/track-helpers.c
+++ b/src/indexer/track-helpers.c
@@ -17,8 +17,8 @@
#include
#include
+#include "../components/track-item.h"
#include "../db/cartographer.h"
-#include "../koto-track-item.h"
#include "../koto-utils.h"
#include "structs.h"
@@ -40,7 +40,40 @@ void koto_track_helpers_init() {
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;
+ return koto_utils_string_is_valid(lookedup_genre) ? lookedup_genre : original_genre;
+}
+
+guint64 koto_track_helpers_get_cd_based_on_file_name(const gchar * file_name) {
+ gchar ** part_split = g_strsplit(file_name, "Part", -1);
+ gchar * part_str = NULL;
+
+ for (guint i = 0; i < g_strv_length(part_split); i++) { // Iterate on the parts
+ gchar * stripped_part = g_strdup(g_strstrip(part_split[i])); // Trim the whitespace around this part
+ gchar ** split = g_regex_split_simple("^([\\d]+)", stripped_part, G_REGEX_JAVASCRIPT_COMPAT, 0);
+
+ g_free(stripped_part); // Free the stripped part
+
+ if (g_strv_length(split) > 1) { // Has positional info at the beginning of the string
+ part_str = g_strdup(split[1]);
+ g_strfreev(split);
+ break;
+ } else {
+ g_strfreev(split);
+ }
+ }
+
+ guint64 cd = 0;
+
+ if (koto_utils_string_is_valid(part_str)) { // Have a valid string for the part
+ cd = g_ascii_strtoull(part_str, NULL, 10);
+ g_free(part_str);
+ }
+
+ if (cd == 0) {
+ cd = 1; // Should be first CD, not 0
+ }
+
+ return cd;
}
gchar * koto_track_helpers_get_name_for_file(
@@ -58,19 +91,19 @@ gchar * koto_track_helpers_get_name_for_file(
taglib_tag_free_strings(); // Free strings
taglib_file_free(t_file); // Free the file
- if (koto_utils_is_string_valid(file_name)) { // File name not set yet
+ if (koto_utils_string_is_valid(file_name)) { // File name not set yet
return file_name;
}
- gchar * name_without_hyphen_surround = koto_utils_replace_string_all(koto_utils_get_filename_without_extension((gchar*) path), " - ", ""); // Remove - surrounded by spaces
+ gchar * name_without_hyphen_surround = koto_utils_string_replace_all(koto_utils_get_filename_without_extension((gchar*) path), " - ", ""); // Remove - surrounded by spaces
- gchar * name_without_hyphen = koto_utils_replace_string_all(name_without_hyphen_surround, "-", ""); // Remove just -
+ gchar * name_without_hyphen = koto_utils_string_replace_all(name_without_hyphen_surround, "-", ""); // Remove just -
g_free(name_without_hyphen_surround);
- file_name = koto_utils_replace_string_all(name_without_hyphen, "_", " "); // Replace underscore with whitespace
+ file_name = koto_utils_string_replace_all(name_without_hyphen, "_", " "); // Replace underscore with whitespace
- if (koto_utils_is_string_valid(optional_artist_name)) { // Was provided an optional artist name
- gchar * replaced_artist = koto_utils_replace_string_all(file_name, optional_artist_name, ""); // Remove the artist
+ if (koto_utils_string_is_valid(optional_artist_name)) { // Was provided an optional artist name
+ gchar * replaced_artist = koto_utils_string_replace_all(file_name, optional_artist_name, ""); // Remove the artist
g_free(file_name);
file_name = g_strdup(replaced_artist);
g_free(replaced_artist);
@@ -87,21 +120,67 @@ gchar * koto_track_helpers_get_name_for_file(
}
guint64 koto_track_helpers_get_position_based_on_file_name(const gchar * file_name) {
- guint64 position = 0; // Default to position 0
- gchar ** split = g_regex_split_simple("^([\\d]+)", file_name, G_REGEX_JAVASCRIPT_COMPAT, 0);
+ GRegex * num_pat = g_regex_new("^([\\d]+)", G_REGEX_JAVASCRIPT_COMPAT, 0, NULL);
+ gchar ** split = g_regex_split(num_pat, file_name, 0);
if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file
- gchar * num = split[1];
+ gchar * num = g_strdup(split[1]);
+ g_strfreev(split);
if ((strcmp(num, "0") != 0) && (strcmp(num, "00") != 0)) { // Is not zero
guint64 potential_pos = g_ascii_strtoull(num, NULL, 10); // Attempt to convert
if (potential_pos != 0) { // Got a legitimate position
- position = potential_pos; // Update position
+ g_free(num_pat); // Free our regex
+ return potential_pos; // Return this position
}
}
}
+ gchar * fn_no_ext = koto_utils_get_filename_without_extension((gchar*) file_name); // Get the filename without the extension
+ split = g_strsplit(fn_no_ext, ".", -1); // Split every time we see .
+
+ guint len_of_extension_split = g_strv_length(split);
+
+ gchar * fn_last_split_on_period = g_strdup(split[len_of_extension_split - 1]);
+ g_free(fn_no_ext);
+
+ g_strfreev(split); // Free our split
+
+ gchar ** whitespace_split = g_strsplit(fn_last_split_on_period, " ", -1); // Split on whitespace
+ g_free(fn_last_split_on_period);
+
+ gchar * last_item = koto_utils_string_replace_all(whitespace_split[g_strv_length(split) - 1], "#", ""); // Get last item, removing any # from it
+ g_strfreev(whitespace_split);
+
+ gchar ** hyphen_split = g_strsplit(last_item, "-", -1); // Split on hyphen
+ g_free(last_item);
+
+ gchar * position_str = NULL;
+ for (guint i = 0; i < g_strv_length(hyphen_split); i++) { // Iterate over each item
+ gchar * pos_str = hyphen_split[i];
+
+ if (!g_regex_match(num_pat, pos_str, 0, NULL)) { // Is not a number
+ continue;
+ }
+
+ position_str = g_strdup(pos_str);
+ break;
+ }
+
+ g_strfreev(hyphen_split);
+
+ guint64 position = 0;
+
+ if (position_str != NULL) { // If we have a string defined
+ if (g_regex_match(num_pat, position_str, 0, NULL)) { // Matches being a number
+ position = g_ascii_strtoull(position_str, NULL, 10); // Attempt to convert
+ }
+
+ g_free(position_str);
+ }
+
+ g_free(num_pat);
return position;
}
diff --git a/src/indexer/track-helpers.h b/src/indexer/track-helpers.h
index 299131d..e918766 100644
--- a/src/indexer/track-helpers.h
+++ b/src/indexer/track-helpers.h
@@ -19,6 +19,8 @@
void koto_track_helpers_init();
+guint64 koto_track_helpers_get_cd_based_on_file_name(const gchar * file_name);
+
gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre);
gchar * koto_track_helpers_get_name_for_file(
diff --git a/src/indexer/track.c b/src/indexer/track.c
index 8e5fb1d..f5eb42b 100644
--- a/src/indexer/track.c
+++ b/src/indexer/track.c
@@ -39,7 +39,11 @@ struct _KotoTrack {
guint cd;
guint64 position;
guint64 duration;
- guint64 * playback_position;
+ gchar * description;
+ gchar * narrator;
+ guint64 playback_position;
+ guint64 year;
+
GList * genres;
gboolean do_initial_index;
@@ -57,6 +61,9 @@ enum {
PROP_CD,
PROP_POSITION,
PROP_DURATION,
+ PROP_DESCRIPTION,
+ PROP_NARRATOR,
+ PROP_YEAR,
PROP_PLAYBACK_POSITION,
PROP_PREPARSED_GENRES,
N_PROPERTIES
@@ -157,6 +164,32 @@ static void koto_track_class_init(KotoTrackClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
+ props[PROP_DESCRIPTION] = g_param_spec_string(
+ "description",
+ "Description of Track, typically show notes for a podcast episode",
+ "Description of Track, typically show notes for a podcast episode",
+ NULL,
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
+ props[PROP_NARRATOR] = g_param_spec_string(
+ "narrator",
+ "Narrator, typically of an Audiobook",
+ "Narrator, typically of an Audiobook",
+ NULL,
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
+ props[PROP_YEAR] = g_param_spec_uint64(
+ "year",
+ "Year",
+ "Year",
+ 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",
@@ -179,10 +212,13 @@ static void koto_track_class_init(KotoTrackClass * c) {
}
static void koto_track_init(KotoTrack * self) {
+ self->description = NULL; // Initialize our description
self->duration = 0; // Initialize our duration
self->genres = NULL; // Initialize our genres list
+ self->narrator = NULL, // Initialize our narrator
self->paths = g_hash_table_new(g_str_hash, g_str_equal); // Create our hash table of paths
self->position = 0; // Initialize our duration
+ self->year = 0; // Initialize our year
}
static void koto_track_get_property(
@@ -209,11 +245,20 @@ static void koto_track_get_property(
case PROP_CD:
g_value_set_uint(val, self->cd);
break;
+ case PROP_DESCRIPTION:
+ g_value_set_string(val, self->description);
+ break;
+ case PROP_NARRATOR:
+ g_value_set_string(val, self->narrator);
+ break;
+ case PROP_YEAR:
+ g_value_set_uint64(val, self->year);
+ break;
case PROP_POSITION:
- g_value_set_uint(val, self->position);
+ g_value_set_uint64(val, self->position);
break;
case PROP_PLAYBACK_POSITION:
- g_value_set_uint(val, GPOINTER_TO_UINT(self->playback_position));
+ g_value_set_uint64(val, koto_track_get_playback_position(self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
@@ -254,11 +299,20 @@ static void koto_track_set_property(
koto_track_set_position(self, g_value_get_uint64(val));
break;
case PROP_PLAYBACK_POSITION:
- self->playback_position = GUINT_TO_POINTER(g_value_get_uint64(val));
+ koto_track_set_playback_position(self, g_value_get_uint64(val));
break;
case PROP_DURATION:
koto_track_set_duration(self, g_value_get_uint64(val));
break;
+ case PROP_DESCRIPTION:
+ koto_track_set_description(self, g_value_get_string(val));
+ break;
+ case PROP_NARRATOR:
+ koto_track_set_narrator(self, g_strdup(g_value_get_string(val)));
+ break;
+ case PROP_YEAR:
+ koto_track_set_year(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;
@@ -273,14 +327,10 @@ void koto_track_commit(KotoTrack * self) {
return;
}
- if (!koto_utils_is_string_valid(self->artist_uuid)) { // No valid required artist UUID
+ if (!koto_utils_string_is_valid(self->artist_uuid)) { // No valid required artist UUID
return;
}
- if (!koto_utils_is_string_valid(self->album_uuid)) { // If we do not have a valid album UUID
- self->album_uuid = g_strdup("");
- }
-
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;";
@@ -292,7 +342,7 @@ void koto_track_commit(KotoTrack * self) {
commit_msg,
self->uuid,
self->artist_uuid,
- self->album_uuid,
+ koto_utils_string_get_valid(self->album_uuid),
g_strescape(self->parsed_name, NULL),
(int) self->cd,
(int) self->position,
@@ -326,6 +376,10 @@ void koto_track_commit(KotoTrack * self) {
}
}
+gchar * koto_track_get_description(KotoTrack * self) {
+ return KOTO_IS_TRACK(self) ? g_strdup(self->description) : NULL;
+}
+
guint koto_track_get_disc_number(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->cd : 1;
}
@@ -347,14 +401,14 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid);
gchar * artist_name = koto_artist_get_name(artist);
- if (koto_utils_is_string_valid(self->album_uuid)) { // Have an album associated
+ if (koto_utils_string_is_valid(self->album_uuid)) { // Have an album associated
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid);
if (KOTO_IS_ALBUM(album)) {
gchar * album_art_path = koto_album_get_art(album);
gchar * album_name = koto_album_get_name(album);
- if (koto_utils_is_string_valid(album_art_path)) { // Valid album art path
+ if (koto_utils_string_is_valid(album_art_path)) { // Valid album art path
album_art_path = g_strconcat("file://", album_art_path, NULL); // Prepend with file://
g_variant_builder_add(builder, "{sv}", "mpris:artUrl", g_variant_new_string(album_art_path));
}
@@ -366,7 +420,7 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new_string(self->uuid));
- if (koto_utils_is_string_valid(artist_name)) { // Valid artist name
+ if (koto_utils_string_is_valid(artist_name)) { // Valid artist name
GVariant * artist_name_variant;
GVariantBuilder * artist_list_builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
g_variant_builder_add(artist_list_builder, "s", artist_name);
@@ -388,11 +442,11 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
}
gchar * koto_track_get_name(KotoTrack * self) {
- if (!KOTO_IS_TRACK(self)) { // Not a track
- return NULL;
- }
+ return KOTO_IS_TRACK(self) ? g_strdup(self->parsed_name) : NULL;
+}
- return g_strdup(self->parsed_name);
+gchar * koto_track_get_narrator(KotoTrack * self) {
+ return KOTO_IS_TRACK(self) ? g_strdup(self->narrator) : NULL;
}
gchar * koto_track_get_path(KotoTrack * self) {
@@ -420,6 +474,10 @@ gchar * koto_track_get_path(KotoTrack * self) {
return path;
}
+guint64 koto_track_get_playback_position(KotoTrack * self) {
+ return KOTO_IS_TRACK(self) ? self->playback_position : 0;
+}
+
guint64 koto_track_get_position(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->position : 0;
}
@@ -433,13 +491,13 @@ gchar * koto_track_get_uniqueish_key(KotoTrack * self) {
gchar * artist_name = koto_artist_get_name(artist); // Get the artist name
- if (koto_utils_is_string_valid(self->album_uuid)) { // If we have an album associated with this track (not necessarily guaranteed)
+ if (koto_utils_string_is_valid(self->album_uuid)) { // If we have an album associated with this track (not necessarily guaranteed)
KotoAlbum * possible_album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid);
if (KOTO_IS_ALBUM(possible_album)) { // Album exists
gchar * album_name = koto_album_get_name(possible_album); // Get the name of the album
- if (koto_utils_is_string_valid(album_name)) {
+ if (koto_utils_string_is_valid(album_name)) {
return g_strdup_printf("%s-%s-%s", artist_name, album_name, self->parsed_name); // Create a key of (ARTIST/WRITER)-(ALBUM/AUDIOBOOK)-(CHAPTER/TRACK)
}
}
@@ -456,6 +514,14 @@ gchar * koto_track_get_uuid(KotoTrack * self) {
return self->uuid; // Do not return a duplicate since otherwise comparison refs fail due to pointer positions being different
}
+guint64 koto_track_get_year(KotoTrack * self) {
+ if (!KOTO_IS_TRACK(self)) {
+ return 0;
+ }
+
+ return self->year;
+}
+
void koto_track_remove_from_playlist(
KotoTrack * self,
gchar * playlist_uuid
@@ -487,7 +553,7 @@ void koto_track_set_album_uuid(
gchar * uuid = g_strdup(album_uuid);
- if (!koto_utils_is_string_valid(uuid)) { // If this is not a valid string
+ if (!koto_utils_string_is_valid(uuid)) { // If this is not a valid string
return;
}
@@ -497,19 +563,17 @@ void koto_track_set_album_uuid(
void koto_track_save_to_playlist(
KotoTrack * self,
- gchar * playlist_uuid,
- gint current
+ gchar * playlist_uuid
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
gchar * commit_op = g_strdup_printf(
- "INSERT INTO playlist_tracks(playlist_id, track_id, current)"
- "VALUES('%s', '%s', quote(\"%d\"))",
+ "INSERT INTO playlist_tracks(playlist_id, track_id)"
+ "VALUES('%s', '%s')",
playlist_uuid,
- self->uuid,
- current
+ self->uuid
);
new_transaction(commit_op, "Failed to save track to playlist", FALSE);
@@ -531,6 +595,30 @@ void koto_track_set_cd(
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CD]);
}
+void koto_track_set_description(
+ KotoTrack * self,
+ const gchar * description
+) {
+ if (!KOTO_IS_TRACK(self)) {
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(description)) {
+ return;
+ }
+
+ if (g_strcmp0(self->description, description) == 0) { // Same description
+ return;
+ }
+
+ if (koto_utils_string_is_valid(self->description)) {
+ g_free(self->description); // Free the existing narrator
+ }
+
+ self->description = g_strdup(description); // Duplicate our description
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DESCRIPTION]);
+}
+
void koto_track_set_duration(
KotoTrack * self,
guint64 duration
@@ -544,6 +632,7 @@ void koto_track_set_duration(
}
self->duration = duration;
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DURATION]);
}
void koto_track_set_genres(
@@ -554,7 +643,7 @@ void koto_track_set_genres(
return;
}
- if (!koto_utils_is_string_valid((gchar*) genrelist)) { // If it is an empty string
+ if (!koto_utils_string_is_valid((gchar*) genrelist)) { // If it is an empty string
return;
}
@@ -570,7 +659,7 @@ void koto_track_set_genres(
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, " ", "-");
+ gchar * lowercased_hyphenated_genre = koto_utils_string_replace_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
@@ -585,6 +674,30 @@ void koto_track_set_genres(
g_strfreev(genres); // Free the list of genres locally
}
+void koto_track_set_narrator(
+ KotoTrack * self,
+ const gchar * narrator
+) {
+ if (!KOTO_IS_TRACK(self)) {
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(narrator)) {
+ return;
+ }
+
+ if (g_strcmp0(self->narrator, narrator) == 0) { // Same narrator
+ return;
+ }
+
+ if (koto_utils_string_is_valid(self->narrator)) {
+ g_free(self->narrator); // Free the existing narrator
+ }
+
+ self->narrator = g_strdup(narrator); // Duplicate our narrator
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NARRATOR]);
+}
+
void koto_track_set_parsed_name(
KotoTrack * self,
gchar * new_parsed_name
@@ -593,11 +706,11 @@ void koto_track_set_parsed_name(
return;
}
- if (!koto_utils_is_string_valid(new_parsed_name)) {
+ if (!koto_utils_string_is_valid(new_parsed_name)) {
return;
}
- gboolean have_existing_name = koto_utils_is_string_valid(self->parsed_name);
+ gboolean have_existing_name = koto_utils_string_is_valid(self->parsed_name);
if (have_existing_name && (strcmp(self->parsed_name, new_parsed_name) == 0)) { // Have existing name that matches one provided
return; // Don't do anything
@@ -620,7 +733,7 @@ void koto_track_set_path(
return;
}
- if (!koto_utils_is_string_valid(fixed_path)) { // Not a valid path
+ if (!koto_utils_string_is_valid(fixed_path)) { // Not a valid path
return;
}
@@ -635,10 +748,25 @@ void koto_track_set_path(
}
}
+void koto_track_set_playback_position(
+ KotoTrack * self,
+ guint64 position
+) {
+ if (!KOTO_IS_TRACK(self)) { // Not a track
+ return;
+ }
+
+ self->playback_position = position;
+}
+
void koto_track_set_position(
KotoTrack * self,
guint64 pos
) {
+ if (!KOTO_IS_TRACK(self)) { // Not a track
+ return;
+ }
+
if (pos == 0) { // No position change really
return;
}
@@ -647,29 +775,48 @@ void koto_track_set_position(
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_POSITION]);
}
+void koto_track_set_year(
+ KotoTrack * self,
+ guint64 year
+) {
+ if (!KOTO_IS_TRACK(self)) {
+ return;
+ }
+
+ self->year = year;
+ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_YEAR]);
+}
+
void koto_track_update_metadata(KotoTrack * self) {
if (!KOTO_IS_TRACK(self)) { // Not a track
return;
}
gchar * optimal_track_path = koto_track_get_path(self); // Check all the libraries associated with this track, based on priority, return a built path using lib path + relative file path
+
+ if (!koto_utils_string_is_valid(optimal_track_path)) { // Not a valid string
+ return;
+ }
+
TagLib_File * t_file = taglib_file_new(optimal_track_path); // Get a taglib file for this file
- g_free(optimal_track_path);
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
-
+ koto_track_set_year(self, (guint64) taglib_tag_year(tag)); // Get the track year and convert it to guint64
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
+ }
+
+ if (self->position == 0) { // Failed to get tag info or got the tag info but position is zero
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
}
taglib_tag_free_strings(); // Free strings
taglib_file_free(t_file); // Free the file
+ g_free(optimal_track_path);
}
void koto_track_set_preparsed_genres(
@@ -680,7 +827,7 @@ void koto_track_set_preparsed_genres(
return;
}
- if (!koto_utils_is_string_valid(genrelist)) { // If it is an empty string
+ if (!koto_utils_string_is_valid(genrelist)) { // If it is an empty string
return;
}
diff --git a/src/koto-dialog-container.c b/src/koto-dialog-container.c
index 41049d0..fb7c2ac 100644
--- a/src/koto-dialog-container.c
+++ b/src/koto-dialog-container.c
@@ -17,7 +17,7 @@
#include
#include
-#include "koto-button.h"
+#include "components/button.h"
#include "koto-dialog-container.h"
struct _KotoDialogContainer {
diff --git a/src/koto-expander.c b/src/koto-expander.c
index 14fb48a..f95af87 100644
--- a/src/koto-expander.c
+++ b/src/koto-expander.c
@@ -17,8 +17,8 @@
#include
#include
+#include "components/button.h"
#include "config/config.h"
-#include "koto-button.h"
#include "koto-expander.h"
#include "koto-utils.h"
@@ -212,7 +212,7 @@ void koto_expander_set_icon_name(
return;
}
- if (!koto_utils_is_string_valid(icon)) { // Not a valid string
+ if (!koto_utils_string_is_valid(icon)) { // Not a valid string
return;
}
diff --git a/src/koto-expander.h b/src/koto-expander.h
index 48f6b40..558ebcf 100644
--- a/src/koto-expander.h
+++ b/src/koto-expander.h
@@ -18,7 +18,7 @@
#pragma once
#include
-#include "koto-button.h"
+#include "components/button.h"
G_BEGIN_DECLS
diff --git a/src/koto-nav.c b/src/koto-nav.c
index 44f9b63..7c67785 100644
--- a/src/koto-nav.c
+++ b/src/koto-nav.c
@@ -16,11 +16,11 @@
*/
#include
+#include "components/button.h"
+#include "config/config.h"
#include "db/cartographer.h"
#include "indexer/structs.h"
#include "playlist/playlist.h"
-#include "config/config.h"
-#include "koto-button.h"
#include "koto-expander.h"
#include "koto-nav.h"
#include "koto-utils.h"
@@ -112,12 +112,17 @@ void koto_nav_create_audiobooks_section(KotoNav * self) {
koto_expander_set_content(a_expander, new_content);
self->audiobooks_local = koto_button_new_plain("Library");
+ koto_button_set_data(self->audiobooks_local, &"audiobooks.library");
+
self->audiobooks_audible = koto_button_new_plain("Audible");
self->audiobooks_librivox = koto_button_new_plain("LibriVox");
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_local));
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_audible));
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_librivox));
+
+ koto_button_add_click_handler(self->audiobooks_local, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), self->audiobooks_local);
+
}
void koto_nav_create_music_section(KotoNav * self) {
@@ -129,13 +134,15 @@ void koto_nav_create_music_section(KotoNav * self) {
GtkWidget * new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
self->music_local = koto_button_new_plain("Library");
+ koto_button_set_data(self->music_local, &"music.library");
+
self->music_radio = koto_button_new_plain("Radio");
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->music_local));
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->music_radio));
koto_expander_set_content(m_expander, new_content);
- koto_button_add_click_handler(self->music_local, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_nav_handle_local_music_click), NULL);
+ koto_button_add_click_handler(self->music_local, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), self->music_local);
}
void koto_nav_create_playlist_section(KotoNav * self) {
@@ -187,37 +194,6 @@ void koto_nav_handle_playlist_add_click(
koto_window_show_dialog(main_window, "create-modify-playlist");
}
-void koto_nav_handle_local_music_click(
- GtkGestureClick * gesture,
- int n_press,
- double x,
- double y,
- gpointer user_data
-) {
- (void) gesture;
- (void) n_press;
- (void) x;
- (void) y;
- (void) user_data;
- koto_window_go_to_page(main_window, "music.local"); // Go to the playlist page
-}
-
-void koto_nav_handle_playlist_button_click(
- GtkGestureClick * gesture,
- int n_press,
- double x,
- double y,
- gpointer user_data
-) {
- (void) gesture;
- (void) n_press;
- (void) x;
- (void) y;
- gchar * playlist_uuid = user_data;
-
- koto_window_go_to_page(main_window, playlist_uuid); // Go to the playlist page
-}
-
void koto_nav_handle_playlist_added(
KotoCartographer * carto,
KotoPlaylist * playlist,
@@ -228,6 +204,10 @@ void koto_nav_handle_playlist_added(
return;
}
+ if (koto_playlist_get_is_hidden(playlist)) { // Should be hidden from nav
+ return;
+ }
+
KotoNav * self = user_data;
if (!KOTO_IS_NAV(self)) {
@@ -245,25 +225,28 @@ void koto_nav_handle_playlist_added(
gchar * playlist_art_path = koto_playlist_get_artwork(playlist); // Get any file path for it
KotoButton * playlist_button = NULL;
- if (koto_utils_is_string_valid(playlist_art_path)) { // Have a file associated
+ if (koto_utils_string_is_valid(playlist_art_path)) { // Have a file associated
playlist_button = koto_button_new_with_file(playlist_name, playlist_art_path, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
} else { // No file associated
playlist_button = koto_button_new_with_icon(playlist_name, "audio-x-generic-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
}
- if (KOTO_IS_BUTTON(playlist_button)) {
- g_hash_table_insert(self->playlist_buttons, playlist_uuid, playlist_button); // Add the button
+ if (!KOTO_IS_BUTTON(playlist_button)) { // Failed to create the playlist button
+ return;
+ }
- // TODO: Make this a ListBox and sort the playlists alphabetically
- GtkBox * playlist_expander_content = GTK_BOX(koto_expander_get_content(self->playlists_expander));
+ koto_button_set_data(playlist_button, playlist_uuid); // Set our data to the playlist UUID string
+ g_hash_table_insert(self->playlist_buttons, playlist_uuid, playlist_button); // Add the button
- if (GTK_IS_BOX(playlist_expander_content)) {
- gtk_box_append(playlist_expander_content, GTK_WIDGET(playlist_button));
+ // TODO: Make this a ListBox and sort the playlists alphabetically
+ GtkBox * playlist_expander_content = GTK_BOX(koto_expander_get_content(self->playlists_expander));
- koto_button_add_click_handler(playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_nav_handle_playlist_button_click), playlist_uuid);
- koto_window_handle_playlist_added(koto_maps, playlist, main_window); // TODO: MOVE THIS
- g_signal_connect(playlist, "modified", G_CALLBACK(koto_nav_handle_playlist_modified), self);
- }
+ if (GTK_IS_BOX(playlist_expander_content)) {
+ gtk_box_append(playlist_expander_content, GTK_WIDGET(playlist_button));
+
+ koto_button_add_click_handler(playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), playlist_button);
+ koto_window_handle_playlist_added(koto_maps, playlist, main_window); // TODO: MOVE THIS
+ g_signal_connect(playlist, "modified", G_CALLBACK(koto_nav_handle_playlist_modified), self);
}
}
@@ -291,13 +274,13 @@ void koto_nav_handle_playlist_modified(
gchar * artwork = koto_playlist_get_artwork(playlist); // Get the artwork
- if (koto_utils_is_string_valid(artwork)) { // Have valid artwork
+ if (koto_utils_string_is_valid(artwork)) { // Have valid artwork
koto_button_set_file_path(playlist_button, artwork); // Update the artwork path
}
gchar * name = koto_playlist_get_name(playlist); // Get the name
- if (koto_utils_is_string_valid(name)) { // Have valid name
+ if (koto_utils_string_is_valid(name)) { // Have valid name
koto_button_set_text(playlist_button, name); // Update the button text
}
}
diff --git a/src/koto-nav.h b/src/koto-nav.h
index d69d7bf..6583eb4 100644
--- a/src/koto-nav.h
+++ b/src/koto-nav.h
@@ -60,14 +60,6 @@ void koto_nav_handle_playlist_removed(
gpointer user_data
);
-void koto_nav_handle_local_music_click(
- GtkGestureClick * gesture,
- int n_press,
- double x,
- double y,
- gpointer user_data
-);
-
GtkWidget * koto_nav_get_nav(KotoNav * self);
G_END_DECLS
diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c
index 6625ef9..7070e30 100644
--- a/src/koto-playerbar.c
+++ b/src/koto-playerbar.c
@@ -17,13 +17,13 @@
#include
#include
+#include "components/button.h"
#include "config/config.h"
#include "db/cartographer.h"
#include "playlist/add-remove-track-popover.h"
#include "playlist/current.h"
#include "playlist/playlist.h"
#include "playback/engine.h"
-#include "koto-button.h"
#include "config/config.h"
#include "koto-playerbar.h"
#include "koto-utils.h"
@@ -64,6 +64,9 @@ struct _KotoPlayerBar {
GtkWidget * playback_album;
GtkWidget * playback_artist;
+ /* Misc Widgets */
+ GtkWidget * playback_position_label;
+
gint64 last_recorded_duration;
gboolean is_progressbar_seeking;
@@ -177,98 +180,104 @@ void koto_playerbar_apply_configuration_state(
gtk_scale_button_set_value(GTK_SCALE_BUTTON(self->volume_button), config_last_used_volume * 100);
}
-void koto_playerbar_create_playback_details(KotoPlayerBar * bar) {
- bar->playback_details_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+void koto_playerbar_create_playback_details(KotoPlayerBar * self) {
+ self->playback_details_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
GtkIconTheme * default_icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); // Get the icon theme for this display
if (default_icon_theme != NULL) {
- gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(bar->main));
+ gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self->main));
GtkIconPaintable * audio_paintable = gtk_icon_theme_lookup_icon(default_icon_theme, "audio-x-generic-symbolic", NULL, 96, scale_factor, GTK_TEXT_DIR_NONE, GTK_ICON_LOOKUP_PRELOAD);
if (GTK_IS_ICON_PAINTABLE(audio_paintable)) {
- if (GTK_IS_IMAGE(bar->artwork)) {
- gtk_image_set_from_paintable(GTK_IMAGE(bar->artwork), GDK_PAINTABLE(audio_paintable));
+ if (GTK_IS_IMAGE(self->artwork)) {
+ gtk_image_set_from_paintable(GTK_IMAGE(self->artwork), GDK_PAINTABLE(audio_paintable));
} else { // Not an image
- bar->artwork = gtk_image_new_from_paintable(GDK_PAINTABLE(audio_paintable));
- gtk_widget_add_css_class(bar->artwork, "circular");
- gtk_widget_set_size_request(bar->artwork, 96, 96);
- gtk_box_append(GTK_BOX(bar->playback_section), bar->artwork);
+ self->artwork = gtk_image_new_from_paintable(GDK_PAINTABLE(audio_paintable));
+ gtk_widget_add_css_class(self->artwork, "circular");
+ gtk_widget_set_size_request(self->artwork, 96, 96);
+ gtk_box_append(GTK_BOX(self->playback_section), self->artwork);
}
}
}
- bar->playback_title = gtk_label_new("Title");
- gtk_label_set_xalign(GTK_LABEL(bar->playback_title), 0);
+ self->playback_title = gtk_label_new("Title");
+ gtk_label_set_xalign(GTK_LABEL(self->playback_title), 0);
- bar->playback_album = gtk_label_new("Album");
- gtk_label_set_xalign(GTK_LABEL(bar->playback_album), 0);
+ self->playback_album = gtk_label_new("Album");
+ gtk_label_set_xalign(GTK_LABEL(self->playback_album), 0);
- bar->playback_artist = gtk_label_new("Artist");
- gtk_label_set_xalign(GTK_LABEL(bar->playback_artist), 0);
+ self->playback_artist = gtk_label_new("Artist");
+ gtk_label_set_xalign(GTK_LABEL(self->playback_artist), 0);
- gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_title));
- gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_album));
- gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_artist));
+ gtk_box_append(GTK_BOX(self->playback_details_section), GTK_WIDGET(self->playback_title));
+ gtk_box_append(GTK_BOX(self->playback_details_section), GTK_WIDGET(self->playback_album));
+ gtk_box_append(GTK_BOX(self->playback_details_section), GTK_WIDGET(self->playback_artist));
- gtk_box_append(GTK_BOX(bar->playback_section), GTK_WIDGET(bar->playback_details_section));
+ gtk_box_append(GTK_BOX(self->playback_section), GTK_WIDGET(self->playback_details_section));
}
-void koto_playerbar_create_primary_controls(KotoPlayerBar * bar) {
- bar->back_button = koto_button_new_with_icon("", "media-skip-backward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
- bar->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_LARGE); // TODO: Have this take in a state and switch to a different icon if necessary
- bar->forward_button = koto_button_new_with_icon("", "media-skip-forward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
+void koto_playerbar_create_primary_controls(KotoPlayerBar * self) {
+ self->back_button = koto_button_new_with_icon("", "media-skip-backward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
+ self->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_LARGE); // TODO: Have this take in a state and switch to a different icon if necessary
+ self->forward_button = koto_button_new_with_icon("", "media-skip-forward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
- if (KOTO_IS_BUTTON(bar->back_button)) {
- gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->back_button));
- koto_button_add_click_handler(bar->back_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_backwards), bar);
+ if (KOTO_IS_BUTTON(self->back_button)) {
+ gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->back_button));
+ koto_button_add_click_handler(self->back_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_backwards), self);
}
- if (KOTO_IS_BUTTON(bar->play_pause_button)) {
- gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->play_pause_button));
- koto_button_add_click_handler(bar->play_pause_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_play_pause), bar);
+ if (KOTO_IS_BUTTON(self->play_pause_button)) {
+ gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->play_pause_button));
+ koto_button_add_click_handler(self->play_pause_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_play_pause), self);
}
- if (KOTO_IS_BUTTON(bar->forward_button)) {
- gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->forward_button));
- koto_button_add_click_handler(bar->forward_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_forwards), bar);
+ if (KOTO_IS_BUTTON(self->forward_button)) {
+ gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->forward_button));
+ koto_button_add_click_handler(self->forward_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_forwards), self);
}
}
-void koto_playerbar_create_secondary_controls(KotoPlayerBar * bar) {
- bar->repeat_button = koto_button_new_with_icon("", "media-playlist-repeat-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
- bar->shuffle_button = koto_button_new_with_icon("", "media-playlist-shuffle-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
- bar->playlist_button = koto_button_new_with_icon("", "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
- bar->eq_button = koto_button_new_with_icon("", "multimedia-equalizer-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
+void koto_playerbar_create_secondary_controls(KotoPlayerBar * self) {
+ self->playback_position_label = gtk_label_new(NULL); // Create our label to communicate position and duration of a track
- bar->volume_button = gtk_volume_button_new(); // Have this take in a state and switch to a different icon if necessary
+ self->repeat_button = koto_button_new_with_icon("", "media-playlist-repeat-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
+ self->shuffle_button = koto_button_new_with_icon("", "media-playlist-shuffle-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
+ self->playlist_button = koto_button_new_with_icon("", "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
+ self->eq_button = koto_button_new_with_icon("", "multimedia-equalizer-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
- if (KOTO_IS_BUTTON(bar->repeat_button)) {
- gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->repeat_button));
- koto_button_add_click_handler(bar->repeat_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_track_repeat), bar);
+ self->volume_button = gtk_volume_button_new(); // Have this take in a state and switch to a different icon if necessary
+
+ if (GTK_IS_LABEL(self->playback_position_label)) {
+ gtk_box_append(GTK_BOX(self->secondary_controls_section), self->playback_position_label);
}
- if (KOTO_IS_BUTTON(bar->shuffle_button)) {
- gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->shuffle_button));
- koto_button_add_click_handler(bar->shuffle_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_playlist_shuffle), bar);
+ if (KOTO_IS_BUTTON(self->repeat_button)) {
+ gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->repeat_button));
+ koto_button_add_click_handler(self->repeat_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_track_repeat), self);
}
- if (KOTO_IS_BUTTON(bar->playlist_button)) {
- gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->playlist_button));
- koto_button_add_click_handler(bar->playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_handle_playlist_button_clicked), bar);
+ if (KOTO_IS_BUTTON(self->shuffle_button)) {
+ gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->shuffle_button));
+ koto_button_add_click_handler(self->shuffle_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_playlist_shuffle), self);
}
- if (KOTO_IS_BUTTON(bar->eq_button)) {
- gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->eq_button));
+ if (KOTO_IS_BUTTON(self->playlist_button)) {
+ gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->playlist_button));
+ koto_button_add_click_handler(self->playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_handle_playlist_button_clicked), self);
}
- if (GTK_IS_VOLUME_BUTTON(bar->volume_button)) {
+ if (KOTO_IS_BUTTON(self->eq_button)) {
+ gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->eq_button));
+ }
+
+ if (GTK_IS_VOLUME_BUTTON(self->volume_button)) {
GtkAdjustment * granular_volume_change = gtk_adjustment_new(50.0, 0, 100.0, 1.0, 1.0, 1.0);
- g_object_set(bar->volume_button, "use-symbolic", TRUE, NULL);
- gtk_scale_button_set_adjustment(GTK_SCALE_BUTTON(bar->volume_button), granular_volume_change); // Set our adjustment
- gtk_box_append(GTK_BOX(bar->secondary_controls_section), bar->volume_button);
+ g_object_set(self->volume_button, "use-symbolic", TRUE, NULL);
+ gtk_scale_button_set_adjustment(GTK_SCALE_BUTTON(self->volume_button), granular_volume_change); // Set our adjustment
+ gtk_box_append(GTK_BOX(self->secondary_controls_section), self->volume_button);
- g_signal_connect(GTK_SCALE_BUTTON(bar->volume_button), "value-changed", G_CALLBACK(koto_playerbar_handle_volume_button_change), bar);
+ g_signal_connect(GTK_SCALE_BUTTON(self->volume_button), "value-changed", G_CALLBACK(koto_playerbar_handle_volume_button_change), self);
}
}
@@ -312,13 +321,13 @@ void koto_playerbar_handle_is_playing(
return;
}
- KotoPlayerBar * bar = user_data;
+ KotoPlayerBar * self = user_data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- koto_button_show_image(bar->play_pause_button, TRUE); // Set to TRUE to show pause as the next action
+ koto_button_show_image(self->play_pause_button, TRUE); // Set to TRUE to show pause as the next action
}
void koto_playerbar_handle_is_paused(
@@ -329,13 +338,13 @@ void koto_playerbar_handle_is_paused(
return;
}
- KotoPlayerBar * bar = user_data;
+ KotoPlayerBar * self = user_data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- koto_button_show_image(bar->play_pause_button, FALSE); // Set to FALSE to show play as the next action
+ koto_button_show_image(self->play_pause_button, FALSE); // Set to FALSE to show play as the next action
}
void koto_playerbar_handle_playlist_button_clicked(
@@ -366,13 +375,13 @@ void koto_playerbar_handle_progressbar_gesture_begin(
) {
(void) gesture;
(void) seq;
- KotoPlayerBar * bar = data;
+ KotoPlayerBar * self = data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- bar->is_progressbar_seeking = TRUE;
+ self->is_progressbar_seeking = TRUE;
}
void koto_playerbar_handle_progressbar_gesture_end(
@@ -382,12 +391,12 @@ void koto_playerbar_handle_progressbar_gesture_end(
) {
(void) gesture;
(void) seq;
- KotoPlayerBar * bar = data;
+ KotoPlayerBar * self = data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- bar->is_progressbar_seeking = FALSE;
+ self->is_progressbar_seeking = FALSE;
}
void koto_playerbar_handle_progressbar_pressed(
@@ -401,26 +410,26 @@ void koto_playerbar_handle_progressbar_pressed(
(void) n_press;
(void) x;
(void) y;
- KotoPlayerBar * bar = data;
+ KotoPlayerBar * self = data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- bar->is_progressbar_seeking = TRUE;
+ self->is_progressbar_seeking = TRUE;
}
void koto_playerbar_handle_progressbar_value_changed(
GtkRange * progress_bar,
gpointer data
) {
- KotoPlayerBar * bar = data;
+ KotoPlayerBar * self = data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- if (!bar->is_progressbar_seeking) {
+ if (!self->is_progressbar_seeking) {
return;
}
@@ -437,13 +446,13 @@ void koto_playerbar_handle_tick_duration(
return;
}
- KotoPlayerBar * bar = user_data;
+ KotoPlayerBar * self = user_data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- koto_playerbar_set_progressbar_duration(bar, koto_playback_engine_get_duration(engine));
+ koto_playerbar_set_progressbar_duration(self, koto_playback_engine_get_duration(engine));
}
void koto_playerbar_handle_tick_track(
@@ -454,15 +463,27 @@ void koto_playerbar_handle_tick_track(
return;
}
- KotoPlayerBar * bar = user_data;
+ KotoPlayerBar * self = user_data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- if (!bar->is_progressbar_seeking) {
- koto_playerbar_set_progressbar_value(bar, koto_playback_engine_get_progress(engine));
+ if (self->is_progressbar_seeking) { // Currently seeking
+ return;
}
+
+ KotoTrack * current_track = koto_playback_engine_get_current_track(engine);
+
+ gtk_label_set_text(
+ GTK_LABEL(self->playback_position_label),
+ g_strdup_printf(
+ "%s / %s",
+ koto_utils_seconds_to_time_format(koto_track_get_playback_position(current_track)),
+ koto_utils_seconds_to_time_format(koto_track_get_duration(current_track))
+ ));
+
+ koto_playerbar_set_progressbar_value(self, koto_playback_engine_get_progress(engine));
}
void koto_playerbar_handle_track_repeat(
@@ -473,16 +494,16 @@ void koto_playerbar_handle_track_repeat(
return;
}
- KotoPlayerBar * bar = user_data;
+ KotoPlayerBar * self = user_data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
if (koto_playback_engine_get_track_repeat(engine)) { // Is repeating
- gtk_widget_add_css_class(GTK_WIDGET(bar->repeat_button), "active"); // Add active CSS class
+ gtk_widget_add_css_class(GTK_WIDGET(self->repeat_button), "active"); // Add active CSS class
} else { // Is not repeating
- gtk_widget_remove_css_class(GTK_WIDGET(bar->repeat_button), "active"); // Remove active CSS class
+ gtk_widget_remove_css_class(GTK_WIDGET(self->repeat_button), "active"); // Remove active CSS class
}
}
@@ -494,16 +515,16 @@ void koto_playerbar_handle_track_shuffle(
return;
}
- KotoPlayerBar * bar = user_data;
+ KotoPlayerBar * self = user_data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
if (koto_playback_engine_get_track_shuffle(engine)) { // Is repeating
- gtk_widget_add_css_class(GTK_WIDGET(bar->shuffle_button), "active"); // Add active CSS class
+ gtk_widget_add_css_class(GTK_WIDGET(self->shuffle_button), "active"); // Add active CSS class
} else { // Is not repeating
- gtk_widget_remove_css_class(GTK_WIDGET(bar->shuffle_button), "active"); // Remove active CSS class
+ gtk_widget_remove_css_class(GTK_WIDGET(self->shuffle_button), "active"); // Remove active CSS class
}
}
@@ -517,28 +538,28 @@ void koto_playerbar_handle_volume_button_change(
koto_playback_engine_set_volume(playback_engine, (double) value / 100);
}
-void koto_playerbar_reset_progressbar(KotoPlayerBar * bar) {
- if (!KOTO_IS_PLAYERBAR(bar)) {
+void koto_playerbar_reset_progressbar(KotoPlayerBar * self) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- if (!GTK_IS_RANGE(bar->progress_bar)) {
+ if (!GTK_IS_RANGE(self->progress_bar)) {
return;
}
- gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, 0); // Reset range
- gtk_range_set_value(GTK_RANGE(bar->progress_bar), 0); // Set value to 0
+ gtk_range_set_range(GTK_RANGE(self->progress_bar), 0, 0); // Reset range
+ gtk_range_set_value(GTK_RANGE(self->progress_bar), 0); // Set value to 0
}
void koto_playerbar_set_progressbar_duration(
- KotoPlayerBar * bar,
+ KotoPlayerBar * self,
gint64 duration
) {
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- if (!GTK_IS_RANGE(bar->progress_bar)) {
+ if (!GTK_IS_RANGE(self->progress_bar)) {
return;
}
@@ -546,25 +567,25 @@ void koto_playerbar_set_progressbar_duration(
return;
}
- if (duration != bar->last_recorded_duration) { // Duration is different than what we recorded
- bar->last_recorded_duration = duration;
- gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, bar->last_recorded_duration);
+ if (duration != self->last_recorded_duration) { // Duration is different than what we recorded
+ self->last_recorded_duration = duration;
+ gtk_range_set_range(GTK_RANGE(self->progress_bar), 0, self->last_recorded_duration);
}
}
void koto_playerbar_set_progressbar_value(
- KotoPlayerBar * bar,
+ KotoPlayerBar * self,
double progress
) {
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
- if (!GTK_IS_RANGE(bar->progress_bar)) {
+ if (!GTK_IS_RANGE(self->progress_bar)) {
return;
}
- gtk_range_set_value(GTK_RANGE(bar->progress_bar), progress);
+ gtk_range_set_value(GTK_RANGE(self->progress_bar), progress);
}
void koto_playerbar_toggle_play_pause(
@@ -622,9 +643,9 @@ void koto_playerbar_update_track_info(
return;
}
- KotoPlayerBar * bar = user_data;
+ KotoPlayerBar * self = user_data;
- if (!KOTO_IS_PLAYERBAR(bar)) {
+ if (!KOTO_IS_PLAYERBAR(self)) {
return;
}
@@ -644,8 +665,8 @@ void koto_playerbar_update_track_info(
g_free(artist_uuid);
- if (koto_utils_is_string_valid(track_name)) { // Have a track name
- gtk_label_set_text(GTK_LABEL(bar->playback_title), track_name); // Set the label
+ if (koto_utils_string_is_valid(track_name)) { // Have a track name
+ gtk_label_set_text(GTK_LABEL(self->playback_title), track_name); // Set the label
}
if (KOTO_IS_ARTIST(artist)) {
@@ -653,16 +674,17 @@ void koto_playerbar_update_track_info(
g_object_get(artist, "name", &artist_name, NULL);
if ((artist_name != NULL) && (strcmp(artist_name, "") != 0)) { // Have an artist name
- gtk_label_set_text(GTK_LABEL(bar->playback_artist), artist_name);
- gtk_widget_show(bar->playback_artist);
+ gtk_label_set_text(GTK_LABEL(self->playback_artist), artist_name);
+ gtk_widget_show(self->playback_artist);
} else { // Don't have an artist name somehow
- gtk_widget_hide(bar->playback_artist);
+ gtk_widget_hide(self->playback_artist);
}
}
gchar * art_path = NULL;
- if (koto_utils_is_string_valid(album_uuid)) { // Have a valid album UUID
+ gboolean set_album_label = FALSE;
+ if (koto_utils_string_is_valid(album_uuid)) { // Have a valid album UUID
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
g_free(album_uuid);
@@ -671,21 +693,22 @@ void koto_playerbar_update_track_info(
g_object_get(album, "name", &album_name, "art-path", &art_path, NULL); // Get album name and art path
if ((album_name != NULL) && (strcmp(album_name, "") != 0)) { // Have an album name
- gtk_label_set_text(GTK_LABEL(bar->playback_album), album_name);
- gtk_widget_show(bar->playback_album);
- } else {
- gtk_widget_hide(bar->playback_album);
+ gtk_label_set_text(GTK_LABEL(self->playback_album), album_name);
+ set_album_label = TRUE;
+ gtk_widget_show(self->playback_album);
}
}
}
+ (set_album_label) ? gtk_widget_show(self->playback_album) : gtk_widget_hide(self->playback_album);
+
if ((art_path != NULL) && g_path_is_absolute(art_path)) { // Have an album artist path
- gtk_image_set_from_file(GTK_IMAGE(bar->artwork), art_path); // Update the art
+ gtk_image_set_from_file(GTK_IMAGE(self->artwork), art_path); // Update the art
} else {
- gtk_image_set_from_icon_name(GTK_IMAGE(bar->artwork), "audio-x-generic-symbolic"); // Use generic instead
+ gtk_image_set_from_icon_name(GTK_IMAGE(self->artwork), "audio-x-generic-symbolic"); // Use generic instead
}
}
-GtkWidget * koto_playerbar_get_main(KotoPlayerBar * bar) {
- return bar->main;
+GtkWidget * koto_playerbar_get_main(KotoPlayerBar * self) {
+ return self->main;
}
diff --git a/src/koto-playerbar.h b/src/koto-playerbar.h
index a542502..a3b3d2e 100644
--- a/src/koto-playerbar.h
+++ b/src/koto-playerbar.h
@@ -27,7 +27,7 @@ G_DECLARE_FINAL_TYPE(KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GObject)
#define KOTO_IS_PLAYERBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYERBAR))
KotoPlayerBar * koto_playerbar_new(void);
-GtkWidget * koto_playerbar_get_main(KotoPlayerBar * bar);
+GtkWidget * koto_playerbar_get_main(KotoPlayerBar * self);
void koto_playerbar_apply_configuration_state(
KotoConfig * config,
@@ -35,11 +35,11 @@ void koto_playerbar_apply_configuration_state(
KotoPlayerBar * self
);
-void koto_playerbar_create_playback_details(KotoPlayerBar * bar);
+void koto_playerbar_create_playback_details(KotoPlayerBar * self);
-void koto_playerbar_create_primary_controls(KotoPlayerBar * bar);
+void koto_playerbar_create_primary_controls(KotoPlayerBar * self);
-void koto_playerbar_create_secondary_controls(KotoPlayerBar * bar);
+void koto_playerbar_create_secondary_controls(KotoPlayerBar * self);
void koto_playerbar_go_backwards(
GtkGestureClick * gesture,
@@ -131,15 +131,15 @@ void koto_playerbar_handle_volume_button_change(
gpointer user_data
);
-void koto_playerbar_reset_progressbar(KotoPlayerBar * bar);
+void koto_playerbar_reset_progressbar(KotoPlayerBar * self);
void koto_playerbar_set_progressbar_duration(
- KotoPlayerBar * bar,
+ KotoPlayerBar * self,
gint64 duration
);
void koto_playerbar_set_progressbar_value(
- KotoPlayerBar * bar,
+ KotoPlayerBar * self,
gdouble progress
);
diff --git a/src/koto-utils.c b/src/koto-utils.c
index 8b22649..3a536f5 100644
--- a/src/koto-utils.c
+++ b/src/koto-utils.c
@@ -100,6 +100,7 @@ gchar * koto_utils_get_filename_without_extension(gchar * filename) {
gchar * stripped_file_name = g_strstrip(g_strdup(trimmed_file_name)); // Strip leading and trailing whitespace
g_free(trimmed_file_name);
+ g_strfreev(split);
return stripped_file_name;
}
@@ -111,13 +112,13 @@ gchar * koto_utils_join_string_list (
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
+ if (!koto_utils_string_is_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
+ if (koto_utils_string_is_valid(liststring)) { // Is a valid string
gchar * new_string = g_strconcat(liststring, item_plus_sep, NULL);
g_free(liststring);
liststring = new_string;
@@ -128,11 +129,6 @@ gchar * koto_utils_join_string_list (
return (liststring == NULL) ? g_strdup("") : liststring;
}
-
-gboolean koto_utils_is_string_valid(gchar * str) {
- return ((str != NULL) && (g_strcmp0(str, "") != 0));
-}
-
void koto_utils_mkdir(gchar * path) {
mkdir(path, 0755);
chown(path, getuid(), getgid());
@@ -145,12 +141,37 @@ void koto_utils_push_queue_element_to_store(
g_list_store_append(G_LIST_STORE(user_data), data);
}
-gchar * koto_utils_replace_string_all(
+gchar * koto_utils_seconds_to_time_format(guint64 seconds) {
+ GDateTime * date = g_date_time_new_from_unix_utc(seconds); // Add our seconds after UTC
+ gchar * date_string = g_date_time_format(date, (g_date_time_get_hour(date) != 0) ? "%H:%M:%S" : "%M:%S");
+ g_date_time_unref(date);
+ return date_string;
+}
+
+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;
+}
+
+gchar * koto_utils_string_get_valid(gchar * str) {
+ return koto_utils_string_is_valid(str) ? str : g_strdup(""); // Return string if a string, otherwise return an empty string
+}
+
+gboolean koto_utils_string_is_valid(const gchar * str) {
+ return ((str != NULL) && (g_strcmp0(str, "") != 0));
+}
+
+gchar * koto_utils_string_replace_all(
gchar * str,
gchar * find,
gchar * repl
) {
- if (!koto_utils_is_string_valid(str)) { // Not a valid string
+ if (!koto_utils_string_is_valid(str)) { // Not a valid string
return g_strdup("");
}
@@ -166,22 +187,12 @@ gchar * koto_utils_replace_string_all(
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;
- if (!koto_utils_is_string_valid(s)) { // Provided string is not valid
+ if (!koto_utils_string_is_valid(s)) { // Provided string is not valid
return list;
}
@@ -189,17 +200,41 @@ GList * koto_utils_string_to_string_list(
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));
+ if (g_strcmp0(item, "") != 0) { // Not an empty string
+ list = g_list_append(list, g_strdup(item));
+ }
}
g_strfreev(separated_strings); // Free our strings
return list;
}
-gchar * koto_utils_unquote_string(gchar * s) {
+gchar * koto_utils_string_title(const gchar * s) {
+ if (!koto_utils_string_is_valid(s)) { // Not a valid string
+ return NULL;
+ }
+
+ glong len = g_utf8_strlen(s, -1);
+
+ if (len == 0) { // Empty string
+ return g_strdup(s); // Just duplicate itself
+ } else if (len == 1) { // One char
+ return g_utf8_strup(s, -1); // Uppercase all relevant cases
+ } else {
+ gchar * first_char = g_utf8_substring(s, 0, 1);
+ gchar * rest_of_string = g_utf8_substring(s, 1, len); // Rest of string
+ gchar * titled_string = g_strdup_printf("%s%s", g_utf8_strup(first_char, -1), rest_of_string);
+
+ g_free(first_char);
+ g_free(rest_of_string);
+ return titled_string;
+ }
+}
+
+gchar * koto_utils_string_unquote(gchar * s) {
gchar * new_s = NULL;
- if (!koto_utils_is_string_valid(s)) { // Not a valid string
+ if (!koto_utils_string_is_valid(s)) { // Not a valid string
new_s = g_strdup("");
return new_s;
}
diff --git a/src/koto-utils.h b/src/koto-utils.h
index cff0b46..131fe36 100644
--- a/src/koto-utils.h
+++ b/src/koto-utils.h
@@ -39,8 +39,6 @@ gchar * koto_utils_join_string_list(
gchar * sep
);
-gboolean koto_utils_is_string_valid(gchar * str);
-
void koto_utils_mkdir(gchar * path);
void koto_utils_push_queue_element_to_store(
@@ -48,22 +46,30 @@ void koto_utils_push_queue_element_to_store(
gpointer user_data
);
-gchar * koto_utils_replace_string_all(
- gchar * str,
- gchar * find,
- gchar * repl
-);
+gchar * koto_utils_seconds_to_time_format(guint64 seconds);
gboolean koto_utils_string_contains_substring(
gchar * s,
gchar * sub
);
+gchar * koto_utils_string_get_valid(gchar * str);
+
+gboolean koto_utils_string_is_valid(const gchar * str);
+
+gchar * koto_utils_string_replace_all(
+ gchar * str,
+ gchar * find,
+ gchar * repl
+);
+
GList * koto_utils_string_to_string_list(
gchar * s,
gchar * sep
);
-gchar * koto_utils_unquote_string(gchar * s);
+gchar * koto_utils_string_title(const gchar * s);
+
+gchar * koto_utils_string_unquote(gchar * s);
G_END_DECLS
diff --git a/src/koto-window.c b/src/koto-window.c
index 67be588..dcf014d 100644
--- a/src/koto-window.c
+++ b/src/koto-window.c
@@ -16,9 +16,10 @@
*/
#include
-#include "components/koto-action-bar.h"
+#include "components/action-bar.h"
#include "db/cartographer.h"
#include "indexer/structs.h"
+#include "pages/audiobooks/library.h"
#include "pages/music/music-local.h"
#include "pages/playlist/list.h"
#include "playback/engine.h"
@@ -33,6 +34,7 @@
extern KotoActionBar * action_bar;
extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup;
+extern KotoAudiobooksLibraryPage * audiobooks_library_page;
extern KotoCartographer * koto_maps;
extern KotoCreateModifyPlaylistDialog * playlist_create_modify_dialog;
extern KotoConfig * config;
@@ -138,11 +140,13 @@ static void koto_window_init (KotoWindow * self) {
gtk_widget_queue_draw(self->content_layout);
+ audiobooks_library_page = koto_audiobooks_library_page_new(); // Create our audiobooks library page
music_local_page = koto_page_music_local_new();
// TODO: Remove and do some fancy state loading
- koto_window_add_page(self, "music.local", GTK_WIDGET(music_local_page));
- koto_window_go_to_page(self, "music.local");
+ koto_window_add_page(self, "audiobooks.library", GTK_WIDGET(koto_audiobooks_library_page_get_main(audiobooks_library_page)));
+ koto_window_add_page(self, "music.library", GTK_WIDGET(music_local_page));
+ koto_window_go_to_page(self, "audiobooks.library");
gtk_widget_show(self->pages); // Do not remove this. Will cause sporadic hiding of the local page content otherwise.
}
@@ -174,7 +178,7 @@ void koto_window_manage_style(
(void) prop_id;
if (!KOTO_IS_WINDOW(self)) { // Not a Koto Window
- g_warning("Not a window");
+ return;
}
gchar * desired_theme = NULL;
@@ -188,7 +192,7 @@ void koto_window_manage_style(
NULL
);
- if (!koto_utils_is_string_valid(desired_theme)) { // Theme not valid
+ if (!koto_utils_string_is_valid(desired_theme)) { // Theme not valid
desired_theme = "dark";
}
diff --git a/src/koto.gresource.xml b/src/koto.gresource.xml
index 60a81d4..b09893c 100644
--- a/src/koto.gresource.xml
+++ b/src/koto.gresource.xml
@@ -1,6 +1,11 @@
+ ../data/genres/business-and-personal-finance.png
+ ../data/genres/foreign-languages.png
+ ../data/genres/mystery-and-thriller.png
+ ../data/genres/sci-fi.png
+ ../data/genres/travel.png
../theme/koto-builtin-dark.css
../theme/koto-builtin-gruvbox.css
../theme/koto-builtin-light.css
diff --git a/src/main.c b/src/main.c
index e1cc3b6..01e9637 100644
--- a/src/main.c
+++ b/src/main.c
@@ -28,6 +28,7 @@
#include "playback/mimes.h"
#include "playback/mpris.h"
#include "paths.h"
+#include "playlist/current.h"
#include "config/config.h"
#include "koto-paths.h"
@@ -40,6 +41,7 @@ extern GDBusNodeInfo * introspection_data;
extern KotoPlaybackEngine * playback_engine;
extern KotoCartographer * koto_maps;
+extern KotoCurrentPlaylist * current_playlist;
extern sqlite3 * koto_db;
extern GHashTable * supported_mimes_hash;
@@ -74,6 +76,7 @@ static void on_activate (GtkApplication * app) {
static void on_shutdown(GtkApplication * app) {
(void) app;
+ koto_current_playlist_save_playlist_state(current_playlist); // Save the current playlist state if necessary before closure
koto_config_save(config); // Save our config
close_db(); // Close the database
g_bus_unown_name(mpris_bus_id);
diff --git a/src/meson.build b/src/meson.build
index 7087ead..01ac94b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -5,8 +5,11 @@ add_global_arguments([
], language: 'c')
koto_sources = [
- 'components/koto-action-bar.c',
- 'components/koto-cover-art-button.c',
+ 'components/album-info.c',
+ 'components/action-bar.c',
+ 'components/button.c',
+ 'components/cover-art-button.c',
+ 'components/track-item.c',
'components/track-table.c',
'config/config.c',
'db/cartographer.c',
@@ -18,6 +21,11 @@ koto_sources = [
'indexer/library.c',
'indexer/track-helpers.c',
'indexer/track.c',
+ 'pages/audiobooks/audiobook-view.c',
+ 'pages/audiobooks/genres-banner.c',
+ 'pages/audiobooks/genre-button.c',
+ 'pages/audiobooks/library.c',
+ 'pages/audiobooks/writer-page.c',
'pages/music/album-view.c',
'pages/music/artist-view.c',
'pages/music/disc-view.c',
@@ -32,13 +40,11 @@ koto_sources = [
'playlist/current.c',
'playlist/playlist.c',
'main.c',
- 'koto-button.c',
'koto-dialog-container.c',
'koto-expander.c',
'koto-nav.c',
'koto-playerbar.c',
'koto-paths.c',
- 'koto-track-item.c',
'koto-utils.c',
'koto-window.c',
]
diff --git a/src/pages/audiobooks/audiobook-view.c b/src/pages/audiobooks/audiobook-view.c
new file mode 100644
index 0000000..be2b1bc
--- /dev/null
+++ b/src/pages/audiobooks/audiobook-view.c
@@ -0,0 +1,255 @@
+/* audiobook-view.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 "../../components/album-info.h"
+#include "../../components/button.h"
+#include "../../components/track-item.h"
+#include "../../db/cartographer.h"
+#include "../../indexer/album-playlist-funcs.h"
+#include "../../indexer/structs.h"
+#include "../../playlist/current.h"
+#include "../../koto-utils.h"
+#include "../../koto-window.h"
+#include "audiobook-view.h"
+
+extern KotoCartographer * koto_maps;
+extern KotoCurrentPlaylist * current_playlist;
+extern KotoWindow * main_window;
+
+struct _KotoAudiobookView {
+ GtkBox parent_instance;
+
+ KotoAlbum * album; // Album associated with this view
+
+ GtkWidget * side_info; // Our side info (artwork, playback button, position)
+ GtkWidget * info_contents; // Our info and contents vertical box
+
+ KotoAlbumInfo * album_info; // Our "Album" (Audiobook) info
+
+ GtkWidget * audiobook_art; // Our GtkImage for the audiobook art
+ GtkWidget * playback_button; // Our GtkButton for playback
+ GtkWidget * chapter_info; // Our GtkLabel of the position, effectively
+ GtkWidget * playback_position; // Our GtkLabel for the track playback position
+
+ GtkWidget * tracks_list; // GtkListBox of tracks
+};
+
+struct _KotoAudiobookViewClass {
+ GtkBoxClass parent_class;
+};
+
+G_DEFINE_TYPE(KotoAudiobookView, koto_audiobook_view, GTK_TYPE_BOX);
+
+static void koto_audiobook_view_class_init(KotoAudiobookViewClass * c) {
+ (void) c;
+}
+
+static void koto_audiobook_view_init(KotoAudiobookView * self) {
+ gtk_widget_add_css_class(GTK_WIDGET(self), "audiobook-view");
+
+ self->side_info = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_add_css_class(self->side_info, "side-info");
+ gtk_widget_set_size_request(self->side_info, 220, -1); // Match audiobook art size
+
+ self->info_contents = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ self->audiobook_art = gtk_image_new_from_icon_name("audio-x-generic-symbolic"); // Create an image with a symbolic icon
+ gtk_widget_set_size_request(self->audiobook_art, 220, 220); // Set to 220 like our cover art button
+
+ self->playback_button = gtk_button_new_with_label("Play"); // Default to our button saying Play instead of continue
+ gtk_widget_add_css_class(self->playback_button, "suggested-action");
+ g_signal_connect(self->playback_button, "clicked", G_CALLBACK(koto_audiobook_view_handle_play_clicked), self);
+
+ self->chapter_info = gtk_label_new(NULL);
+ self->playback_position = gtk_label_new(NULL);
+
+ gtk_widget_set_halign(self->chapter_info, GTK_ALIGN_START);
+ gtk_widget_set_halign(self->playback_position, GTK_ALIGN_START);
+
+ gtk_box_append(GTK_BOX(self->side_info), self->audiobook_art); // Add our audiobook art to the side info
+ gtk_box_append(GTK_BOX(self->side_info), GTK_WIDGET(self->playback_button)); // Add our playback button to the side info
+ gtk_box_append(GTK_BOX(self->side_info), self->chapter_info);
+ gtk_box_append(GTK_BOX(self->side_info), self->playback_position);
+
+ self->album_info = koto_album_info_new("audiobook"); // Create an "audiobook" type KotoAlbumInfo
+ gtk_box_append(GTK_BOX(self->info_contents), GTK_WIDGET(self->album_info)); // Add the album info the info contents
+
+ GtkWidget * chapters_label = gtk_label_new("Chapters");
+ gtk_widget_add_css_class(chapters_label, "chapters-label");
+ gtk_widget_set_halign(chapters_label, GTK_ALIGN_START); // Align to the start
+
+ gtk_box_append(GTK_BOX(self->info_contents), chapters_label);
+
+ self->tracks_list = gtk_list_box_new(); // Create our list of our tracks
+ gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->tracks_list), FALSE);
+ gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->tracks_list), GTK_SELECTION_MULTIPLE);
+ gtk_widget_add_css_class(self->tracks_list, "track-list");
+ gtk_box_append(GTK_BOX(self->info_contents), self->tracks_list); // Add our listbox to the info contents
+
+ gtk_box_append(GTK_BOX(self), self->side_info); // Add our side info to the box
+ gtk_box_append(GTK_BOX(self), self->info_contents); // Add our info contents to the box
+}
+
+GtkWidget * koto_audiobook_view_create_track_item(
+ gpointer item,
+ gpointer user_data
+) {
+ (void) user_data;
+
+ if (!KOTO_IS_TRACK(item)) { // Not actually a track
+ return NULL;
+ }
+
+ KotoTrack * track = KOTO_TRACK(item); // Cast our item as a track
+ KotoTrackItem * track_item = koto_track_item_new(track); // Create our track item
+
+ if (!KOTO_IS_TRACK_ITEM(track_item)) { // Not a track item
+ return NULL;
+ }
+
+ return GTK_WIDGET(track_item); // Cast as a widget and return the track item
+}
+
+void koto_audiobook_view_handle_play_clicked(
+ GtkButton * button,
+ gpointer user_data
+) {
+ (void) button;
+ KotoAudiobookView * self = user_data;
+
+ if (!KOTO_IS_AUDIOBOOK_VIEW(self)) {
+ return;
+ }
+
+ if (!KOTO_IS_ALBUM(self->album)) { // Don't have an album associated with this view
+ return;
+ }
+
+ koto_playlist_commit(koto_album_get_playlist(self->album)); // Ensure we commit the current playlist to the database. This is needed to ensure we are able to save the playlist state going forward
+ koto_current_playlist_set_playlist(current_playlist, koto_album_get_playlist(self->album), TRUE, TRUE); // Set this playlist to be played immediately, play current track
+}
+
+void koto_audiobook_view_handle_playlist_updated(
+ KotoPlaylist * playlist,
+ gpointer user_data
+) {
+ (void) playlist;
+
+ KotoAudiobookView * self = user_data;
+
+ if (!KOTO_IS_AUDIOBOOK_VIEW(self)) {
+ return;
+ }
+
+ koto_audiobook_view_update_side_info(self); // Update the side info based on the playlist modification
+}
+
+void koto_audiobook_view_set_album(
+ KotoAudiobookView * self,
+ KotoAlbum * album
+) {
+ if (!KOTO_IS_AUDIOBOOK_VIEW(self)) {
+ return;
+ }
+
+ if (!KOTO_IS_ALBUM(album)) { // Not an album
+ return;
+ }
+
+ self->album = album;
+
+ gchar * album_art_path = koto_album_get_art(album); // Get any artwork
+
+ if (koto_utils_string_is_valid(album_art_path)) { // Have album art
+ gtk_image_set_from_file(GTK_IMAGE(self->audiobook_art), album_art_path); // Set our album art
+ }
+
+ koto_album_info_set_album_uuid(self->album_info, koto_album_get_uuid(album)); // Apply our album info
+
+ gtk_list_box_bind_model(
+ // Apply our binding for the GtkListBox
+ GTK_LIST_BOX(self->tracks_list),
+ G_LIST_MODEL(koto_album_get_store(album)),
+ koto_audiobook_view_create_track_item,
+ NULL,
+ koto_audiobook_view_destroy_associated_user_data
+ );
+
+
+ KotoPlaylist * album_playlist = koto_album_get_playlist(self->album); // Get the album playlist
+ if (!KOTO_IS_PLAYLIST(album_playlist)) { // Not a playlist
+ return;
+ }
+
+ g_signal_connect(album_playlist, "modified", G_CALLBACK(koto_audiobook_view_handle_playlist_updated), self); // Handle modifications of a playlist
+ g_signal_connect(album_playlist, "track-load-finalized", G_CALLBACK(koto_audiobook_view_handle_playlist_updated), self); // Handle when a playlist is finalized
+ koto_audiobook_view_update_side_info(self); // Update our side info
+}
+
+void koto_audiobook_view_update_side_info(KotoAudiobookView * self) {
+ if (!KOTO_IS_AUDIOBOOK_VIEW(self)) {
+ return;
+ }
+
+ if (!KOTO_IS_ALBUM(self->album)) { // Not an album
+ return;
+ }
+
+ KotoPlaylist * album_playlist = koto_album_get_playlist(self->album); // Get the album playlist
+ if (!KOTO_IS_PLAYLIST(album_playlist)) { // Not a playlist
+ return;
+ }
+
+ gint playlist_position = koto_playlist_get_current_position(album_playlist); // Get the current position in the playlist
+ gint playlist_length = koto_playlist_get_length(album_playlist); // Get the length of the playlist
+
+ KotoTrack * current_track = koto_playlist_get_current_track(album_playlist); // Get the track for the playlist
+
+ if (!KOTO_IS_TRACK(current_track)) { // Not a track
+ return;
+ }
+
+ guint playback_position = koto_track_get_playback_position(current_track);
+ guint duration = koto_track_get_duration(current_track);
+ gboolean continuing = (playlist_position >= 1) || ((playlist_position <= 0) && (playback_position != 0));
+
+ gtk_button_set_label(GTK_BUTTON(self->playback_button), continuing ? "Resume" : "Play");
+
+ if (continuing) { // Have been playing track
+ gtk_label_set_text(GTK_LABEL(self->chapter_info), g_strdup_printf("Track %i of %i", (playlist_position <= 0) ? 1 : (playlist_position + 1), playlist_length));
+ gtk_label_set_text(GTK_LABEL(self->playback_position), g_strdup_printf("%s / %s", koto_utils_seconds_to_time_format(playback_position), koto_utils_seconds_to_time_format(duration)));
+ gtk_widget_show(self->chapter_info);
+ gtk_widget_show(self->playback_position);
+ } else {
+ gtk_widget_hide(self->chapter_info); // Hide by default
+ gtk_widget_hide(self->playback_position); // Hide by default
+ }
+}
+
+void koto_audiobook_view_destroy_associated_user_data(gpointer user_data) {
+ (void) user_data;
+}
+
+KotoAudiobookView * koto_audiobook_view_new() {
+ return g_object_new(
+ KOTO_TYPE_AUDIOBOOK_VIEW,
+ "orientation",
+ GTK_ORIENTATION_HORIZONTAL,
+ NULL
+ );
+}
\ No newline at end of file
diff --git a/src/pages/audiobooks/audiobook-view.h b/src/pages/audiobooks/audiobook-view.h
new file mode 100644
index 0000000..fe5cee4
--- /dev/null
+++ b/src/pages/audiobooks/audiobook-view.h
@@ -0,0 +1,56 @@
+/* audiobook-view.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
+#include
+#include "../../indexer/structs.h"
+
+G_BEGIN_DECLS
+
+#define KOTO_TYPE_AUDIOBOOK_VIEW (koto_audiobook_view_get_type())
+G_DECLARE_FINAL_TYPE(KotoAudiobookView, koto_audiobook_view, KOTO, AUDIOBOOK_VIEW, GtkBox);
+#define KOTO_IS_AUDIOBOOK_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOK_VIEW))
+
+void koto_audiobook_view_set_album(
+ KotoAudiobookView * self,
+ KotoAlbum * album
+);
+
+GtkWidget * koto_audiobook_view_create_track_item(
+ gpointer item,
+ gpointer user_data
+);
+
+void koto_audiobook_view_handle_play_clicked(
+ GtkButton * button,
+ gpointer user_data
+);
+
+void koto_audiobook_view_handle_playlist_updated(
+ KotoPlaylist * playlist,
+ gpointer user_data
+);
+
+void koto_audiobook_view_update_side_info(KotoAudiobookView * self);
+
+void koto_audiobook_view_destroy_associated_user_data(gpointer user_data);
+
+KotoAudiobookView * koto_audiobook_view_new();
+
+G_END_DECLS
\ No newline at end of file
diff --git a/src/pages/audiobooks/genre-button.c b/src/pages/audiobooks/genre-button.c
new file mode 100644
index 0000000..a706a5e
--- /dev/null
+++ b/src/pages/audiobooks/genre-button.c
@@ -0,0 +1,216 @@
+/* genres-button.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 "../../components/button.h"
+#include "../../koto-utils.h"
+#include "genre-button.h"
+
+enum {
+ PROP_0,
+ PROP_GENRE_ID,
+ PROP_GENRE_NAME,
+ N_PROPS
+};
+
+static GParamSpec * genre_button_props[N_PROPS] = {
+ NULL,
+};
+
+struct _KotoAudiobooksGenreButton {
+ GtkBox parent_instance;
+
+ gchar * genre_id;
+ gchar * genre_name;
+
+ GtkWidget * bg_image;
+ KotoButton * button;
+};
+
+struct _KotoAudiobooksGenreButtonClass {
+ GtkBoxClass parent_class;
+};
+
+G_DEFINE_TYPE(KotoAudiobooksGenreButton, koto_audiobooks_genre_button, GTK_TYPE_BOX);
+
+static void koto_audiobooks_genre_button_get_property(
+ GObject * obj,
+ guint prop_id,
+ GValue * val,
+ GParamSpec * spec
+);
+
+static void koto_audiobooks_genre_button_set_property(
+ GObject * obj,
+ guint prop_id,
+ const GValue * val,
+ GParamSpec * spec
+);
+
+static void koto_audiobooks_genre_button_class_init(KotoAudiobooksGenreButtonClass * c) {
+ GObjectClass * gobject_class;
+
+ gobject_class = G_OBJECT_CLASS(c);
+ gobject_class->set_property = koto_audiobooks_genre_button_set_property;
+ gobject_class->get_property = koto_audiobooks_genre_button_get_property;
+
+ genre_button_props[PROP_GENRE_ID] = g_param_spec_string(
+ "genre-id",
+ "Genre ID",
+ "Genre ID",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
+ genre_button_props[PROP_GENRE_NAME] = g_param_spec_string(
+ "genre-name",
+ "Genre Name",
+ "Genre Name",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
+ g_object_class_install_properties(gobject_class, N_PROPS, genre_button_props);
+}
+
+static void koto_audiobooks_genre_button_init(KotoAudiobooksGenreButton * self) {
+ gtk_widget_add_css_class(GTK_WIDGET(self), "audiobook-genre-button");
+ gtk_widget_set_hexpand(GTK_WIDGET(self), FALSE);
+ gtk_widget_set_vexpand(GTK_WIDGET(self), FALSE);
+
+ gtk_widget_set_size_request(GTK_WIDGET(self), 260, 120);
+ GtkWidget * overlay = gtk_overlay_new(); // Create our overlay
+ self->bg_image = gtk_picture_new(); // Create our new image
+
+ self->button = koto_button_new_plain(NULL); // Create a new button
+ koto_button_set_text_wrap(self->button, TRUE); // Enable text wrapping for long genre lines
+ gtk_widget_set_valign(GTK_WIDGET(self->button), GTK_ALIGN_END); // Align towards bottom of button
+
+ gtk_widget_set_hexpand(overlay, TRUE);
+
+ gtk_overlay_set_child(GTK_OVERLAY(overlay), self->bg_image);
+ gtk_overlay_add_overlay(GTK_OVERLAY(overlay), GTK_WIDGET(self->button));
+
+ gtk_box_append(GTK_BOX(self), overlay); // Add the overlay to self
+}
+
+static void koto_audiobooks_genre_button_get_property(
+ GObject * obj,
+ guint prop_id,
+ GValue * val,
+ GParamSpec * spec
+) {
+ KotoAudiobooksGenreButton * self = KOTO_AUDIOBOOKS_GENRE_BUTTON(obj);
+
+ switch (prop_id) {
+ case PROP_GENRE_ID:
+ g_value_set_string(val, self->genre_id);
+ break;
+ case PROP_GENRE_NAME:
+ g_value_set_string(val, self->genre_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
+ break;
+ }
+}
+
+static void koto_audiobooks_genre_button_set_property(
+ GObject * obj,
+ guint prop_id,
+ const GValue * val,
+ GParamSpec * spec
+) {
+ KotoAudiobooksGenreButton * self = KOTO_AUDIOBOOKS_GENRE_BUTTON(obj);
+
+ switch (prop_id) {
+ case PROP_GENRE_ID:
+ koto_audiobooks_genre_button_set_id(self, g_strdup(g_value_get_string(val)));
+ break;
+ case PROP_GENRE_NAME:
+ koto_audiobooks_genre_button_set_name(self, g_strdup(g_value_get_string(val)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
+ break;
+ }
+}
+
+KotoButton * koto_audiobooks_genre_button_get_button(KotoAudiobooksGenreButton * self) {
+ return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->button : NULL;
+}
+
+gchar * koto_audiobooks_genre_button_get_id(KotoAudiobooksGenreButton * self) {
+ return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->genre_id : NULL;
+}
+
+gchar * koto_audiobooks_genre_button_get_name(KotoAudiobooksGenreButton * self) {
+ return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->genre_name : NULL;
+}
+
+void koto_audiobooks_genre_button_set_id(
+ KotoAudiobooksGenreButton * self,
+ gchar * genre_id
+) {
+ if (!KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self)) {
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(genre_id)) {
+ return;
+ }
+
+ self->genre_id = genre_id;
+
+ gtk_picture_set_resource(GTK_PICTURE(self->bg_image), g_strdup_printf("/com/github/joshstrobl/koto/%s.png", genre_id));
+ gtk_widget_set_size_request(self->bg_image, 260, 120);
+}
+
+void koto_audiobooks_genre_button_set_name(
+ KotoAudiobooksGenreButton * self,
+ gchar * genre_name
+) {
+ if (!KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self)) {
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(genre_name)) {
+ return;
+ }
+
+ if (koto_utils_string_is_valid(self->genre_name)) { // Already have a genre name
+ g_free(self->genre_name);
+ }
+
+ self->genre_name = genre_name;
+ koto_button_set_text(self->button, genre_name);
+}
+
+KotoAudiobooksGenreButton * koto_audiobooks_genre_button_new(
+ gchar * genre_id,
+ gchar * genre_name
+) {
+ return g_object_new(
+ KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON,
+ "genre-id",
+ genre_id,
+ "genre-name",
+ genre_name,
+ NULL
+ );
+}
\ No newline at end of file
diff --git a/src/pages/audiobooks/genre-button.h b/src/pages/audiobooks/genre-button.h
new file mode 100644
index 0000000..dc98a4b
--- /dev/null
+++ b/src/pages/audiobooks/genre-button.h
@@ -0,0 +1,49 @@
+/* genres-button.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.
+ */
+
+#include
+#include
+#include "../../components/button.h"
+
+G_BEGIN_DECLS
+
+#define KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON koto_audiobooks_genre_button_get_type()
+G_DECLARE_FINAL_TYPE(KotoAudiobooksGenreButton, koto_audiobooks_genre_button, KOTO, AUDIOBOOKS_GENRE_BUTTON, GtkBox)
+#define KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON))
+
+KotoButton * koto_audiobooks_genre_button_get_button(KotoAudiobooksGenreButton * self);
+
+gchar * koto_audiobooks_genre_button_get_id(KotoAudiobooksGenreButton * self);
+
+gchar * koto_audiobooks_genre_button_get_name(KotoAudiobooksGenreButton * self);
+
+void koto_audiobooks_genre_button_set_id(
+ KotoAudiobooksGenreButton * self,
+ gchar * genre_id
+);
+
+void koto_audiobooks_genre_button_set_name(
+ KotoAudiobooksGenreButton * self,
+ gchar * genre_name
+);
+
+KotoAudiobooksGenreButton * koto_audiobooks_genre_button_new(
+ gchar * genre_id,
+ gchar * genre_name
+);
+
+G_END_DECLS
\ No newline at end of file
diff --git a/src/pages/audiobooks/genres-banner.c b/src/pages/audiobooks/genres-banner.c
new file mode 100644
index 0000000..72d6374
--- /dev/null
+++ b/src/pages/audiobooks/genres-banner.c
@@ -0,0 +1,153 @@
+/* genres-banner.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 "../../components/button.h"
+#include "../../koto-utils.h"
+#include "genre-button.h"
+#include "genres-banner.h"
+
+enum {
+ SIGNAL_GENRE_CLICKED,
+ N_SIGNALS
+};
+
+static guint banner_signals[N_SIGNALS] = {
+ 0
+};
+
+struct _KotoAudiobooksGenresBanner {
+ GObject parent_instance;
+
+ GtkWidget * main; // Our main content
+
+ GtkWidget * banner_revealer; // Our GtkRevealer for the strip
+ GtkWidget * banner_viewport; // Our banner viewport
+ GtkWidget * banner_content; // Our banner content as a horizontal box
+
+ GtkWidget * strip_revealer; // Our GtkRevealer for the strip
+ GtkWidget * strip_viewport; // Our strip viewport
+ GtkWidget * strip_content; // Our strip content
+
+ GHashTable * genre_ids_to_names; // Our genre "ids" to the name
+ GHashTable * genre_ids_to_ptrs; // Our HashTable of genre IDs to our GPtrArray with pointers to banner and strip buttons
+};
+
+struct _KotoAudiobooksGenresBannerClass {
+ GObjectClass parent_instance;
+ void (* genre_clicked) (gchar * genre);
+};
+
+G_DEFINE_TYPE(KotoAudiobooksGenresBanner, koto_audiobooks_genres_banner, G_TYPE_OBJECT);
+
+static void koto_audiobooks_genres_banner_class_init(KotoAudiobooksGenresBannerClass * c) {
+ GObjectClass * gobject_class = G_OBJECT_CLASS(c);
+
+ banner_signals[SIGNAL_GENRE_CLICKED] = g_signal_new(
+ "genre-clicked",
+ G_TYPE_FROM_CLASS(gobject_class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(KotoAudiobooksGenresBannerClass, genre_clicked),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_CHAR
+ );
+}
+
+static void koto_audiobooks_genres_banner_init(KotoAudiobooksGenresBanner * self) {
+ self->genre_ids_to_ptrs = g_hash_table_new(g_str_hash, g_str_equal);
+ self->genre_ids_to_names = g_hash_table_new(g_str_hash, g_str_equal);
+ g_hash_table_insert(self->genre_ids_to_names, g_strdup("hip-hop"), g_strdup("Hip-hop"));
+ g_hash_table_insert(self->genre_ids_to_names, g_strdup("indie"), g_strdup("Indie"));
+ g_hash_table_insert(self->genre_ids_to_names, g_strdup("sci-fi"), g_strdup("Science Fiction"));
+
+ self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Create the main box as a vertical box
+ gtk_widget_add_css_class(self->main, "genres-banner");
+ gtk_widget_set_halign(self->main, GTK_ALIGN_START);
+ gtk_widget_set_vexpand(self->main, FALSE);
+
+ self->banner_revealer = gtk_revealer_new(); // Create our revealer for the banner
+ self->strip_revealer = gtk_revealer_new(); // Create a revealer for the strip
+
+ gtk_revealer_set_transition_type(GTK_REVEALER(self->strip_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
+ gtk_revealer_set_transition_type(GTK_REVEALER(self->banner_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
+
+ self->banner_viewport = gtk_viewport_new(NULL, NULL); // Create our viewport for enabling the banner content to be scrollable
+ self->strip_viewport = gtk_viewport_new(NULL, NULL); // Create our viewport for enabling the strip content to be scrollable
+
+ self->banner_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create our horizontal box for the content
+ gtk_widget_add_css_class(self->banner_content, "large-banner");
+
+ self->strip_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create the horizontal box for the strip content
+ gtk_widget_add_css_class(self->strip_content, "strip");
+
+ gtk_revealer_set_child(GTK_REVEALER(self->banner_revealer), self->banner_viewport); // Set the viewport to be the content which gets revealed
+ gtk_revealer_set_child(GTK_REVEALER(self->strip_revealer), self->strip_viewport); // Set the viewport to be the cont ent which gets revealed
+ gtk_revealer_set_reveal_child(GTK_REVEALER(self->banner_revealer), TRUE); // Show the banner by default
+
+ gtk_viewport_set_child(GTK_VIEWPORT(self->banner_viewport), self->banner_content); // Set the banner content for this viewport
+ gtk_viewport_set_child(GTK_VIEWPORT(self->strip_viewport), self->strip_content); // Set the strip content for this viewport
+
+ gtk_box_append(GTK_BOX(self->main), self->strip_revealer);
+ gtk_box_append(GTK_BOX(self->main), self->banner_revealer);
+}
+
+void koto_audiobooks_genres_banner_add_genre(
+ KotoAudiobooksGenresBanner * self,
+ gchar * genre_id
+) {
+ if (!KOTO_IS_AUDIOBOOKS_GENRES_BANNER(self)) { // Not a banner
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(genre_id)) { // Not a valid genre ID
+ return;
+ }
+
+ if (g_hash_table_contains(self->genre_ids_to_ptrs, genre_id)) { // Already have added this
+ return;
+ }
+
+ gchar * name = g_hash_table_lookup(self->genre_ids_to_names, genre_id); // Get the named equivelant for this ID
+
+ if (name == NULL) { // Didn't find a name equivelant
+ name = g_strdup(genre_id); // Just duplicate the genre ID for now
+ }
+
+ KotoButton * genre_strip_button = koto_button_new_plain(name); // Create a new button with the name
+ gtk_box_append(GTK_BOX(self->strip_content), GTK_WIDGET(genre_strip_button)); // Add our KotoButton to the strip content
+ KotoAudiobooksGenreButton * genre_banner_button = koto_audiobooks_genre_button_new(genre_id, name); // Create our big button
+ gtk_box_append(GTK_BOX(self->banner_content), GTK_WIDGET(genre_banner_button));
+
+ GPtrArray * buttons = g_ptr_array_new();
+ g_ptr_array_add(buttons, (gpointer) genre_strip_button); // Add our KotoButton as a gpointer to our GPtrArray as the first item
+ g_ptr_array_add(buttons, (gpointer) genre_banner_button); // Add our KotoAudiobooksGenresButton as a gpointer to our GPtrArray as the second item
+
+ g_hash_table_replace(self->genre_ids_to_ptrs, genre_id, buttons); // Add our GPtrArray to our genre_ids_to_ptrs
+}
+
+GtkWidget * koto_audiobooks_genres_banner_get_main(KotoAudiobooksGenresBanner * self) {
+ return KOTO_IS_AUDIOBOOKS_GENRES_BANNER(self) ? self->main : NULL;
+}
+
+KotoAudiobooksGenresBanner * koto_audiobooks_genres_banner_new() {
+ return g_object_new(KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER, NULL);
+}
\ No newline at end of file
diff --git a/src/pages/audiobooks/genres-banner.h b/src/pages/audiobooks/genres-banner.h
new file mode 100644
index 0000000..7a4e1b7
--- /dev/null
+++ b/src/pages/audiobooks/genres-banner.h
@@ -0,0 +1,44 @@
+/* genres-banner.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
+#include
+
+G_BEGIN_DECLS
+
+#define KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER koto_audiobooks_genres_banner_get_type()
+#define KOTO_AUDIOBOOKS_GENRES_BANNER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER, KotoAudiobooksGenresBanner))
+typedef struct _KotoAudiobooksGenresBanner KotoAudiobooksGenresBanner;
+typedef struct _KotoAudiobooksGenresBannerClass KotoAudiobooksGenresBannerClass;
+
+GLIB_AVAILABLE_IN_ALL
+GType koto_audiobooks_genres_banner_get_type(void) G_GNUC_CONST;
+
+#define KOTO_IS_AUDIOBOOKS_GENRES_BANNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER))
+
+void koto_audiobooks_genres_banner_add_genre(
+ KotoAudiobooksGenresBanner * self,
+ gchar * genre_id
+);
+
+GtkWidget * koto_audiobooks_genres_banner_get_main(KotoAudiobooksGenresBanner * self);
+
+KotoAudiobooksGenresBanner * koto_audiobooks_genres_banner_new();
+
+G_END_DECLS
\ No newline at end of file
diff --git a/src/pages/audiobooks/library.c b/src/pages/audiobooks/library.c
new file mode 100644
index 0000000..b6c9864
--- /dev/null
+++ b/src/pages/audiobooks/library.c
@@ -0,0 +1,213 @@
+/* library.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 "../../components/button.h"
+#include "../../db/cartographer.h"
+#include "../../indexer/structs.h"
+#include "../../koto-utils.h"
+#include "../../koto-window.h"
+#include "genres-banner.h"
+#include "library.h"
+#include "writer-page.h"
+
+extern KotoCartographer * koto_maps;
+extern KotoWindow * main_window;
+
+struct _KotoAudiobooksLibraryPage {
+ GObject parent_instance;
+
+ GtkWidget * main; // Our main content, contains banner and scrolled window
+ GtkWidget * content_scroll; // Our Scrolled Window
+ GtkWidget * content; // Content inside scrolled window
+
+ KotoAudiobooksGenresBanner * banner;
+
+ GtkWidget * writers_flow;
+ GHashTable * writers_to_buttons; // HashTable of UUIDs of "Artists" to their KotoButton
+ GHashTable * writers_to_pages; // HashTable of UUIDs of "Artists" to their KotoAudiobooksWritersPage
+};
+
+struct _KotoAudiobooksLibraryPageClass {
+ GObjectClass parent_instance;
+};
+
+G_DEFINE_TYPE(KotoAudiobooksLibraryPage, koto_audiobooks_library_page, G_TYPE_OBJECT);
+
+KotoAudiobooksLibraryPage * audiobooks_library_page;
+
+static void koto_audiobooks_library_page_init(KotoAudiobooksLibraryPage * self) {
+ self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_add_css_class(self->main, "audiobook-library");
+ self->content_scroll = gtk_scrolled_window_new(); // Create our GtkScrolledWindow
+ self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_set_vexpand(self->content, TRUE); // Ensure content expands vertically to allow flowboxchild to take up as much space as it requests
+ gtk_widget_add_css_class(self->content, "content");
+
+ self->banner = koto_audiobooks_genres_banner_new(); // Create our banner
+
+ self->writers_flow = gtk_flow_box_new(); // Create our flow box
+ //gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(self->writers_flow), TRUE);
+ gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(self->writers_flow), 100); // Set to a random amount that is not realistic, however GTK sets a default to 7 which is too small.
+ gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->writers_flow), GTK_SELECTION_NONE);
+ gtk_widget_add_css_class(self->writers_flow, "writers-button-flow");
+
+ self->writers_to_buttons = g_hash_table_new(g_str_hash, g_str_equal);
+ self->writers_to_pages = g_hash_table_new(g_str_hash, g_str_equal);
+
+ g_signal_connect(koto_maps, "album-added", G_CALLBACK(koto_audiobooks_library_page_handle_add_album), self); // Notify when we have a new Album
+ g_signal_connect(koto_maps, "artist-added", G_CALLBACK(koto_audiobooks_library_page_handle_add_artist), self); // Notify when we have a new Artist
+
+ gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->content_scroll), self->content); // Add our content to the content scroll
+ gtk_box_append(GTK_BOX(self->main), koto_audiobooks_genres_banner_get_main(self->banner)); // Add the banner to the content
+ gtk_box_append(GTK_BOX(self->main), self->content_scroll); // Add our scroll window to the main content
+ gtk_box_append(GTK_BOX(self->content), self->writers_flow); // Add our flowbox to the content
+}
+
+static void koto_audiobooks_library_page_class_init(KotoAudiobooksLibraryPageClass * c) {
+ (void) c;
+}
+
+void koto_audiobooks_library_page_create_artist_ux(
+ KotoAudiobooksLibraryPage * self,
+ KotoArtist * artist
+) {
+ if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage
+ return;
+ }
+
+ if (!KOTO_IS_ARTIST(artist)) { // Not an artist
+ return;
+ }
+
+ gchar * artist_uuid = koto_artist_get_uuid(artist); // Get the artist UUID
+
+ if (!g_hash_table_contains(self->writers_to_pages, artist_uuid)) { // Don't have the page
+ KotoWriterPage * writers_page = koto_writer_page_new(artist);
+ koto_window_add_page(main_window, artist_uuid, koto_writer_page_get_main(writers_page)); // Add the page to the stack
+ }
+
+ if (!g_hash_table_contains(self->writers_to_buttons, artist_uuid)) { // Don't have the button for this
+ KotoButton * artist_button = koto_button_new_plain(koto_artist_get_name(artist)); // Create a KotoButton for this artist
+ koto_button_set_data(artist_button, artist_uuid); // Set the artist_uuid as the data for the button
+
+ koto_button_set_text_justify(artist_button, GTK_JUSTIFY_CENTER); // Center the text
+ koto_button_set_text_wrap(artist_button, TRUE);
+ gtk_widget_add_css_class(GTK_WIDGET(artist_button), "writer-button");
+ gtk_widget_set_size_request(GTK_WIDGET(artist_button), 260, 120);
+
+ koto_button_add_click_handler(artist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), artist_button);
+
+ g_hash_table_replace(self->writers_to_buttons, artist_uuid, artist_button); // Add the button to the Writers to Buttons hashtable
+ gtk_flow_box_insert(GTK_FLOW_BOX(self->writers_flow), GTK_WIDGET(artist_button), -1); // Append the button to our flowbox
+
+ GtkWidget * button_parent = gtk_widget_get_parent(GTK_WIDGET(artist_button)); // This is the GtkFlowboxChild that the button is in
+ gtk_widget_set_halign(button_parent, GTK_ALIGN_START);
+ }
+}
+
+void koto_audiobooks_library_page_handle_add_album(
+ KotoCartographer * carto,
+ KotoAlbum * album,
+ KotoAudiobooksLibraryPage * self
+) {
+
+ if (!KOTO_IS_CARTOGRAPHER(carto)) { // Not cartographer
+ return;
+ }
+
+ if (!KOTO_IS_ALBUM(album)) { // Not an album
+ return;
+ }
+
+ if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage
+ return;
+ }
+
+ gchar * artist_uuid = koto_album_get_artist_uuid(album); // Get the Album's artist UUID
+ KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
+
+ if (!KOTO_IS_ARTIST(artist)) { // Failed to get artist
+ return;
+ }
+
+ if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Not in an Audiobook library
+ return;
+ }
+
+ koto_audiobooks_library_page_add_genres(self, koto_album_get_genres(album)); // Add all the genres necessary for this album
+}
+
+void koto_audiobooks_library_page_handle_add_artist(
+ KotoCartographer * carto,
+ KotoArtist * artist,
+ KotoAudiobooksLibraryPage * self
+) {
+ if (!KOTO_IS_CARTOGRAPHER(carto)) { // Not cartographer
+ return;
+ }
+
+ if (!KOTO_IS_ARTIST(artist)) { // Not an artist
+ return;
+ }
+
+ if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage
+ return;
+ }
+
+ if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Not in an Audiobook library
+ return;
+ }
+
+ koto_audiobooks_library_page_create_artist_ux(self, artist); // Create the UX for the artist if necessary
+}
+
+void koto_audiobooks_library_page_add_genres(
+ KotoAudiobooksLibraryPage * self,
+ GList * genres
+) {
+ if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage
+ return;
+ }
+
+ if (g_list_length(genres) == 0) { // No contents
+ return;
+ }
+
+ GList * cur_list;
+ for (cur_list = genres; cur_list != NULL; cur_list = cur_list->next) { // Iterate over each genre
+ gchar * genre = (gchar*) cur_list->data;
+ if (!koto_utils_string_is_valid(genre)) {
+ continue;
+ }
+
+ if (g_strcmp0(genre, "audiobook") == 0) { // Is generic
+ continue;
+ }
+
+ koto_audiobooks_genres_banner_add_genre(self->banner, genre); // Add this genre
+ }
+}
+
+GtkWidget * koto_audiobooks_library_page_get_main(KotoAudiobooksLibraryPage * self) {
+ return KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self) ? self->main : NULL;
+}
+
+KotoAudiobooksLibraryPage * koto_audiobooks_library_page_new() {
+ return g_object_new(KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE, NULL);
+}
\ No newline at end of file
diff --git a/src/pages/audiobooks/library.h b/src/pages/audiobooks/library.h
new file mode 100644
index 0000000..88e633f
--- /dev/null
+++ b/src/pages/audiobooks/library.h
@@ -0,0 +1,56 @@
+/* library.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
+#include
+#include "../../db/cartographer.h"
+#include "../../indexer/structs.h"
+
+G_BEGIN_DECLS
+
+#define KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE koto_audiobooks_library_page_get_type()
+#define KOTO_AUDIOBOOKS_LIBRARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE, KotoAudiobooksLibraryPage))
+typedef struct _KotoAudiobooksLibraryPage KotoAudiobooksLibraryPage;
+typedef struct _KotoAudiobooksLibraryPageClass KotoAudiobooksLibraryPageClass;
+
+GLIB_AVAILABLE_IN_ALL
+GType koto_audiobooks_library_page_get_type(void) G_GNUC_CONST;
+
+#define KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE))
+
+void koto_audiobooks_library_page_add_genres(
+ KotoAudiobooksLibraryPage * self,
+ GList * genres
+);
+
+void koto_audiobooks_library_page_handle_add_album(
+ KotoCartographer * carto,
+ KotoAlbum * album,
+ KotoAudiobooksLibraryPage * self
+);
+
+void koto_audiobooks_library_page_handle_add_artist(
+ KotoCartographer * carto,
+ KotoArtist * artist,
+ KotoAudiobooksLibraryPage * self
+);
+
+GtkWidget * koto_audiobooks_library_page_get_main(KotoAudiobooksLibraryPage * self);
+
+KotoAudiobooksLibraryPage * koto_audiobooks_library_page_new();
\ No newline at end of file
diff --git a/src/pages/audiobooks/writer-page.c b/src/pages/audiobooks/writer-page.c
new file mode 100644
index 0000000..f0944ce
--- /dev/null
+++ b/src/pages/audiobooks/writer-page.c
@@ -0,0 +1,206 @@
+/* writers-page.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 "../../db/cartographer.h"
+#include "../../indexer/structs.h"
+#include "../../koto-utils.h"
+#include "../../koto-window.h"
+#include "audiobook-view.h"
+#include "writer-page.h"
+
+extern KotoCartographer * koto_maps;
+extern KotoWindow * main_window;
+
+enum {
+ PROP_0,
+ PROP_ARTIST,
+ N_PROPERTIES
+};
+
+static GParamSpec * props[N_PROPERTIES] = {
+ NULL,
+};
+
+struct _KotoWriterPage {
+ GObject parent_instance;
+
+ KotoArtist * artist;
+
+ GtkWidget * main; // Our main content will be a scrolled Scrolled Window
+ GtkWidget * content; // Content inside scrolled window
+
+ GtkWidget * writers_header; // Header GtkLabel for the writer
+ GListModel * model;
+
+ GtkWidget * audiobooks_flow;
+};
+
+struct _KotoWriterPageClass {
+ GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE(KotoWriterPage, koto_writer_page, G_TYPE_OBJECT);
+
+static void koto_writer_page_get_property(
+ GObject * obj,
+ guint prop_id,
+ GValue * val,
+ GParamSpec * spec
+);
+
+static void koto_writer_page_set_property(
+ GObject * obj,
+ guint prop_id,
+ const GValue * val,
+ GParamSpec * spec
+);
+
+static void koto_writer_page_class_init(KotoWriterPageClass * c) {
+ GObjectClass * gobject_class;
+
+ gobject_class = G_OBJECT_CLASS(c);
+ gobject_class->set_property = koto_writer_page_set_property;
+ gobject_class->get_property = koto_writer_page_get_property;
+
+ props[PROP_ARTIST] = g_param_spec_object(
+ "artist",
+ "Artist",
+ "Artist",
+ KOTO_TYPE_ARTIST,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
+ g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
+}
+
+static void koto_writer_page_init(KotoWriterPage * self) {
+ self->main = gtk_scrolled_window_new(); // Set main to be a scrolled window
+ self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_add_css_class(self->content, "writer-page");
+
+ gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->main), self->content); // Set content to be the child of the scrolled window
+
+ self->writers_header = gtk_label_new(NULL); // Create an empty label
+ gtk_widget_add_css_class(self->writers_header, "writer-header");
+ gtk_widget_set_halign(self->writers_header, GTK_ALIGN_START);
+
+ self->audiobooks_flow = gtk_flow_box_new(); // Create our flowbox of the audiobooks views
+ gtk_widget_add_css_class(self->audiobooks_flow, "audiobooks-flow");
+
+ gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(self->audiobooks_flow), 2); // Allow 2 to ensure adequate spacing for description
+ gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->audiobooks_flow), GTK_SELECTION_NONE); // Do not allow selection
+
+ gtk_widget_set_hexpand(self->audiobooks_flow, TRUE); // Expand horizontally
+ gtk_widget_set_vexpand(self->audiobooks_flow, TRUE); // Expand vertically
+
+ gtk_box_append(GTK_BOX(self->content), self->writers_header);
+ gtk_box_append(GTK_BOX(self->content), self->audiobooks_flow); // Add the audiobooks flow to the content
+}
+
+static void koto_writer_page_get_property(
+ GObject * obj,
+ guint prop_id,
+ GValue * val,
+ GParamSpec * spec
+) {
+ KotoWriterPage * self = KOTO_WRITER_PAGE(obj);
+
+ switch (prop_id) {
+ case PROP_ARTIST:
+ g_value_set_object(val, self->artist);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
+ break;
+ }
+}
+
+static void koto_writer_page_set_property(
+ GObject * obj,
+ guint prop_id,
+ const GValue * val,
+ GParamSpec * spec
+) {
+ KotoWriterPage * self = KOTO_WRITER_PAGE(obj);
+
+ switch (prop_id) {
+ case PROP_ARTIST:
+ koto_writer_page_set_artist(self, (KotoArtist*) g_value_get_object(val));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
+ break;
+ }
+}
+
+GtkWidget * koto_writer_page_create_item(
+ gpointer item,
+ gpointer user_data
+) {
+ (void) user_data;
+
+ KotoAlbum * album = KOTO_ALBUM(item); // Cast the model data as an album
+
+ if (!KOTO_IS_ALBUM(album)) { // Fetched item from list is not album
+ return NULL;
+ }
+
+ KotoAudiobookView * view = koto_audiobook_view_new(); // Create our KotoAudiobookView
+ koto_audiobook_view_set_album(view, album); // Set the item for this set up audiobook view
+ return GTK_WIDGET(view);
+}
+
+GtkWidget * koto_writer_page_get_main(KotoWriterPage * self) {
+ return self->main;
+}
+
+void koto_writer_page_set_artist(
+ KotoWriterPage * self,
+ KotoArtist * artist
+) {
+ if (!KOTO_IS_WRITER_PAGE(self)) { // Not a writers page
+ return;
+ }
+
+ if (!KOTO_IS_ARTIST(artist)) { // Not an artist
+ return;
+ }
+
+ self->artist = artist;
+ gtk_label_set_text(GTK_LABEL(self->writers_header), koto_artist_get_name(self->artist)); // Get the label for the writers header
+
+ self->model = G_LIST_MODEL(koto_artist_get_albums_store(artist)); // Get the store and cast it as a list model for our model
+
+ gtk_flow_box_bind_model(
+ GTK_FLOW_BOX(self->audiobooks_flow),
+ self->model,
+ koto_writer_page_create_item,
+ NULL,
+ NULL
+ );
+}
+
+KotoWriterPage * koto_writer_page_new(KotoArtist * artist) {
+ return g_object_new(
+ KOTO_TYPE_WRITER_PAGE,
+ "artist",
+ artist,
+ NULL
+ );
+}
\ No newline at end of file
diff --git a/src/pages/audiobooks/writer-page.h b/src/pages/audiobooks/writer-page.h
new file mode 100644
index 0000000..2a2a913
--- /dev/null
+++ b/src/pages/audiobooks/writer-page.h
@@ -0,0 +1,41 @@
+/* writers-page.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
+#include
+
+G_BEGIN_DECLS
+
+#define KOTO_TYPE_WRITER_PAGE (koto_writer_page_get_type())
+G_DECLARE_FINAL_TYPE(KotoWriterPage, koto_writer_page, KOTO, WRITER_PAGE, GObject)
+
+GtkWidget* koto_writer_page_create_item(
+ gpointer item,
+ gpointer user_data
+);
+
+GtkWidget * koto_writer_page_get_main(KotoWriterPage * self);
+
+void koto_writer_page_set_artist(
+ KotoWriterPage * self,
+ KotoArtist * artist
+);
+
+KotoWriterPage * koto_writer_page_new(KotoArtist * artist);
+
+G_END_DECLS
\ No newline at end of file
diff --git a/src/pages/music/album-view.c b/src/pages/music/album-view.c
index d733dc2..678f23b 100644
--- a/src/pages/music/album-view.c
+++ b/src/pages/music/album-view.c
@@ -17,10 +17,11 @@
#include
#include
-#include "../../components/koto-cover-art-button.h"
+#include "../../components/album-info.h"
+#include "../../components/button.h"
+#include "../../components/cover-art-button.h"
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
-#include "../../koto-button.h"
#include "album-view.h"
#include "disc-view.h"
#include "config/config.h"
@@ -37,7 +38,7 @@ struct _KotoAlbumView {
KotoCoverArtButton * album_cover;
- GtkWidget * album_label;
+ KotoAlbumInfo * album_info;
GHashTable * cd_to_disc_views;
};
@@ -90,6 +91,8 @@ static void koto_album_view_init(KotoAlbumView * self) {
gtk_widget_add_css_class(self->main, "album-view");
gtk_widget_set_can_focus(self->main, FALSE);
self->album_tracks_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ self->album_info = koto_album_info_new(KOTO_ALBUM_INFO_TYPE_ALBUM); // Create an Album-type Album Info
+ gtk_box_prepend(GTK_BOX(self->album_tracks_box), GTK_WIDGET(self->album_info)); // Add our Album Info to the album_tracks box
self->discs = gtk_list_box_new(); // Create our list of our tracks
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->discs), GTK_SELECTION_NONE);
@@ -228,11 +231,7 @@ void koto_album_view_set_album(
gchar * album_art = koto_album_get_art(self->album); // Get the art for the album
koto_cover_art_button_set_art_path(self->album_cover, album_art);
-
- self->album_label = gtk_label_new(koto_album_get_name(album));
- gtk_widget_set_halign(self->album_label, GTK_ALIGN_START);
- gtk_box_prepend(GTK_BOX(self->album_tracks_box), self->album_label); // Prepend our new label to the album + tracks box
-
+ koto_album_info_set_album_uuid(self->album_info, koto_album_get_uuid(album)); // Set the album we should be displaying info about on the Album Info
g_signal_connect(self->album, "track-added", G_CALLBACK(koto_album_view_handle_track_added), self); // Handle track added on our album
}
diff --git a/src/pages/music/artist-view.c b/src/pages/music/artist-view.c
index 314487f..d0c4215 100644
--- a/src/pages/music/artist-view.c
+++ b/src/pages/music/artist-view.c
@@ -17,7 +17,7 @@
#include
#include
-#include "../../components/koto-cover-art-button.h"
+#include "../../components/cover-art-button.h"
#include "../../components/track-table.h"
#include "../../db/cartographer.h"
#include "../../indexer/artist-playlist-funcs.h"
@@ -323,7 +323,7 @@ void koto_artist_view_toggle_playback(
return;
}
- koto_current_playlist_set_playlist(current_playlist, artist_playlist, TRUE); // Set our playlist to the one associated with the Artist and start playback immediately
+ koto_current_playlist_set_playlist(current_playlist, artist_playlist, TRUE, FALSE); // Set our playlist to the one associated with the Artist and start playback immediately
}
KotoArtistView * koto_artist_view_new(KotoArtist * artist) {
diff --git a/src/pages/music/disc-view.c b/src/pages/music/disc-view.c
index 4425baa..01a8971 100644
--- a/src/pages/music/disc-view.c
+++ b/src/pages/music/disc-view.c
@@ -16,11 +16,11 @@
*/
#include
-#include "../../components/koto-action-bar.h"
+#include "../../components/action-bar.h"
+#include "../../components/track-item.h"
#include "../../db/cartographer.h"
#include "../../indexer/track-helpers.h"
#include "../../indexer/structs.h"
-#include "../../koto-track-item.h"
#include "../../koto-utils.h"
#include "disc-view.h"
@@ -194,7 +194,6 @@ void koto_disc_view_add_track(
KotoTrackItem * track_item = koto_track_item_new(track); // Create our new track item
if (!KOTO_IS_TRACK_ITEM(track_item)) { // Failed to create a track item for this track
- g_warning("Failed to build track item for track with UUID %s", track_uuid);
return;
}
@@ -212,9 +211,9 @@ void koto_disc_view_handle_selected_rows_changed(
return;
}
- gchar * album_uuid = koto_album_get_album_uuid(self->album); // Get the UUID
+ gchar * album_uuid = koto_album_get_uuid(self->album); // Get the UUID
- if (!koto_utils_is_string_valid(album_uuid)) { // Not set
+ if (!koto_utils_string_is_valid(album_uuid)) { // Not set
return;
}
@@ -247,7 +246,7 @@ void koto_disc_view_remove_track(
return;
}
- if (!koto_utils_is_string_valid(track_uuid)) { // If our UUID is not a valid
+ if (!koto_utils_string_is_valid(track_uuid)) { // If our UUID is not a valid
return;
}
diff --git a/src/pages/music/music-local.c b/src/pages/music/music-local.c
index fae261c..b979817 100644
--- a/src/pages/music/music-local.c
+++ b/src/pages/music/music-local.c
@@ -17,9 +17,9 @@
#include
#include
+#include "../../components/button.h"
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
-#include "koto-button.h"
#include "config/config.h"
#include "../../koto-utils.h"
#include "music-local.h"
@@ -154,7 +154,7 @@ void koto_page_music_local_go_to_artist_by_uuid(
NULL
);
- if (!koto_utils_is_string_valid(artist_name)) { // Failed to get the artist name
+ if (!koto_utils_string_is_valid(artist_name)) { // Failed to get the artist name
return;
}
diff --git a/src/pages/playlist/list.c b/src/pages/playlist/list.c
index f35f3e2..b49b330 100644
--- a/src/pages/playlist/list.c
+++ b/src/pages/playlist/list.c
@@ -17,14 +17,14 @@
#include
#include
-#include "../../components/koto-action-bar.h"
-#include "../../components/koto-cover-art-button.h"
+#include "../../components/action-bar.h"
+#include "../../components/button.h"
+#include "../../components/cover-art-button.h"
#include "../../components/track-table.h"
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
#include "../../playlist/current.h"
#include "../../playlist/playlist.h"
-#include "../../koto-button.h"
#include "../../koto-window.h"
#include "../../playlist/create-modify-dialog.h"
#include "list.h"
@@ -209,7 +209,7 @@ void koto_playlist_page_handle_cover_art_clicked(
return;
}
- koto_current_playlist_set_playlist(current_playlist, self->playlist, TRUE); // Switch to this playlist and start playing immediately
+ koto_current_playlist_set_playlist(current_playlist, self->playlist, TRUE, FALSE); // Switch to this playlist and start playing immediately
}
void koto_playlist_page_handle_edit_button_clicked(
@@ -245,13 +245,13 @@ void koto_playlist_page_handle_playlist_modified(
gchar * artwork = koto_playlist_get_artwork(playlist); // Get the artwork
- if (koto_utils_is_string_valid(artwork)) { // Have valid artwork
+ if (koto_utils_string_is_valid(artwork)) { // Have valid artwork
koto_cover_art_button_set_art_path(self->playlist_image, artwork); // Update our Koto Cover Art Button
}
gchar * name = koto_playlist_get_name(playlist); // Get the name
- if (koto_utils_is_string_valid(name)) { // Have valid name
+ if (koto_utils_string_is_valid(name)) { // Have valid name
gtk_label_set_label(GTK_LABEL(self->name_label), name); // Update the name label
}
}
@@ -264,7 +264,7 @@ void koto_playlist_page_set_playlist_uuid(
return;
}
- if (!koto_utils_is_string_valid(playlist_uuid)) { // Provided UUID string is not valid
+ if (!koto_utils_string_is_valid(playlist_uuid)) { // Provided UUID string is not valid
return;
}
@@ -309,7 +309,7 @@ void koto_playlist_page_update_header(KotoPlaylistPage * self) {
gchar * artwork = koto_playlist_get_artwork(self->playlist);
- if (koto_utils_is_string_valid(artwork)) { // Have artwork
+ if (koto_utils_string_is_valid(artwork)) { // Have artwork
koto_cover_art_button_set_art_path(self->playlist_image, artwork); // Update our artwork
}
}
diff --git a/src/pages/playlist/list.h b/src/pages/playlist/list.h
index eb263c0..6822a26 100644
--- a/src/pages/playlist/list.h
+++ b/src/pages/playlist/list.h
@@ -19,7 +19,7 @@
#include
#include
-#include "../../components/koto-action-bar.h"
+#include "../../components/action-bar.h"
#include "../../playlist/playlist.h"
#include "../../koto-utils.h"
diff --git a/src/playback/engine.c b/src/playback/engine.c
index e449842..3ece126 100644
--- a/src/playback/engine.c
+++ b/src/playback/engine.c
@@ -73,11 +73,14 @@ struct _KotoPlaybackEngine {
gboolean tick_duration_timer_running;
gboolean tick_track_timer_running;
+ gboolean tick_update_playlist_state;
gboolean via_config_continue_on_playlist; // Pulls from our Koto Config
gboolean via_config_maintain_shuffle; // Pulls from our Koto Config
guint playback_position;
+ gint requested_playback_position;
+
gdouble volume;
};
@@ -214,7 +217,7 @@ static void koto_playback_engine_init(KotoPlaybackEngine * self) {
self->monitor = gst_bus_new(); // Get the bus for the playbin
self->duration_query = gst_query_new_duration(GST_FORMAT_TIME); // Create our re-usable query for querying the duration
- self->position_query = gst_query_new_position(GST_FORMAT_DEFAULT); // Create our re-usable query for querying the position
+ self->position_query = gst_query_new_position(GST_FORMAT_TIME); // Create our re-usable query for querying the position
if (GST_IS_BUS(self->monitor)) {
gst_bus_add_watch(self->monitor, koto_playback_engine_monitor_changed, self);
@@ -226,8 +229,10 @@ static void koto_playback_engine_init(KotoPlaybackEngine * self) {
self->is_playing_specific_track = FALSE;
self->is_repeat_enabled = FALSE;
self->is_shuffle_enabled = FALSE;
+ self->requested_playback_position = -1;
self->tick_duration_timer_running = FALSE;
self->tick_track_timer_running = FALSE;
+ self->tick_update_playlist_state = FALSE;
self->via_config_continue_on_playlist = FALSE;
self->via_config_maintain_shuffle = TRUE;
@@ -247,6 +252,10 @@ void koto_playback_engine_apply_configuration_state(
guint prop_id,
KotoPlaybackEngine * self
) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
+ return;
+ }
+
(void) c;
(void) prop_id;
@@ -274,6 +283,10 @@ void koto_playback_engine_apply_configuration_state(
}
void koto_playback_engine_backwards(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
+ return;
+ }
+
KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
@@ -284,6 +297,7 @@ void koto_playback_engine_backwards(KotoPlaybackEngine * self) {
return;
}
+ koto_playback_engine_set_track_playback_position(self, 0); // Reset the current track position to 0
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_previous(playlist), FALSE);
}
@@ -291,6 +305,7 @@ void koto_playback_engine_handle_current_playlist_changed(
KotoCurrentPlaylist * current_pl,
KotoPlaylist * playlist,
gboolean play_immediately,
+ gboolean play_current,
gpointer user_data
) {
(void) current_pl;
@@ -305,11 +320,29 @@ void koto_playback_engine_handle_current_playlist_changed(
}
if (play_immediately) { // Should play immediately
- koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist), FALSE); // Go to "next" which is the first track
+ gchar * play_uuid = NULL;
+
+ if (play_current) { // Play the current track
+ KotoTrack * track = koto_playlist_get_current_track(playlist); // Get the current track
+
+ if (KOTO_IS_TRACK(track)) {
+ play_uuid = koto_track_get_uuid(track);
+ }
+ }
+
+ if (!koto_utils_string_is_valid(play_uuid)) {
+ play_uuid = koto_playlist_go_to_next(playlist);
+ }
+
+ koto_playback_engine_set_track_by_uuid(self, play_uuid, FALSE); // Go to "next" which is the first track
}
}
void koto_playback_engine_forwards(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
+ return;
+ }
+
KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
@@ -323,16 +356,21 @@ void koto_playback_engine_forwards(KotoPlaybackEngine * self) {
(self->via_config_continue_on_playlist && self->is_playing_specific_track) || // Playing a specific track and wanting to continue on the playlist
(!self->is_playing_specific_track) // Not playing a specific track
) {
+ koto_playback_engine_set_track_playback_position(self, 0); // Reset the current track position to 0
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist), FALSE);
}
}
}
KotoTrack * koto_playback_engine_get_current_track(KotoPlaybackEngine * self) {
- return self->current_track;
+ return KOTO_IS_PLAYBACK_ENGINE(self) ? self->current_track : NULL;
}
gint64 koto_playback_engine_get_duration(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
+ return 0;
+ }
+
guint64 track_duration = koto_track_get_duration(self->current_track); // Get the current track's duration
if (track_duration != 0) { // Have a duration stored
@@ -350,6 +388,10 @@ gint64 koto_playback_engine_get_duration(KotoPlaybackEngine * self) {
}
gdouble koto_playback_engine_get_progress(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine
+ return 0;
+ }
+
gdouble progress = 0.0;
gint64 gstprog = 0;
@@ -360,7 +402,7 @@ gdouble koto_playback_engine_get_progress(KotoPlaybackEngine * self) {
return 0.0;
}
- progress = gstprog / 100000;
+ progress = gstprog / GST_SECOND; // Devide by NS to get seconds
}
return progress;
@@ -371,15 +413,15 @@ GstState koto_playback_engine_get_state(KotoPlaybackEngine * self) {
}
gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine * self) {
- return self->is_repeat_enabled;
+ return KOTO_IS_PLAYBACK_ENGINE(self) ? self->is_repeat_enabled : FALSE;
}
gboolean koto_playback_engine_get_track_shuffle(KotoPlaybackEngine * self) {
- return self->is_shuffle_enabled;
+ return KOTO_IS_PLAYBACK_ENGINE(self) ? self->is_shuffle_enabled : FALSE;
}
gdouble koto_playback_engine_get_volume(KotoPlaybackEngine * self) {
- return self->volume;
+ return KOTO_IS_PLAYBACK_ENGINE(self) ? self->volume : 0;
}
gboolean koto_playback_engine_monitor_changed(
@@ -390,6 +432,10 @@ gboolean koto_playback_engine_monitor_changed(
(void) bus;
KotoPlaybackEngine * self = user_data;
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return FALSE;
+ }
+
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_DURATION_CHANGED: { // Duration changed
koto_playback_engine_tick_duration(self);
@@ -402,6 +448,15 @@ gboolean koto_playback_engine_monitor_changed(
GstState pending_state;
gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state);
+ gboolean possibly_buffered = (old_state == GST_STATE_READY) && (new_state == GST_STATE_PAUSED) && (pending_state == GST_STATE_PLAYING);
+
+ if (possibly_buffered && (self->requested_playback_position != -1)) { // Have a requested playback position
+ koto_playback_engine_set_position(self, self->requested_playback_position); // Set the position
+ self->requested_playback_position = -1;
+ koto_playback_engine_play(self); // Start playback
+ return TRUE;
+ }
+
if (new_state == GST_STATE_PLAYING) { // Now playing
koto_playback_engine_tick_duration(self);
g_signal_emit(self, playback_engine_signals[SIGNAL_IS_PLAYING], 0); // Emit our is playing state signal
@@ -426,6 +481,10 @@ gboolean koto_playback_engine_monitor_changed(
}
void koto_playback_engine_play(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
self->is_playing = TRUE;
gst_element_set_state(self->player, GST_STATE_PLAYING); // Set our state to play
@@ -439,10 +498,19 @@ void koto_playback_engine_play(KotoPlaybackEngine * self) {
g_timeout_add(100, koto_playback_engine_tick_track, self); // Create a 100ms track tick
}
+ if (!self->tick_update_playlist_state) {
+ self->tick_update_playlist_state = TRUE;
+ g_timeout_add(60000, koto_playback_engine_tick_update_playlist_state, self); // Update the current playlist state every minute
+ }
+
koto_update_mpris_playback_state(GST_STATE_PLAYING);
}
void koto_playback_engine_pause(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
self->is_playing = FALSE;
gst_element_change_state(self->player, GST_STATE_CHANGE_PLAYING_TO_PAUSED);
koto_update_mpris_playback_state(GST_STATE_PAUSED);
@@ -452,6 +520,10 @@ void koto_playback_engine_set_position(
KotoPlaybackEngine * self,
int position
) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
gst_element_seek(
self->playbin,
1.0,
@@ -464,6 +536,21 @@ void koto_playback_engine_set_position(
);
}
+void koto_playback_engine_set_track_playback_position(
+ KotoPlaybackEngine * self,
+ guint64 pos
+) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
+ if (!KOTO_IS_TRACK(self->current_track) || self->is_repeat_enabled) { // If we do not have a track or repeating
+ return;
+ }
+
+ koto_track_set_playback_position(self->current_track, pos); // Set the playback position of the track
+}
+
void koto_playback_engine_set_track_repeat(
KotoPlaybackEngine * self,
gboolean enable_repeat
@@ -476,6 +563,10 @@ void koto_playback_engine_set_track_shuffle(
KotoPlaybackEngine * self,
gboolean enable_shuffle
) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist);
if (!KOTO_IS_PLAYLIST(playlist)) { // Don't have a playlist currently
@@ -496,7 +587,7 @@ void koto_playback_engine_set_track_by_uuid(
return;
}
- if (!koto_utils_is_string_valid(track_uuid)) { // If this is not a valid track uuid string
+ if (!koto_utils_string_is_valid(track_uuid)) { // If this is not a valid track uuid string
return;
}
@@ -519,10 +610,8 @@ void koto_playback_engine_set_track_by_uuid(
g_object_set(self->playbin, "uri", gst_filename, NULL);
g_free(gst_filename); // Free the filename
+ self->requested_playback_position = koto_track_get_playback_position(self->current_track); // Set our requested playback position
koto_playback_engine_play(self); // Play the new track
-
- // TODO: Add prior position state setting here, like picking up at a specific part of an audiobook or podcast
- koto_playback_engine_set_position(self, 0);
koto_playback_engine_set_volume(self, self->volume); // Re-enforce our volume on the updated playbin
GVariant * metadata = koto_track_get_metadata_vardict(track); // Get the GVariantBuilder variable dict for the metadata
@@ -540,14 +629,14 @@ void koto_playback_engine_set_track_by_uuid(
GVariant * artist_name_var = g_variant_dict_lookup_value(metadata_dict, "playbackengine:artist", NULL); // Get the GVariant for the name of the artist
gchar * artist_name = g_strdup(g_variant_get_string(artist_name_var, NULL)); // Get the string for the artist name
- gchar * artist_album_combo = koto_utils_is_string_valid(album_name) ? g_strjoin(" - ", artist_name, album_name, NULL) : artist_name; // Join artist and album name separated by " - "
+ gchar * artist_album_combo = koto_utils_string_is_valid(album_name) ? g_strjoin(" - ", artist_name, album_name, NULL) : artist_name; // Join artist and album name separated by " - "
gchar * icon_name = "audio-x-generic-symbolic";
if (g_variant_dict_contains(metadata_dict, "mpris:artUrl")) { // If we have artwork specified
GVariant * art_url_var = g_variant_dict_lookup_value(metadata_dict, "mpris:artUrl", NULL); // Get the GVariant for the art URL
const gchar * art_uri = g_variant_get_string(art_url_var, NULL); // Get the string for the artwork
- icon_name = koto_utils_replace_string_all(g_strdup(art_uri), "file://", "");
+ icon_name = koto_utils_string_replace_all(g_strdup(art_uri), "file://", "");
}
// Super important note: We are not using libnotify directly because the synchronous nature of notify_notification_send seems to result in dbus timeouts
@@ -577,11 +666,19 @@ void koto_playback_engine_set_volume(
KotoPlaybackEngine * self,
gdouble volume
) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
self->volume = volume;
g_object_set(self->playbin, "volume", self->volume, NULL);
}
void koto_playback_engine_stop(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
gst_element_set_state(self->player, GST_STATE_NULL);
GstPad * pad = gst_element_get_static_pad(self->player, "sink"); // Get the static pad of the audio element
@@ -594,16 +691,26 @@ void koto_playback_engine_stop(KotoPlaybackEngine * self) {
}
void koto_playback_engine_toggle(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
if (self->is_playing) { // Currently playing
koto_playback_engine_pause(self); // Pause
} else {
koto_playback_engine_play(self); // Play
}
+
+ koto_current_playlist_save_playlist_state(current_playlist); // Save the playlist state during pause and play in case we are doing that remotely
}
gboolean koto_playback_engine_tick_duration(gpointer user_data) {
KotoPlaybackEngine * self = user_data;
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return FALSE;
+ }
+
if (self->is_playing) { // Is playing
g_signal_emit(self, playback_engine_signals[SIGNAL_TICK_DURATION], 0); // Emit our 1s track tick
} else { // Not playing so exiting timer
@@ -616,23 +723,67 @@ gboolean koto_playback_engine_tick_duration(gpointer user_data) {
gboolean koto_playback_engine_tick_track(gpointer user_data) {
KotoPlaybackEngine * self = user_data;
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return FALSE;
+ }
+
if (self->is_playing) { // Is playing
+ KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist);
+
+ if (KOTO_IS_PLAYLIST(playlist)) {
+ koto_playlist_emit_modified(playlist); // Emit modified on the playlist to update UI in realtime (ish, okay? every 100ms is enough geez)
+ }
+
g_signal_emit(self, playback_engine_signals[SIGNAL_TICK_TRACK], 0); // Emit our 100ms track tick
} else {
self->tick_track_timer_running = FALSE;
}
+ koto_playback_engine_update_track_position(self); // Update track position if needed
+
+ return self->is_playing;
+}
+
+gboolean koto_playback_engine_tick_update_playlist_state(gpointer user_data) {
+ KotoPlaybackEngine * self = user_data;
+
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return FALSE;
+ }
+
+ if (self->is_playing) { // Is playing
+ koto_current_playlist_save_playlist_state(current_playlist); // Save the state of the current playlist
+ } else {
+ self->tick_update_playlist_state = FALSE;
+ }
+
return self->is_playing;
}
void koto_playback_engine_toggle_track_repeat(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
koto_playback_engine_set_track_repeat(self, !self->is_repeat_enabled);
}
void koto_playback_engine_toggle_track_shuffle(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
koto_playback_engine_set_track_shuffle(self, !self->is_shuffle_enabled); // Invert the currently shuffling vale
}
+void koto_playback_engine_update_track_position(KotoPlaybackEngine * self) {
+ if (!KOTO_IS_PLAYBACK_ENGINE(self)) {
+ return;
+ }
+
+ koto_playback_engine_set_track_playback_position(self, (guint64) koto_playback_engine_get_progress(self)); // Set to current progress
+}
+
KotoPlaybackEngine * koto_playback_engine_new() {
return g_object_new(KOTO_TYPE_PLAYBACK_ENGINE, NULL);
}
diff --git a/src/playback/engine.h b/src/playback/engine.h
index 88889fb..ecf9d45 100644
--- a/src/playback/engine.h
+++ b/src/playback/engine.h
@@ -56,6 +56,7 @@ void koto_playback_engine_handle_current_playlist_changed(
KotoCurrentPlaylist * current_pl,
KotoPlaylist * playlist,
gboolean play_immediately,
+ gboolean play_current,
gpointer user_data
);
@@ -94,6 +95,11 @@ void koto_playback_engine_set_position(
int position
);
+void koto_playback_engine_set_track_playback_position(
+ KotoPlaybackEngine * self,
+ guint64 pos
+);
+
void koto_playback_engine_set_track_repeat(
KotoPlaybackEngine * self,
gboolean enable_repeat
@@ -123,6 +129,10 @@ void koto_playback_engine_toggle_track_shuffle(KotoPlaybackEngine * self);
void koto_playback_engine_update_duration(KotoPlaybackEngine * self);
+void koto_playback_engine_update_track_position(KotoPlaybackEngine * self);
+
gboolean koto_playback_engine_tick_duration(gpointer user_data);
gboolean koto_playback_engine_tick_track(gpointer user_data);
+
+gboolean koto_playback_engine_tick_update_playlist_state(gpointer user_data);
diff --git a/src/playback/mimes.c b/src/playback/mimes.c
index eabaecd..8c3f0ea 100644
--- a/src/playback/mimes.c
+++ b/src/playback/mimes.c
@@ -41,7 +41,7 @@ gboolean koto_playback_engine_gst_caps_iter(
g_hash_table_add(supported_mimes_hash, g_strdup(caps_name));
supported_mimes = g_list_prepend(supported_mimes, g_strdup(caps_name));
- supported_mimes = g_list_prepend(supported_mimes, g_strdup(koto_utils_replace_string_all(caps_name, "x-", "")));
+ supported_mimes = g_list_prepend(supported_mimes, g_strdup(koto_utils_string_replace_all(caps_name, "x-", "")));
return TRUE;
}
diff --git a/src/playlist/add-remove-track-popover.c b/src/playlist/add-remove-track-popover.c
index b730769..01c08a6 100644
--- a/src/playlist/add-remove-track-popover.c
+++ b/src/playlist/add-remove-track-popover.c
@@ -150,11 +150,10 @@ void koto_add_remove_track_popover_handle_checkbutton_toggle(
continue; // Skip this
}
- gchar * track_uuid = koto_track_get_uuid(track); // Get the track
-
if (should_add) { // Should be adding
- koto_playlist_add_track_by_uuid(playlist, track_uuid, FALSE, TRUE); // Add the track to the playlist
+ koto_playlist_add_track(playlist, track, FALSE, TRUE); // Add the track to the playlist
} else { // Should be removing
+ gchar * track_uuid = koto_track_get_uuid(track); // Get the track
koto_playlist_remove_track_by_uuid(playlist, track_uuid); // Remove the track from the playlist
}
}
diff --git a/src/playlist/create-modify-dialog.c b/src/playlist/create-modify-dialog.c
index e8201dd..32c2ee9 100644
--- a/src/playlist/create-modify-dialog.c
+++ b/src/playlist/create-modify-dialog.c
@@ -205,7 +205,7 @@ void koto_create_modify_playlist_dialog_handle_create_click(
}
KotoPlaylist * playlist = NULL;
- gboolean modify_existing_playlist = koto_utils_is_string_valid(self->playlist_uuid);
+ gboolean modify_existing_playlist = koto_utils_string_is_valid(self->playlist_uuid);
if (modify_existing_playlist) { // Modifying an existing playlist
playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->playlist_uuid);
@@ -313,13 +313,14 @@ void koto_create_modify_playlist_dialog_reset(KotoCreateModifyPlaylistDialog * s
gtk_entry_set_placeholder_text(GTK_ENTRY(self->name_entry), "Name of playlist"); // Reset placeholder
gtk_image_set_from_icon_name(GTK_IMAGE(self->playlist_image), "insert-image-symbolic"); // Reset the image
gtk_button_set_label(GTK_BUTTON(self->create_button), "Create");
+ self->playlist_uuid = NULL; // Reset playlist UUID
}
void koto_create_modify_playlist_dialog_set_playlist_uuid(
KotoCreateModifyPlaylistDialog * self,
gchar * playlist_uuid
) {
- if (!koto_utils_is_string_valid(playlist_uuid)) { // Not a valid playlist UUID string
+ if (!koto_utils_string_is_valid(playlist_uuid)) { // Not a valid playlist UUID string
return;
}
@@ -335,7 +336,7 @@ void koto_create_modify_playlist_dialog_set_playlist_uuid(
gchar * art = koto_playlist_get_artwork(playlist);
- if (!koto_utils_is_string_valid(art)) { // If art is not defined
+ if (!koto_utils_string_is_valid(art)) { // If art is not defined
gtk_image_set_from_icon_name(GTK_IMAGE(self->playlist_image), "insert-image-symbolic"); // Reset the image
} else {
gtk_image_set_from_file(GTK_IMAGE(self->playlist_image), art);
diff --git a/src/playlist/current.c b/src/playlist/current.c
index 4b6a980..ba27fa5 100644
--- a/src/playlist/current.c
+++ b/src/playlist/current.c
@@ -58,8 +58,9 @@ static void koto_current_playlist_class_init(KotoCurrentPlaylistClass * c) {
NULL,
NULL,
G_TYPE_NONE,
- 2,
+ 3,
KOTO_TYPE_PLAYLIST,
+ G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN
);
}
@@ -71,13 +72,26 @@ static void koto_current_playlist_init(KotoCurrentPlaylist * self) {
}
KotoPlaylist * koto_current_playlist_get_playlist(KotoCurrentPlaylist * self) {
- return self->current_playlist;
+ return KOTO_IS_CURRENT_PLAYLIST(self) ? self->current_playlist : NULL;
+}
+
+void koto_current_playlist_save_playlist_state(KotoCurrentPlaylist * self) {
+ if (!KOTO_IS_CURRENT_PLAYLIST(self)) { // Not a CurrentPlaylist
+ return;
+ }
+
+ if (!KOTO_IS_PLAYLIST(self->current_playlist)) { // No playlist
+ return;
+ }
+
+ koto_playlist_save_current_playback_state(self->current_playlist); // Save the current playlist state
}
void koto_current_playlist_set_playlist(
KotoCurrentPlaylist * self,
KotoPlaylist * playlist,
- gboolean play_immediately
+ gboolean play_immediately,
+ gboolean play_current
) {
if (!KOTO_IS_CURRENT_PLAYLIST(self)) {
return;
@@ -88,6 +102,8 @@ void koto_current_playlist_set_playlist(
}
if (KOTO_IS_PLAYLIST(self->current_playlist)) {
+ koto_current_playlist_save_playlist_state(self); // Save the current playlist state if needed
+
gboolean * is_temp = FALSE;
g_object_get(self->current_playlist, "ephemeral", &is_temp, NULL); // Get the current ephemeral value
@@ -101,15 +117,14 @@ void koto_current_playlist_set_playlist(
}
self->current_playlist = playlist;
- // TODO: Saved state
- koto_playlist_set_position(self->current_playlist, -1); // Reset our position, use -1 since "next" song is then 0
g_object_ref(playlist); // Increment the reference
g_signal_emit(
self,
signals[SIGNAL_PLAYLIST_CHANGED],
0,
playlist,
- play_immediately
+ play_immediately,
+ play_current
);
}
diff --git a/src/playlist/current.h b/src/playlist/current.h
index 09409e5..57b8cd2 100644
--- a/src/playlist/current.h
+++ b/src/playlist/current.h
@@ -43,10 +43,13 @@ KotoCurrentPlaylist * koto_current_playlist_new();
KotoPlaylist * koto_current_playlist_get_playlist(KotoCurrentPlaylist * self);
+void koto_current_playlist_save_playlist_state(KotoCurrentPlaylist * self);
+
void koto_current_playlist_set_playlist(
KotoCurrentPlaylist * self,
KotoPlaylist * playlist,
- gboolean play_immediately
+ gboolean play_immediately,
+ gboolean play_current
);
G_END_DECLS
diff --git a/src/playlist/playlist.c b/src/playlist/playlist.c
index 58a99d8..7e3bb39 100644
--- a/src/playlist/playlist.c
+++ b/src/playlist/playlist.c
@@ -30,6 +30,7 @@ enum {
PROP_0,
PROP_UUID,
PROP_NAME,
+ PROP_ALBUM_UUID,
PROP_ART_PATH,
PROP_EPHEMERAL,
PROP_IS_SHUFFLE_ENABLED,
@@ -49,10 +50,11 @@ struct _KotoPlaylist {
gchar * uuid;
gchar * name;
gchar * art_path;
+ gchar * album_uuid;
gint current_position;
gchar * current_uuid;
- KotoPreferredModelType model;
+ KotoPreferredPlaylistSortType model;
gboolean ephemeral;
gboolean is_shuffle_enabled;
@@ -127,6 +129,14 @@ static void koto_playlist_class_init(KotoPlaylistClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
+ props[PROP_ALBUM_UUID] = g_param_spec_string(
+ "album-uuid",
+ "UUID of an Album associated with this Playlist",
+ "UUID of an Album associated with this Playlist. Useful for Audiobooks.",
+ NULL,
+ G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
+ );
+
props[PROP_ART_PATH] = g_param_spec_string(
"art-path",
"Path to any associated artwork of the Playlist",
@@ -219,6 +229,9 @@ static void koto_playlist_get_property(
case PROP_NAME:
g_value_set_string(val, self->name);
break;
+ case PROP_ALBUM_UUID:
+ g_value_set_string(val, self->album_uuid);
+ break;
case PROP_ART_PATH:
g_value_set_string(val, self->art_path);
break;
@@ -249,6 +262,9 @@ static void koto_playlist_set_property(
case PROP_NAME:
koto_playlist_set_name(self, g_value_get_string(val));
break;
+ case PROP_ALBUM_UUID:
+ koto_playlist_set_album_uuid(self, g_value_get_string(val));
+ break;
case PROP_ART_PATH:
koto_playlist_set_artwork(self, g_value_get_string(val));
break;
@@ -267,7 +283,7 @@ static void koto_playlist_set_property(
static void koto_playlist_init(KotoPlaylist * self) {
self->current_position = -1; // Default to -1 so first time incrementing puts it at 0
self->current_uuid = NULL;
- self->model = KOTO_PREFERRED_MODEL_TYPE_DEFAULT; // Default to default model
+ self->model = KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT; // Default to default model
self->is_shuffle_enabled = FALSE;
self->ephemeral = FALSE;
self->finalized = FALSE;
@@ -282,7 +298,17 @@ void koto_playlist_add_to_played_tracks(
KotoPlaylist * self,
gchar * uuid
) {
- if (g_queue_index(self->played_tracks, uuid) != -1) { // Already added
+ if (!KOTO_IS_PLAYLIST(self)) {
+ return;
+ }
+
+ KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, uuid);
+
+ if (!KOTO_IS_TRACK(track)) {
+ return;
+ }
+
+ if (koto_playlist_get_position_of_track(self, track) != -1) { // Already added
return;
}
@@ -295,32 +321,22 @@ void koto_playlist_add_track(
gboolean current,
gboolean commit_to_table
) {
- koto_playlist_add_track_by_uuid(self, koto_track_get_uuid(track), current, commit_to_table);
-}
-
-void koto_playlist_add_track_by_uuid(
- KotoPlaylist * self,
- gchar * uuid,
- gboolean current,
- gboolean commit_to_table
-) {
- KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, uuid); // Get the track
+ if (!KOTO_IS_PLAYLIST(self)) {
+ return;
+ }
if (!KOTO_IS_TRACK(track)) {
return;
}
- GList * found_tracks_uuids = g_queue_find_custom(self->tracks, uuid, koto_playlist_compare_track_uuids);
+ gchar * track_uuid = koto_track_get_uuid(track);
- if (found_tracks_uuids != NULL) { // Is somewhere in the tracks already
- g_list_free(found_tracks_uuids);
+ if (koto_playlist_get_position_of_track(self, track) != -1) { // Found already
return;
}
- g_list_free(found_tracks_uuids);
-
- g_queue_push_tail(self->tracks, uuid); // Prepend the UUID to the tracks
- g_queue_push_tail(self->sorted_tracks, uuid); // Also add to our sorted tracks
+ g_queue_push_tail(self->tracks, track_uuid); // Prepend the UUID to the tracks
+ g_queue_push_tail(self->sorted_tracks, track_uuid); // Also add to our sorted tracks
g_list_store_append(self->store, track); // Add to the store
if (self->finalized) { // Is already finalized
@@ -328,24 +344,25 @@ void koto_playlist_add_track_by_uuid(
}
if (commit_to_table) {
- koto_track_save_to_playlist(track, self->uuid, (g_strcmp0(self->current_uuid, uuid) == 0) ? 1 : 0); // Call to save the playlist to the track
+ koto_track_save_to_playlist(track, self->uuid); // Call to save the playlist to the track
}
if (current && (g_queue_get_length(self->tracks) > 1)) { // Is current and NOT the first item
- self->current_uuid = uuid; // Mark this as current UUID
+ self->current_uuid = track_uuid; // Mark this as current UUID
+ koto_playlist_apply_model(self, self->model); // Re-apply our model to enforce mass sort
}
g_signal_emit(
self,
playlist_signals[SIGNAL_TRACK_ADDED],
0,
- uuid
+ track_uuid
);
}
void koto_playlist_apply_model(
KotoPlaylist * self,
- KotoPreferredModelType preferred_model
+ KotoPreferredPlaylistSortType preferred_model
) {
GList * sort_user_data = NULL;
@@ -359,68 +376,97 @@ void koto_playlist_apply_model(
}
void koto_playlist_commit(KotoPlaylist * self) {
- if (self->ephemeral) { // Temporary playlist
+ if (!KOTO_IS_PLAYLIST(self)) {
return;
}
+ gboolean album_acceptable_type = FALSE;
+
+ if (koto_utils_string_is_valid(self->album_uuid)) {
+ KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid); // Get the album
+
+ if (KOTO_IS_ALBUM(album)) { // Got the album
+ KotoLibraryType album_type = koto_album_get_lib_type(album);
+ album_acceptable_type = ((album_type == KOTO_LIBRARY_TYPE_AUDIOBOOK) || (album_type == KOTO_LIBRARY_TYPE_PODCAST));
+ }
+ }
+
+ if (self->ephemeral && !album_acceptable_type) { // Is a temporary playlist and NOT an acceptable type
+ return;
+ }
+
+ guint64 track_playback_pos = 0;
+
+ if (koto_utils_string_is_valid(self->current_uuid)) { // Have a track UUID
+ KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); // Get the track
+
+ if (KOTO_IS_TRACK(track)) { // Is a track
+ track_playback_pos = koto_track_get_playback_position(track); // Get the track playback position and set it to our track_playback_pos
+ }
+ }
+
gchar * commit_op = g_strdup_printf(
- "INSERT INTO playlist_meta(id, name, art_path, preferred_model)"
- "VALUES('%s', quote(\"%s\"), quote(\"%s\"), 0)"
- "ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;",
+ "INSERT INTO playlist_meta(id, name, art_path, preferred_model, album_id, track_id, playback_position_of_track)"
+ "VALUES('%s', quote(\"%s\"), quote(\"%s\"), %li, '%s', '%s', '%li')"
+ "ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path, preferred_model=excluded.preferred_model, album_id=excluded.album_id, track_id=excluded.track_id;",
self->uuid,
- self->name,
- self->art_path
+ koto_utils_string_get_valid(self->name),
+ koto_utils_string_get_valid(self->art_path),
+ (guint64) self->model,
+ koto_utils_string_get_valid(self->album_uuid),
+ koto_utils_string_get_valid(self->current_uuid),
+ track_playback_pos
);
new_transaction(commit_op, "Failed to save playlist", FALSE);
}
-void koto_playlist_commit_tracks(
- gpointer data,
- gpointer user_data
-) {
- KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, data); // Get the track
-
- if (track == NULL) { // Not a track
- KotoPlaylist * self = user_data;
- gchar * playlist_uuid = self->uuid; // Get the playlist UUID
-
- gchar * current_track = g_queue_peek_nth(self->tracks, self->current_position); // Get the UUID of the current track
- //koto_track_save_to_playlist(track, playlist_uuid, (data == current_track) ? 1 : 0); // Call to save the playlist to the track
- g_free(playlist_uuid);
- g_free(current_track);
+void koto_playlist_emit_modified(KotoPlaylist * self) {
+ if (!KOTO_IS_PLAYLIST(self)) {
+ return;
}
-}
-gint koto_playlist_compare_track_uuids(
- gconstpointer a,
- gconstpointer b
-) {
- return g_strcmp0(a, b);
+ g_signal_emit(
+ self,
+ playlist_signals[SIGNAL_MODIFIED],
+ 0
+ );
}
gchar * koto_playlist_get_artwork(KotoPlaylist * self) {
- return (self->art_path == NULL) ? NULL : g_strdup(self->art_path); // Return a duplicate of our art path
+ return (KOTO_IS_PLAYLIST(self) && koto_utils_string_is_valid(self->art_path)) ? g_strdup(self->art_path) : NULL; // Return a duplicate of our art path
}
-KotoPreferredModelType koto_playlist_get_current_model(KotoPlaylist * self) {
- return self->model;
+KotoPreferredPlaylistSortType koto_playlist_get_current_model(KotoPlaylist * self) {
+ return KOTO_IS_PLAYLIST(self) ? self->model : KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT;
}
-guint koto_playlist_get_current_position(KotoPlaylist * self) {
- return self->current_position;
+gint koto_playlist_get_current_position(KotoPlaylist * self) {
+ return KOTO_IS_PLAYLIST(self) ? self->current_position : -1;
+}
+
+KotoTrack * koto_playlist_get_current_track(KotoPlaylist * self) {
+ if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist
+ return NULL;
+ }
+
+ return koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); // Get the current track
}
gboolean koto_playlist_get_is_finalized(KotoPlaylist * self) {
- return self->finalized;
+ return KOTO_IS_PLAYLIST(self) ? self->finalized : FALSE;
+}
+
+gboolean koto_playlist_get_is_hidden(KotoPlaylist * self) {
+ return (self->ephemeral && koto_utils_string_is_valid(self->album_uuid)); // If the playlist is ephemeral and associated with an album, it should be hidden from nav
}
guint koto_playlist_get_length(KotoPlaylist * self) {
- return g_queue_get_length(self->tracks); // Get the length of the tracks
+ return KOTO_IS_PLAYLIST(self) ? g_queue_get_length(self->tracks) : 0; // Get the length of the tracks
}
gchar * koto_playlist_get_name(KotoPlaylist * self) {
- return (self->name == NULL) ? NULL : g_strdup(self->name);
+ return (KOTO_IS_PLAYLIST(self) && koto_utils_string_is_valid(self->name)) ? g_strdup(self->name) : NULL;
}
gint koto_playlist_get_position_of_track(
@@ -501,11 +547,15 @@ gchar * koto_playlist_go_to_next(KotoPlaylist * self) {
if (self->is_shuffle_enabled) { // Shuffling enabled
gchar * random_track_uuid = koto_playlist_get_random_track(self); // Get a random track
+ self->current_uuid = random_track_uuid; // Update our current UUID
koto_playlist_add_to_played_tracks(self, random_track_uuid);
+
+ koto_playlist_emit_modified(self);
+
return random_track_uuid;
}
- if (!koto_utils_is_string_valid(self->current_uuid)) { // No valid UUID yet
+ if (!koto_utils_string_is_valid(self->current_uuid)) { // No valid UUID yet
self->current_position++;
} else { // Have a UUID currently
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid);
@@ -525,6 +575,7 @@ gchar * koto_playlist_go_to_next(KotoPlaylist * self) {
self->current_uuid = g_queue_peek_nth(self->sorted_tracks, self->current_position);
koto_playlist_add_to_played_tracks(self, self->current_uuid);
+ koto_playlist_emit_modified(self);
return self->current_uuid;
}
@@ -534,7 +585,7 @@ gchar * koto_playlist_go_to_previous(KotoPlaylist * self) {
return koto_playlist_get_random_track(self); // Get a random track
}
- if (!koto_utils_is_string_valid(self->current_uuid)) { // No valid UUID
+ if (!koto_utils_string_is_valid(self->current_uuid)) { // No valid UUID
return NULL;
}
@@ -553,11 +604,13 @@ gchar * koto_playlist_go_to_previous(KotoPlaylist * self) {
self->current_position = pos_of_song - 1; // Decrement our position based on position of song
self->current_uuid = g_queue_peek_nth(self->sorted_tracks, self->current_position);
+ koto_playlist_emit_modified(self);
+
return self->current_uuid;
}
void koto_playlist_mark_as_finalized(KotoPlaylist * self) {
- if (self->finalized) { // Already finalized
+ if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist
return;
}
@@ -592,11 +645,11 @@ gint koto_playlist_model_sort_by_track(
GList * ptr_list = data_list;
KotoPlaylist * self = g_list_nth_data(ptr_list, 0); // First item in the GPtrArray is a pointer to our playlist
- KotoPreferredModelType model = GPOINTER_TO_UINT(g_list_nth_data(ptr_list, 1)); // Second item in the GPtrArray is a pointer to our KotoPreferredModelType
+ KotoPreferredPlaylistSortType model = GPOINTER_TO_UINT(g_list_nth_data(ptr_list, 1)); // Second item in the GPtrArray is a pointer to our KotoPreferredPlaylistSortType
if (
- (model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) || // Newest first model
- (model == KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST) // Oldest first
+ (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT) || // Newest first model
+ (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST) // Oldest first
) {
gint first_track_pos = g_queue_index(self->tracks, koto_track_get_uuid(first_track));
gint second_track_pos = g_queue_index(self->tracks, koto_track_get_uuid(second_track));
@@ -609,14 +662,14 @@ gint koto_playlist_model_sort_by_track(
return -1;
}
- if (model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) { // Newest first
+ if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT) { // Newest first
return (first_track_pos < second_track_pos) ? 1 : -1; // Display first at end, not beginning
} else {
return (first_track_pos < second_track_pos) ? -1 : 1; // Display at beginning, not end
}
}
- if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Sort by album name
+ if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM) { // Sort by album name
gchar * first_album_uuid = NULL;
gchar * second_album_uuid = NULL;
@@ -653,7 +706,7 @@ gint koto_playlist_model_sort_by_track(
return g_utf8_collate(koto_album_get_name(first_album), koto_album_get_name(second_album));
}
- if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Sort by artist name
+ if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST) { // Sort by artist name
gchar * first_artist_uuid = NULL;
gchar * second_artist_uuid = NULL;
@@ -682,31 +735,27 @@ gint koto_playlist_model_sort_by_track(
}
return g_utf8_collate(koto_artist_get_name(first_artist), koto_artist_get_name(second_artist));
- }
+ } else if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME) { // Track name
+ return g_utf8_collate(koto_track_get_name(first_track), koto_track_get_name(second_track));
+ } else if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS) { // Sort by track position
- if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Track name
- gchar * first_track_name = NULL;
- gchar * second_track_name = NULL;
+ guint first_track_disc = koto_track_get_disc_number(first_track);
+ guint second_track_disc = koto_track_get_disc_number(second_track);
- g_object_get(
- first_track,
- "parsed-name",
- &first_track_name,
- NULL
- );
+ if (first_track_disc < second_track_disc) { // First is in an earlier CD / Disc / Part
+ return -1;
+ } else if (first_track_disc > second_track_disc) { // First is in later CD / Disc / Part
+ return 1;
+ } // Continue on to same CD
- g_object_get(
- second_track,
- "parsed-name",
- &second_track_name,
- NULL
- );
+ guint64 first_track_pos = koto_track_get_position(first_track);
+ guint64 second_track_pos = koto_track_get_position(second_track);
- gint ret = g_utf8_collate(first_track_name, second_track_name);
- g_free(first_track_name);
- g_free(second_track_name);
-
- return ret;
+ if (first_track_pos < second_track_pos) {
+ return -1;
+ } else if (first_track_pos > second_track_pos) {
+ return 1;
+ }
}
return 0;
@@ -761,10 +810,73 @@ void koto_playlist_remove_track_by_uuid(
);
}
+void koto_playlist_save_current_playback_state(KotoPlaylist * self) {
+ if (!KOTO_IS_PLAYLIST(self)) {
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(self->album_uuid)) { // No album associated with this playlist
+ return;
+ }
+
+ KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid); // Get the album
+
+ if (!KOTO_IS_ALBUM(album)) { // Failed to get the album
+ return;
+ }
+
+ KotoLibraryType album_type = koto_album_get_lib_type(album);
+
+ if ((album_type == KOTO_LIBRARY_TYPE_MUSIC) || (album_type == KOTO_LIBRARY_TYPE_UNKNOWN)) { // Not an Audiobook or Podcast
+ return;
+ }
+
+ KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); // Get the track
+
+ if (!KOTO_IS_TRACK(track) || !koto_utils_string_is_valid(self->current_uuid)) { // If we don't have a track we are currently playing
+ gchar * commit_op = g_strdup_printf("UPDATE playlist_meta SET track_id='', playback_position_of_track=0 WHERE id='%s';", self->uuid);
+ new_transaction(commit_op, "Failed to update our playlist meta", FALSE);
+ return;
+ }
+
+ gchar * commit_op = g_strdup_printf(
+ "UPDATE playlist_meta SET track_id='%s', playback_position_of_track=%li WHERE id='%s';",
+ koto_utils_string_get_valid(self->current_uuid),
+ koto_track_get_playback_position(track),
+ self->uuid
+ );
+
+ new_transaction(commit_op, "Failed to update our playlist state", FALSE);
+ koto_playlist_emit_modified(self);
+}
+
+void koto_playlist_set_album_uuid(
+ KotoPlaylist * self,
+ const gchar * album_uuid
+) {
+ if (!KOTO_IS_PLAYLIST(self)) {
+ return;
+ }
+
+ if (!koto_utils_string_is_valid(album_uuid)) {
+ return;
+ }
+
+ self->album_uuid = g_strdup(album_uuid); // Update the album UUID
+
+ if (self->finalized) { // Has already been loaded
+ koto_playlist_emit_modified(self);
+ }
+}
+
void koto_playlist_set_artwork(
KotoPlaylist * self,
const gchar * path
) {
+ if (!KOTO_IS_PLAYLIST(self)) {
+ return;
+ }
+
if (path == NULL) {
return;
}
@@ -792,11 +904,7 @@ void koto_playlist_set_artwork(
self->art_path = g_strdup(path); // Update our art path to a duplicate of provided path
if (self->finalized) { // Has already been loaded
- g_signal_emit(
- self,
- playlist_signals[SIGNAL_MODIFIED],
- 0
- );
+ koto_playlist_emit_modified(self);
}
free_cookie:
@@ -818,11 +926,7 @@ void koto_playlist_set_name(
self->name = g_strdup(name);
if (self->finalized) { // Has already been loaded
- g_signal_emit(
- self,
- playlist_signals[SIGNAL_MODIFIED],
- 0
- );
+ koto_playlist_emit_modified(self);
}
}
@@ -830,19 +934,39 @@ void koto_playlist_set_position(
KotoPlaylist * self,
gint position
) {
+ if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist
+ return;
+ }
+
self->current_position = position;
+
+ if (self->finalized) { // Has already been loaded
+ koto_playlist_emit_modified(self);
+ }
}
void koto_playlist_set_track_as_current(
KotoPlaylist * self,
gchar * track_uuid
) {
- gint position_of_track = g_queue_index(self->sorted_tracks, track_uuid); // Get the position of the UUID in our tracks
-
- if (position_of_track != -1) { // In tracks
- self->current_uuid = track_uuid;
- self->current_position = position_of_track;
+ if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist
+ return;
}
+
+ KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track
+
+ if (!KOTO_IS_TRACK(track)) { // Not a track
+ return;
+ }
+
+ gint position_of_track = koto_playlist_get_position_of_track(self, track);
+
+ if (position_of_track == -1) {
+ return;
+ }
+
+ self->current_uuid = track_uuid;
+ self->current_position = position_of_track; // Set accurate position
}
void koto_playlist_set_uuid(
diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h
index 997aa54..69f708e 100644
--- a/src/playlist/playlist.h
+++ b/src/playlist/playlist.h
@@ -23,12 +23,13 @@
G_BEGIN_DECLS
typedef enum {
- KOTO_PREFERRED_MODEL_TYPE_DEFAULT, // Considered to be newest first
- KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST,
- KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM,
- KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST,
- KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME
-} KotoPreferredModelType;
+ KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT, // Considered to be newest first
+ KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST,
+ KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM,
+ KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST,
+ KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME,
+ KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS
+} KotoPreferredPlaylistSortType;
/**
* Type Definition
@@ -64,40 +65,29 @@ void koto_playlist_add_track(
gboolean commit_to_table
);
-void koto_playlist_add_track_by_uuid(
- KotoPlaylist * self,
- gchar * uuid,
- gboolean current,
- gboolean commit_to_table
-);
-
void koto_playlist_apply_model(
KotoPlaylist * self,
- KotoPreferredModelType preferred_model
+ KotoPreferredPlaylistSortType preferred_model
);
void koto_playlist_commit(KotoPlaylist * self);
-void koto_playlist_commit_tracks(
- gpointer data,
- gpointer user_data
-);
-
-gint koto_playlist_compare_track_uuids(
- gconstpointer a,
- gconstpointer b
-);
+void koto_playlist_emit_modified(KotoPlaylist * self);
gchar * koto_playlist_get_artwork(KotoPlaylist * self);
-KotoPreferredModelType koto_playlist_get_current_model(KotoPlaylist * self);
+KotoPreferredPlaylistSortType koto_playlist_get_current_model(KotoPlaylist * self);
-guint koto_playlist_get_current_position(KotoPlaylist * self);
+gint koto_playlist_get_current_position(KotoPlaylist * self);
+
+KotoTrack * koto_playlist_get_current_track(KotoPlaylist * self);
guint koto_playlist_get_length(KotoPlaylist * self);
gboolean koto_playlist_get_is_finalized(KotoPlaylist * self);
+gboolean koto_playlist_get_is_hidden(KotoPlaylist * self);
+
gchar * koto_playlist_get_name(KotoPlaylist * self);
gint koto_playlist_get_position_of_track(
@@ -139,12 +129,17 @@ void koto_playlist_remove_track_by_uuid(
gchar * uuid
);
+void koto_playlist_set_album_uuid(
+ KotoPlaylist * self,
+ const gchar * album_uuid
+);
+
void koto_playlist_set_artwork(
KotoPlaylist * self,
const gchar * path
);
-void koto_playlist_save_state(KotoPlaylist * self);
+void koto_playlist_save_current_playback_state(KotoPlaylist * self);
void koto_playlist_set_name(
KotoPlaylist * self,
diff --git a/theme/_disc-view.scss b/theme/_disc-view.scss
index 2b613e4..dc3a487 100644
--- a/theme/_disc-view.scss
+++ b/theme/_disc-view.scss
@@ -10,21 +10,5 @@
color: $text-color-faded;
margin: 10px 0;
}
-
- & .track-list {
- & > row {
- &:not(:active):not(:selected) { // Neither active nor selected, see gtk overrides
- color: $text-color-bright;
-
- &:nth-child(odd):not(:hover) {
- background-color: $bg-primary;
- }
-
- &:nth-child(even), &:hover {
- background-color: $bg-secondary;
- }
- }
- }
- }
}
}
\ No newline at end of file
diff --git a/theme/_main.scss b/theme/_main.scss
index 7f5234f..a02659b 100644
--- a/theme/_main.scss
+++ b/theme/_main.scss
@@ -1,6 +1,12 @@
+@import 'components/album-info';
+@import 'components/audiobook-view';
+@import 'components/badge';
@import 'components/cover-art-button';
@import 'components/gtk-overrides';
+@import 'components/track-list';
@import 'components/track-table';
+@import 'components/writer-page';
+@import 'pages/audiobook-library';
@import 'pages/artist-view';
@import 'pages/music-local';
@import 'pages/playlist-page';
@@ -39,7 +45,8 @@ window {
// All the classes we want consistent padding applied to for its primary content
.artist-view-content, // Has the albums
- .playlist-page { // Individual playlists
+ .playlist-page, // Individual playlists
+ .writer-page { // Writer page in Audiobook
padding: $itempadding;
}
}
diff --git a/theme/_player-bar.scss b/theme/_player-bar.scss
index 99e9338..9c4f1ba 100644
--- a/theme/_player-bar.scss
+++ b/theme/_player-bar.scss
@@ -46,4 +46,11 @@
}
}
}
+
+ .playerbar-secondary-controls { // Secondary controls
+ label { // Inner playback position label
+ font-size: large;
+ margin-right: $halvedpadding;
+ }
+ }
}
diff --git a/theme/_vars.scss b/theme/_vars.scss
index 28ea9b1..2bf2ba5 100644
--- a/theme/_vars.scss
+++ b/theme/_vars.scss
@@ -5,4 +5,5 @@ $palewhite: #cccccc;
$red : #FF4652;
$itempadding: 40px;
-$halvedpadding: $itempadding / 2;
\ No newline at end of file
+$halvedpadding: $itempadding / 2;
+$quarterpadding: $itempadding / 4;
\ No newline at end of file
diff --git a/theme/components/_album-info.scss b/theme/components/_album-info.scss
new file mode 100644
index 0000000..f0c646c
--- /dev/null
+++ b/theme/components/_album-info.scss
@@ -0,0 +1,30 @@
+// This file contain the styling for the Album Info section
+
+.album-info {
+ .album-description,
+ .album-narrator,
+ .album-title-year-combo,
+ .genres-tag-list {
+ margin-bottom: $quarterpadding
+ }
+
+ .album-title-year-combo {
+ padding-top: $halvedpadding;
+ }
+
+ .album-title { // Title of album
+ color: $text-color-faded;
+ font-size: 2.5em;
+ font-weight: bold;
+ }
+
+ .album-year {
+ margin-left: $halvedpadding;
+ }
+
+ .genres-tag-list { // Genres Tag List
+ & .label-badge:not(:last-child) {
+ margin-right: $halvedpadding;
+ }
+ }
+}
\ No newline at end of file
diff --git a/theme/components/_audiobook-view.scss b/theme/components/_audiobook-view.scss
new file mode 100644
index 0000000..dafa68a
--- /dev/null
+++ b/theme/components/_audiobook-view.scss
@@ -0,0 +1,34 @@
+// This is the styling for the Audiobook VIew
+
+@import '../vars';
+
+.audiobook-view {
+ .side-info { // Side Info
+ margin-right: $halvedpadding;
+
+ button,
+ image {
+ margin-bottom: $halvedpadding;
+ }
+
+ button { // Play / Continue Playback button
+ font-size: large;
+ font-weight: bold;
+ }
+
+ & > label {
+ font-size: large;
+
+ &:last-child {
+ margin-bottom: $halvedpadding;
+ }
+ }
+ }
+
+ .chapters-label { // Chapters label after album info
+ color: $text-color-faded;
+ font-size: x-large;
+ font-weight: bold;
+ padding: $halvedpadding 0; // Top / bottom padding
+ }
+}
\ No newline at end of file
diff --git a/theme/components/_badge.scss b/theme/components/_badge.scss
new file mode 100644
index 0000000..1f28024
--- /dev/null
+++ b/theme/components/_badge.scss
@@ -0,0 +1,12 @@
+ // This file contains the styling for our label badge
+
+@import '../vars';
+
+.label-badge {
+ color: $text-color-faded;
+ font-size: large;
+ font-weight: 900;
+ background-color: $bg-secondary;
+ border-radius: 10px;
+ padding: 5px 20px;
+}
\ No newline at end of file
diff --git a/theme/components/_track-list.scss b/theme/components/_track-list.scss
new file mode 100644
index 0000000..217089b
--- /dev/null
+++ b/theme/components/_track-list.scss
@@ -0,0 +1,19 @@
+// Track List styling
+
+@import '../vars';
+
+.track-list {
+ & > row {
+ &:not(:active):not(:selected) { // Neither active nor selected, see gtk overrides
+ color: $text-color-bright;
+
+ &:nth-child(odd):not(:hover) {
+ background-color: $bg-primary;
+ }
+
+ &:nth-child(even), &:hover {
+ background-color: $bg-secondary;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/theme/components/_writer-page.scss b/theme/components/_writer-page.scss
new file mode 100644
index 0000000..c37f3cf
--- /dev/null
+++ b/theme/components/_writer-page.scss
@@ -0,0 +1,12 @@
+// This is the styling for the writer page
+
+@import '../vars';
+
+.writer-page {
+ .writer-header { // Our writer / artist header label
+ color: $text-color-faded;
+ font-size: 4em;
+ font-weight: bold;
+ padding-bottom: $itempadding;
+ }
+}
\ No newline at end of file
diff --git a/theme/pages/_audiobook-library.scss b/theme/pages/_audiobook-library.scss
new file mode 100644
index 0000000..6de0139
--- /dev/null
+++ b/theme/pages/_audiobook-library.scss
@@ -0,0 +1,36 @@
+// This file contains the styling for our Audiobook Library
+
+@import '../vars';
+
+.audiobook-library { // Library page
+ .genres-banner { // Banner for genres list
+ .large-banner { // Large banner with art for each genre
+ padding: $itempadding;
+
+ .audiobook-genre-button { // Genre buttons
+ .koto-button {
+ font-size: 2em;
+ margin: 0.5em;
+ }
+ }
+ }
+ }
+
+ .writers-button-flow { // Flowbox of buttons for writers
+ padding: 0 $itempadding; // Horizontal padding of our standard item padding
+
+ flowboxchild {
+ padding: 0;
+
+ &:nth-child(even) {
+ margin: 0 0.5em;
+ }
+
+ .writer-button { // Writer button
+ color: $text-color-bright;
+ font-size: 1.4em;
+ background-color: $bg-secondary;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/theme/pages/_music-local.scss b/theme/pages/_music-local.scss
index ac257fd..de5cda9 100644
--- a/theme/pages/_music-local.scss
+++ b/theme/pages/_music-local.scss
@@ -21,14 +21,6 @@
& > overlay {
margin-right: $itempadding;
}
-
- & > box {
- & > label {
- font-size: xx-large;
- font-weight: 900;
- padding: $halvedpadding 0;
- }
- }
}
}
}