Implement mutli-Library support.
Honestly, not going to bother summarizing this massive changeset. You are welcome to look it over in your own free time. Fixes #10. Fixes #11
This commit is contained in:
parent
8d823dbbec
commit
44e4564f1c
38 changed files with 2408 additions and 1072 deletions
511
src/indexer/library.c
Normal file
511
src/indexer/library.c
Normal file
|
@ -0,0 +1,511 @@
|
|||
/* 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");
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue