511 lines
13 KiB
C
511 lines
13 KiB
C
|
/* 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 "../config/config.h"
|
||
|
#include "../koto-utils.h"
|
||
|
#include "structs.h"
|
||
|
|
||
|
extern KotoConfig * config;
|
||
|
extern GVolumeMonitor * volume_monitor;
|
||
|
|
||
|
enum {
|
||
|
PROP_0,
|
||
|
PROP_TYPE,
|
||
|
PROP_UUID,
|
||
|
PROP_STORAGE_UUID,
|
||
|
PROP_CONSTRUCTION_PATH,
|
||
|
PROP_NAME,
|
||
|
N_PROPERTIES
|
||
|
};
|
||
|
|
||
|
static GParamSpec * props[N_PROPERTIES] = {
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
SIGNAL_NOW_AVAILABLE,
|
||
|
SIGNAL_NOW_UNAVAILABLE,
|
||
|
N_SIGNALS
|
||
|
};
|
||
|
|
||
|
static guint library_signals[N_SIGNALS] = {
|
||
|
0
|
||
|
};
|
||
|
|
||
|
struct _KotoLibrary {
|
||
|
GObject parent_instance;
|
||
|
gchar * uuid;
|
||
|
|
||
|
KotoLibraryType type;
|
||
|
gchar * directory;
|
||
|
gchar * storage_uuid;
|
||
|
|
||
|
GMount * mount;
|
||
|
gulong mount_unmounted_handler;
|
||
|
gchar * mount_path;
|
||
|
|
||
|
gboolean should_index;
|
||
|
|
||
|
gchar * path;
|
||
|
gchar * relative_path;
|
||
|
gchar * name;
|
||
|
};
|
||
|
|
||
|
struct _KotoLibraryClass {
|
||
|
GObjectClass parent_class;
|
||
|
|
||
|
void (* now_available) (KotoLibrary * library);
|
||
|
|
||
|
void (* now_unavailable) (KotoLibrary * library);
|
||
|
};
|
||
|
|
||
|
G_DEFINE_TYPE(KotoLibrary, koto_library, G_TYPE_OBJECT);
|
||
|
|
||
|
static void koto_library_get_property(
|
||
|
GObject * obj,
|
||
|
guint prop_id,
|
||
|
GValue * val,
|
||
|
GParamSpec * spec
|
||
|
);
|
||
|
|
||
|
static void koto_library_set_property(
|
||
|
GObject * obj,
|
||
|
guint prop_id,
|
||
|
const GValue * val,
|
||
|
GParamSpec * spec
|
||
|
);
|
||
|
|
||
|
static void koto_library_class_init(KotoLibraryClass * c) {
|
||
|
GObjectClass * gobject_class;
|
||
|
|
||
|
gobject_class = G_OBJECT_CLASS(c);
|
||
|
gobject_class->set_property = koto_library_set_property;
|
||
|
gobject_class->get_property = koto_library_get_property;
|
||
|
|
||
|
library_signals[SIGNAL_NOW_AVAILABLE] = g_signal_new(
|
||
|
"now-available",
|
||
|
G_TYPE_FROM_CLASS(gobject_class),
|
||
|
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||
|
G_STRUCT_OFFSET(KotoLibraryClass, now_available),
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
G_TYPE_NONE,
|
||
|
0
|
||
|
);
|
||
|
|
||
|
library_signals[SIGNAL_NOW_UNAVAILABLE] = g_signal_new(
|
||
|
"now-unavailable",
|
||
|
G_TYPE_FROM_CLASS(gobject_class),
|
||
|
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||
|
G_STRUCT_OFFSET(KotoLibraryClass, now_unavailable),
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
G_TYPE_NONE,
|
||
|
0
|
||
|
);
|
||
|
|
||
|
props[PROP_UUID] = g_param_spec_string(
|
||
|
"uuid",
|
||
|
"UUID of Library",
|
||
|
"UUID of Library",
|
||
|
NULL,
|
||
|
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
|
||
|
);
|
||
|
|
||
|
props[PROP_TYPE] = g_param_spec_string(
|
||
|
"type",
|
||
|
"Type of Library",
|
||
|
"Type of Library",
|
||
|
koto_library_type_to_string(KOTO_LIBRARY_TYPE_MUSIC),
|
||
|
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
|
||
|
);
|
||
|
|
||
|
props[PROP_STORAGE_UUID] = g_param_spec_string(
|
||
|
"storage-uuid",
|
||
|
"Storage UUID to associated Mount of Library",
|
||
|
"Storage UUID to associated Mount of Library",
|
||
|
NULL,
|
||
|
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
|
||
|
);
|
||
|
|
||
|
props[PROP_CONSTRUCTION_PATH] = g_param_spec_string(
|
||
|
"construction-path",
|
||
|
"Construction Path",
|
||
|
"Path to this library during construction",
|
||
|
NULL,
|
||
|
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
|
||
|
);
|
||
|
|
||
|
props[PROP_NAME] = g_param_spec_string(
|
||
|
"name",
|
||
|
"Name of the Library",
|
||
|
"Name of the Library",
|
||
|
NULL,
|
||
|
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
|
||
|
);
|
||
|
|
||
|
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||
|
}
|
||
|
|
||
|
static void koto_library_init(KotoLibrary * self) {
|
||
|
(void) self;
|
||
|
}
|
||
|
|
||
|
static void koto_library_get_property(
|
||
|
GObject * obj,
|
||
|
guint prop_id,
|
||
|
GValue * val,
|
||
|
GParamSpec * spec
|
||
|
) {
|
||
|
KotoLibrary * self = KOTO_LIBRARY(obj);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_NAME:
|
||
|
g_value_set_string(val, g_strdup(self->name));
|
||
|
break;
|
||
|
case PROP_UUID:
|
||
|
g_value_set_string(val, self->uuid);
|
||
|
break;
|
||
|
case PROP_STORAGE_UUID:
|
||
|
g_value_set_string(val, self->storage_uuid);
|
||
|
break;
|
||
|
case PROP_TYPE:
|
||
|
g_value_set_string(val, koto_library_type_to_string(self->type));
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void koto_library_set_property(
|
||
|
GObject * obj,
|
||
|
guint prop_id,
|
||
|
const GValue * val,
|
||
|
GParamSpec * spec
|
||
|
) {
|
||
|
KotoLibrary * self = KOTO_LIBRARY(obj);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_UUID:
|
||
|
self->uuid = g_strdup(g_value_get_string(val));
|
||
|
break;
|
||
|
case PROP_TYPE:
|
||
|
self->type = koto_library_type_from_string(g_strdup(g_value_get_string(val)));
|
||
|
break;
|
||
|
case PROP_STORAGE_UUID:
|
||
|
koto_library_set_storage_uuid(self, g_strdup(g_value_get_string(val)));
|
||
|
break;
|
||
|
case PROP_CONSTRUCTION_PATH:
|
||
|
koto_library_set_path(self, g_strdup(g_value_get_string(val)));
|
||
|
break;
|
||
|
case PROP_NAME:
|
||
|
koto_library_set_name(self, g_strdup(g_value_get_string(val)));
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gchar * koto_library_get_path(KotoLibrary * self) {
|
||
|
if (!KOTO_IS_LIBRARY(self)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (G_IS_MOUNT(self->mount)) {
|
||
|
|
||
|
}
|
||
|
|
||
|
return self->path;
|
||
|
}
|
||
|
|
||
|
gchar * koto_library_get_relative_path_to_file(
|
||
|
KotoLibrary * self,
|
||
|
gchar * full_path
|
||
|
) {
|
||
|
if (!KOTO_IS_LIBRARY(self)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
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
|
||
|
|
||
|
g_free(appended_slash_to_library_path);
|
||
|
return cleaned_path;
|
||
|
}
|
||
|
|
||
|
gchar * koto_library_get_storage_uuid(KotoLibrary * self) {
|
||
|
if (!KOTO_IS_LIBRARY(self)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return self->storage_uuid;
|
||
|
}
|
||
|
|
||
|
KotoLibraryType koto_library_get_lib_type(KotoLibrary * self) {
|
||
|
if (!KOTO_IS_LIBRARY(self)) {
|
||
|
return KOTO_LIBRARY_TYPE_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
return self->type;
|
||
|
}
|
||
|
|
||
|
gchar * koto_library_get_uuid(KotoLibrary * self) {
|
||
|
if (!KOTO_IS_LIBRARY(self)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return self->uuid;
|
||
|
}
|
||
|
|
||
|
void koto_library_index(KotoLibrary * self) {
|
||
|
if (!KOTO_IS_LIBRARY(self) || !self->should_index) { // Not a library or should not index
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
index_folder(self, self->path, 0); // Start index operation at the top
|
||
|
}
|
||
|
|
||
|
gboolean koto_library_is_available(KotoLibrary * self) {
|
||
|
if (!KOTO_IS_LIBRARY(self)) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
void koto_library_set_name(
|
||
|
KotoLibrary * self,
|
||
|
gchar * library_name
|
||
|
) {
|
||
|
if (!KOTO_IS_LIBRARY(self)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!koto_utils_is_string_valid(library_name)) { // Not a string
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (koto_utils_is_string_valid(self->name)) { // Name already set
|
||
|
g_free(self->name); // Free the existing value
|
||
|
}
|
||
|
|
||
|
self->name = g_strdup(library_name);
|
||
|
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NAME]);
|
||
|
}
|
||
|
|
||
|
void koto_library_set_path(
|
||
|
KotoLibrary * self,
|
||
|
gchar * path
|
||
|
) {
|
||
|
if (!KOTO_IS_LIBRARY(self)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!koto_utils_is_string_valid(path)) { // Not a valid string
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (koto_utils_is_string_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->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
|
||
|
}
|
||
|
|
||
|
void koto_library_set_storage_uuid(
|
||
|
KotoLibrary * self,
|
||
|
gchar * storage_uuid
|
||
|
) {
|
||
|
if (!KOTO_IS_LIBRARY(self)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (G_IS_MOUNT(self->mount)) { // Already have a mount
|
||
|
g_signal_handler_disconnect(self->mount, self->mount_unmounted_handler); // Stop listening to the unmounted signal for this existing mount
|
||
|
g_object_unref(self->mount); // Dereference the mount
|
||
|
g_free(self->mount_path);
|
||
|
}
|
||
|
|
||
|
if (!koto_utils_is_string_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;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GMount * mount = g_volume_monitor_get_mount_for_uuid(volume_monitor, storage_uuid); // Attempt to get the mount by this UUID
|
||
|
|
||
|
if (!G_IS_MOUNT(mount)) {
|
||
|
g_warning("Failed to get mount for UUID: %s", storage_uuid);
|
||
|
self->mount = NULL;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (g_mount_is_shadowed(mount)) { // Is shadowed and should not use
|
||
|
g_message("This mount is considered \"shadowed\" and will not be used.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GFile * mount_file = g_mount_get_default_location(mount); // Get the file for the entry location of the mount
|
||
|
|
||
|
self->mount = mount;
|
||
|
self->mount_path = g_strdup(g_file_get_path(mount_file)); // Set the mount path to the path defined for the Mount File
|
||
|
self->storage_uuid = g_strdup(storage_uuid);
|
||
|
}
|
||
|
|
||
|
gchar * koto_library_to_config_string(KotoLibrary * self) {
|
||
|
GStrvBuilder * lib_builder = g_strv_builder_new(); // Create new strv builder
|
||
|
g_strv_builder_add(lib_builder, g_strdup("[[library]]")); // Add our library array header
|
||
|
|
||
|
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
|
||
|
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)
|
||
|
g_strv_builder_add(lib_builder, g_strdup_printf("\tstorage_uuid=\"%s\"", self->storage_uuid)); // Add the storage UUID
|
||
|
}
|
||
|
|
||
|
g_strv_builder_add(lib_builder, g_strdup_printf("\ttype=\"%s\"", koto_library_type_to_string(self->type))); // Add the type
|
||
|
g_strv_builder_add(lib_builder, g_strdup_printf("\tuuid=\"%s\"", self->uuid));
|
||
|
|
||
|
GStrv lines = g_strv_builder_end(lib_builder); // Get all the lines as a GStrv which is a gchar **
|
||
|
gchar * content = g_strjoinv("\n", lines); // Separate all lines with newline
|
||
|
g_strfreev(lines); // Free our lines
|
||
|
|
||
|
g_strv_builder_unref(lib_builder); // Unref our builder
|
||
|
return g_strdup(content);
|
||
|
}
|
||
|
|
||
|
KotoLibrary * koto_library_new(
|
||
|
KotoLibraryType type,
|
||
|
const gchar * storage_uuid,
|
||
|
const gchar * path
|
||
|
) {
|
||
|
KotoLibrary * lib = g_object_new(
|
||
|
KOTO_TYPE_LIBRARY,
|
||
|
"type",
|
||
|
koto_library_type_to_string(type),
|
||
|
"uuid",
|
||
|
g_uuid_string_random(), // Create a new Library with a new UUID
|
||
|
"storage-uuid",
|
||
|
storage_uuid,
|
||
|
"construction-path",
|
||
|
path,
|
||
|
NULL
|
||
|
);
|
||
|
|
||
|
lib->should_index = TRUE;
|
||
|
return lib;
|
||
|
}
|
||
|
|
||
|
KotoLibrary * koto_library_new_from_toml_table(toml_table_t * lib_datum) {
|
||
|
toml_datum_t uuid_datum = toml_string_in(lib_datum, "uuid"); // Get the library UUID
|
||
|
|
||
|
if (!uuid_datum.ok) { // No UUID defined
|
||
|
g_warning("No UUID set for this library. Ignoring");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
gchar * uuid = g_strdup(uuid_datum.u.s); // Duplicate our UUID
|
||
|
|
||
|
toml_datum_t type_datum = toml_string_in(lib_datum, "type");
|
||
|
|
||
|
if (!type_datum.ok) { // No type defined
|
||
|
g_warning("Unknown type for library with UUID of %s", uuid);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
gchar * lib_type_as_str = g_strdup(type_datum.u.s);
|
||
|
KotoLibraryType lib_type = koto_library_type_from_string(lib_type_as_str); // Get the library's type
|
||
|
|
||
|
if (lib_type == KOTO_LIBRARY_TYPE_UNKNOWN) { // Known type
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
toml_datum_t dir_datum = toml_string_in(lib_datum, "directory");
|
||
|
|
||
|
if (!dir_datum.ok) {
|
||
|
g_critical("Failed to get directory path for library with UUID of %s", uuid);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
gchar * path = g_strdup(dir_datum.u.s); // Duplicate the path string
|
||
|
|
||
|
toml_datum_t storage_uuid_datum = toml_string_in(lib_datum, "storage_uuid"); // Get the datum for the storage UUID
|
||
|
gchar * storage_uuid = g_strdup((storage_uuid_datum.ok) ? storage_uuid_datum.u.s : "");
|
||
|
|
||
|
toml_datum_t name_datum = toml_string_in(lib_datum, "name"); // Get the datum for the name
|
||
|
gchar * name = g_strdup((name_datum.ok) ? name_datum.u.s : "");
|
||
|
|
||
|
KotoLibrary * lib = g_object_new(
|
||
|
KOTO_TYPE_LIBRARY,
|
||
|
"type",
|
||
|
lib_type_as_str,
|
||
|
"uuid",
|
||
|
uuid,
|
||
|
"storage-uuid",
|
||
|
storage_uuid,
|
||
|
"construction-path",
|
||
|
path,
|
||
|
"name",
|
||
|
name,
|
||
|
NULL
|
||
|
);
|
||
|
|
||
|
lib->should_index = FALSE;
|
||
|
return lib;
|
||
|
}
|
||
|
|
||
|
KotoLibraryType koto_library_type_from_string(gchar * t) {
|
||
|
if (
|
||
|
(g_strcmp0(t, "audiobooks") == 0) ||
|
||
|
(g_strcmp0(t, "audiobook") == 0)
|
||
|
) {
|
||
|
return KOTO_LIBRARY_TYPE_AUDIOBOOK;
|
||
|
} else if (g_strcmp0(t, "music") == 0) {
|
||
|
return KOTO_LIBRARY_TYPE_MUSIC;
|
||
|
} else if (
|
||
|
(g_strcmp0(t, "podcasts") == 0) ||
|
||
|
(g_strcmp0(t, "podcast") == 0)
|
||
|
) {
|
||
|
return KOTO_LIBRARY_TYPE_PODCAST;
|
||
|
}
|
||
|
|
||
|
|
||
|
g_warning("Invalid type provided for koto_library_type_from_string: %s", t);
|
||
|
return KOTO_LIBRARY_TYPE_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
gchar * koto_library_type_to_string(KotoLibraryType t) {
|
||
|
switch (t) {
|
||
|
case KOTO_LIBRARY_TYPE_AUDIOBOOK:
|
||
|
return g_strdup("audiobook");
|
||
|
case KOTO_LIBRARY_TYPE_MUSIC:
|
||
|
return g_strdup("music");
|
||
|
case KOTO_LIBRARY_TYPE_PODCAST:
|
||
|
return g_strdup("podcast");
|
||
|
default:
|
||
|
return g_strdup("UNKNOWN");
|
||
|
}
|
||
|
}
|