mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-29 23:32:04 +02:00
Using recently added local cache functionality, store harvests that have expiration date. With this, next time the same URI is selected for playback we can read it from cache, skipping loading of any enhancer plugins and doing network requests. This also works nicely with Clapper discoverer feature. Making queued items be fetched and cached ahead of playback.
531 lines
14 KiB
C
531 lines
14 KiB
C
/* Clapper Playback Library
|
|
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "clapper-cache-private.h"
|
|
#include "clapper-version.h"
|
|
#include "clapper-extractable.h"
|
|
|
|
#define CLAPPER_CACHE_HEADER "CLAPPER"
|
|
|
|
typedef enum
|
|
{
|
|
CLAPPER_CACHE_IFACE_EXTRACTABLE = 1,
|
|
} ClapperCacheIfaces;
|
|
|
|
static GArray *enum_registry = NULL;
|
|
static GArray *flags_registry = NULL;
|
|
static gboolean cache_disabled = FALSE;
|
|
|
|
void
|
|
clapper_cache_initialize (void)
|
|
{
|
|
const gchar *env = g_getenv ("CLAPPER_DISABLE_CACHE");
|
|
|
|
if (G_LIKELY (!env || !g_str_has_prefix (env, "1"))) {
|
|
enum_registry = g_array_new (FALSE, TRUE, sizeof (GEnumValue *));
|
|
flags_registry = g_array_new (FALSE, TRUE, sizeof (GFlagsValue *));
|
|
} else {
|
|
cache_disabled = TRUE;
|
|
}
|
|
}
|
|
|
|
GMappedFile *
|
|
clapper_cache_open (const gchar *filename, const gchar **data, GError **error)
|
|
{
|
|
GMappedFile *file;
|
|
|
|
if (G_UNLIKELY (cache_disabled))
|
|
return NULL;
|
|
|
|
if (!(file = g_mapped_file_new (filename, FALSE, error)))
|
|
return NULL;
|
|
|
|
if (G_UNLIKELY (g_mapped_file_get_length (file) == 0)) {
|
|
g_mapped_file_unref (file);
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"File is empty");
|
|
return NULL;
|
|
}
|
|
|
|
*data = g_mapped_file_get_contents (file);
|
|
|
|
/* Header name check */
|
|
if (G_UNLIKELY (g_strcmp0 (*data, CLAPPER_CACHE_HEADER) != 0)) {
|
|
g_mapped_file_unref (file);
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"Invalid file header");
|
|
return NULL;
|
|
}
|
|
*data += strlen (*data) + 1;
|
|
|
|
/* Header version check */
|
|
if (clapper_cache_read_uint (data) != CLAPPER_VERSION_HEX) {
|
|
g_mapped_file_unref (file);
|
|
/* Just different version, so no error set */
|
|
return NULL;
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
inline gboolean
|
|
clapper_cache_read_boolean (const gchar **data)
|
|
{
|
|
gboolean val = *(const gboolean *) *data;
|
|
*data += sizeof (gboolean);
|
|
|
|
return val;
|
|
}
|
|
|
|
inline gint
|
|
clapper_cache_read_int (const gchar **data)
|
|
{
|
|
gint val = *(const gint *) *data;
|
|
*data += sizeof (gint);
|
|
|
|
return val;
|
|
}
|
|
|
|
inline guint
|
|
clapper_cache_read_uint (const gchar **data)
|
|
{
|
|
guint val = *(const guint *) *data;
|
|
*data += sizeof (guint);
|
|
|
|
return val;
|
|
}
|
|
|
|
inline gint64
|
|
clapper_cache_read_int64 (const gchar **data)
|
|
{
|
|
gint64 val = *(const gint64 *) *data;
|
|
*data += sizeof (gint64);
|
|
|
|
return val;
|
|
}
|
|
|
|
inline gdouble
|
|
clapper_cache_read_double (const gchar **data)
|
|
{
|
|
gdouble val = *(const gdouble *) *data;
|
|
*data += sizeof (gdouble);
|
|
|
|
return val;
|
|
}
|
|
|
|
inline const gchar *
|
|
clapper_cache_read_string (const gchar **data)
|
|
{
|
|
const gboolean is_null = clapper_cache_read_boolean (data);
|
|
const gchar *str = NULL;
|
|
|
|
if (!is_null) {
|
|
str = *data;
|
|
*data += strlen (str) + 1;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
inline const guint8 *
|
|
clapper_cache_read_data (const gchar **data, gsize *size)
|
|
{
|
|
const guint8 *val = NULL;
|
|
|
|
*size = *(const gsize *) *data;
|
|
*data += sizeof (gsize);
|
|
|
|
if (G_LIKELY (*size > 0)) {
|
|
val = (const guint8 *) *data;
|
|
*data += *size;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
inline GType
|
|
clapper_cache_read_enum (const gchar **data)
|
|
{
|
|
GType type;
|
|
const gchar *enum_name;
|
|
guint i, n_values;
|
|
|
|
enum_name = clapper_cache_read_string (data);
|
|
n_values = clapper_cache_read_uint (data);
|
|
|
|
/* If not registered yet */
|
|
if ((type = g_type_from_name (enum_name)) == 0) {
|
|
GEnumValue *values = g_new0 (GEnumValue, n_values + 1);
|
|
|
|
for (i = 0; i < n_values; ++i) {
|
|
values[i].value = clapper_cache_read_int (data);
|
|
values[i].value_name = g_intern_string (clapper_cache_read_string (data));
|
|
values[i].value_nick = g_intern_string (clapper_cache_read_string (data));
|
|
}
|
|
g_array_append_val (enum_registry, values); // store statically
|
|
|
|
type = g_enum_register_static (g_intern_string (enum_name),
|
|
g_array_index (enum_registry, GEnumValue *, enum_registry->len - 1));
|
|
} else {
|
|
/* Skip over data */
|
|
for (i = 0; i < n_values; ++i) {
|
|
clapper_cache_read_int (data); // value
|
|
clapper_cache_read_string (data); // value_name
|
|
clapper_cache_read_string (data); // value_nick
|
|
}
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
inline GType
|
|
clapper_cache_read_flags (const gchar **data)
|
|
{
|
|
GType type;
|
|
const gchar *flags_name;
|
|
guint i, n_values;
|
|
|
|
flags_name = clapper_cache_read_string (data);
|
|
n_values = clapper_cache_read_uint (data);
|
|
|
|
/* If not registered yet */
|
|
if ((type = g_type_from_name (flags_name)) == 0) {
|
|
GFlagsValue *values = g_new0 (GFlagsValue, n_values + 1);
|
|
|
|
for (i = 0; i < n_values; ++i) {
|
|
values[i].value = clapper_cache_read_int (data);
|
|
values[i].value_name = g_intern_string (clapper_cache_read_string (data));
|
|
values[i].value_nick = g_intern_string (clapper_cache_read_string (data));
|
|
}
|
|
g_array_append_val (flags_registry, values); // store statically
|
|
|
|
type = g_flags_register_static (g_intern_string (flags_name),
|
|
g_array_index (flags_registry, GFlagsValue *, flags_registry->len - 1));
|
|
} else {
|
|
/* Skip over data */
|
|
for (i = 0; i < n_values; ++i) {
|
|
clapper_cache_read_int (data); // value
|
|
clapper_cache_read_string (data); // value_name
|
|
clapper_cache_read_string (data); // value_nick
|
|
}
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
GType
|
|
clapper_cache_read_iface (const gchar **data)
|
|
{
|
|
gint iface_id = clapper_cache_read_int (data);
|
|
|
|
switch (iface_id) {
|
|
case CLAPPER_CACHE_IFACE_EXTRACTABLE:
|
|
return CLAPPER_TYPE_EXTRACTABLE;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
GParamSpec *
|
|
clapper_cache_read_pspec (const gchar **data)
|
|
{
|
|
GParamSpec *pspec;
|
|
GType value_type;
|
|
const gchar *name, *nick, *blurb;
|
|
GParamFlags flags;
|
|
|
|
value_type = *(const GType *) *data;
|
|
*data += sizeof (GType);
|
|
|
|
name = clapper_cache_read_string (data);
|
|
nick = clapper_cache_read_string (data);
|
|
blurb = clapper_cache_read_string (data);
|
|
|
|
flags = *(const GParamFlags *) *data;
|
|
*data += sizeof (GParamFlags);
|
|
|
|
/* NOTE: C does not guarantee order in which function arguments
|
|
* are evaluated, so read into variables and then create pspec */
|
|
|
|
switch (value_type) {
|
|
case G_TYPE_BOOLEAN:
|
|
pspec = g_param_spec_boolean (name, nick, blurb,
|
|
clapper_cache_read_boolean (data), flags);
|
|
break;
|
|
case G_TYPE_INT:{
|
|
gint minimum = clapper_cache_read_int (data);
|
|
gint maximum = clapper_cache_read_int (data);
|
|
gint default_value = clapper_cache_read_int (data);
|
|
|
|
pspec = g_param_spec_int (name, nick, blurb,
|
|
minimum, maximum, default_value, flags);
|
|
break;
|
|
}
|
|
case G_TYPE_UINT:{
|
|
guint minimum = clapper_cache_read_uint (data);
|
|
guint maximum = clapper_cache_read_uint (data);
|
|
guint default_value = clapper_cache_read_uint (data);
|
|
|
|
pspec = g_param_spec_uint (name, nick, blurb,
|
|
minimum, maximum, default_value, flags);
|
|
break;
|
|
}
|
|
case G_TYPE_DOUBLE:{
|
|
gdouble minimum = clapper_cache_read_double (data);
|
|
gdouble maximum = clapper_cache_read_double (data);
|
|
gdouble default_value = clapper_cache_read_double (data);
|
|
|
|
pspec = g_param_spec_double (name, nick, blurb,
|
|
minimum, maximum, default_value, flags);
|
|
break;
|
|
}
|
|
case G_TYPE_STRING:
|
|
pspec = g_param_spec_string (name, nick, blurb,
|
|
clapper_cache_read_string (data), flags);
|
|
break;
|
|
case G_TYPE_ENUM:{
|
|
GType enum_type = clapper_cache_read_enum (data);
|
|
gint default_value = clapper_cache_read_int (data);
|
|
|
|
pspec = g_param_spec_enum (name, nick, blurb,
|
|
enum_type, default_value, flags);
|
|
break;
|
|
}
|
|
case G_TYPE_FLAGS:{
|
|
GType flags_type = clapper_cache_read_flags (data);
|
|
guint default_value = clapper_cache_read_uint (data);
|
|
|
|
pspec = g_param_spec_flags (name, nick, blurb,
|
|
flags_type, default_value, flags);
|
|
break;
|
|
}
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
return g_param_spec_ref_sink (pspec);
|
|
}
|
|
|
|
GByteArray *
|
|
clapper_cache_create (void)
|
|
{
|
|
GByteArray *bytes;
|
|
|
|
if (G_UNLIKELY (cache_disabled))
|
|
return NULL;
|
|
|
|
bytes = g_byte_array_new ();
|
|
|
|
/* NOTE: We do not store whether string is NULL here, since it never is */
|
|
g_byte_array_append (bytes, (const guint8 *) CLAPPER_CACHE_HEADER, 8); // 7 + 1
|
|
clapper_cache_store_uint (bytes, CLAPPER_VERSION_HEX);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
inline void
|
|
clapper_cache_store_boolean (GByteArray *bytes, gboolean val)
|
|
{
|
|
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gboolean));
|
|
}
|
|
|
|
inline void
|
|
clapper_cache_store_int (GByteArray *bytes, gint val)
|
|
{
|
|
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gint));
|
|
}
|
|
|
|
inline void
|
|
clapper_cache_store_uint (GByteArray *bytes, guint val)
|
|
{
|
|
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (guint));
|
|
}
|
|
|
|
inline void
|
|
clapper_cache_store_int64 (GByteArray *bytes, gint64 val)
|
|
{
|
|
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gint64));
|
|
}
|
|
|
|
inline void
|
|
clapper_cache_store_double (GByteArray *bytes, gdouble val)
|
|
{
|
|
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gdouble));
|
|
}
|
|
|
|
inline void
|
|
clapper_cache_store_string (GByteArray *bytes, const gchar *val)
|
|
{
|
|
/* Distinguish empty string from NULL */
|
|
const gboolean is_null = (val == NULL);
|
|
|
|
clapper_cache_store_boolean (bytes, is_null);
|
|
if (!is_null)
|
|
g_byte_array_append (bytes, (const guint8 *) val, strlen (val) + 1);
|
|
}
|
|
|
|
inline void
|
|
clapper_cache_store_data (GByteArray *bytes, const guint8 *val, gsize val_size)
|
|
{
|
|
g_byte_array_append (bytes, (const guint8 *) &val_size, sizeof (gsize));
|
|
if (G_LIKELY (val_size > 0))
|
|
g_byte_array_append (bytes, val, val_size);
|
|
}
|
|
|
|
inline void
|
|
clapper_cache_store_enum (GByteArray *bytes, GType enum_type)
|
|
{
|
|
GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (enum_type));
|
|
guint i;
|
|
|
|
clapper_cache_store_string (bytes, g_type_name (enum_type));
|
|
clapper_cache_store_uint (bytes, enum_class->n_values);
|
|
|
|
for (i = 0; i < enum_class->n_values; ++i) {
|
|
clapper_cache_store_int (bytes, enum_class->values[i].value);
|
|
clapper_cache_store_string (bytes, enum_class->values[i].value_name);
|
|
clapper_cache_store_string (bytes, enum_class->values[i].value_nick);
|
|
}
|
|
}
|
|
|
|
inline void
|
|
clapper_cache_store_flags (GByteArray *bytes, GType flags_type)
|
|
{
|
|
GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (flags_type));
|
|
guint i;
|
|
|
|
clapper_cache_store_string (bytes, g_type_name (flags_type));
|
|
clapper_cache_store_uint (bytes, flags_class->n_values);
|
|
|
|
for (i = 0; i < flags_class->n_values; ++i) {
|
|
clapper_cache_store_int (bytes, flags_class->values[i].value);
|
|
clapper_cache_store_string (bytes, flags_class->values[i].value_name);
|
|
clapper_cache_store_string (bytes, flags_class->values[i].value_nick);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
clapper_cache_store_iface (GByteArray *bytes, GType iface)
|
|
{
|
|
gint iface_id = 0;
|
|
|
|
if (iface == CLAPPER_TYPE_EXTRACTABLE)
|
|
iface_id = CLAPPER_CACHE_IFACE_EXTRACTABLE;
|
|
else
|
|
return FALSE;
|
|
|
|
clapper_cache_store_int (bytes, iface_id);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
clapper_cache_store_pspec (GByteArray *bytes, GParamSpec *pspec)
|
|
{
|
|
GParamFlags flags;
|
|
const gboolean is_enum = G_IS_PARAM_SPEC_ENUM (pspec);
|
|
const gboolean is_flags = (!is_enum && G_IS_PARAM_SPEC_FLAGS (pspec));
|
|
|
|
if (is_enum) {
|
|
GType enum_type = G_TYPE_ENUM;
|
|
g_byte_array_append (bytes, (const guint8 *) &enum_type, sizeof (GType));
|
|
} else if (is_flags) {
|
|
GType flags_type = G_TYPE_FLAGS;
|
|
g_byte_array_append (bytes, (const guint8 *) &flags_type, sizeof (GType));
|
|
} else {
|
|
g_byte_array_append (bytes, (const guint8 *) &pspec->value_type, sizeof (GType));
|
|
}
|
|
|
|
clapper_cache_store_string (bytes, g_param_spec_get_name (pspec));
|
|
clapper_cache_store_string (bytes, g_param_spec_get_nick (pspec));
|
|
clapper_cache_store_string (bytes, g_param_spec_get_blurb (pspec));
|
|
|
|
flags = pspec->flags;
|
|
flags &= ~G_PARAM_STATIC_STRINGS; // Data read from cache is never static
|
|
g_byte_array_append (bytes, (const guint8 *) &flags, sizeof (GParamFlags));
|
|
|
|
switch (pspec->value_type) {
|
|
case G_TYPE_BOOLEAN:{
|
|
GParamSpecBoolean *p = (GParamSpecBoolean *) pspec;
|
|
clapper_cache_store_boolean (bytes, p->default_value);
|
|
break;
|
|
}
|
|
case G_TYPE_INT:{
|
|
GParamSpecInt *p = (GParamSpecInt *) pspec;
|
|
clapper_cache_store_int (bytes, p->minimum);
|
|
clapper_cache_store_int (bytes, p->maximum);
|
|
clapper_cache_store_int (bytes, p->default_value);
|
|
break;
|
|
}
|
|
case G_TYPE_UINT:{
|
|
GParamSpecUInt *p = (GParamSpecUInt *) pspec;
|
|
clapper_cache_store_uint (bytes, p->minimum);
|
|
clapper_cache_store_uint (bytes, p->maximum);
|
|
clapper_cache_store_uint (bytes, p->default_value);
|
|
break;
|
|
}
|
|
case G_TYPE_DOUBLE:{
|
|
GParamSpecDouble *p = (GParamSpecDouble *) pspec;
|
|
clapper_cache_store_double (bytes, p->minimum);
|
|
clapper_cache_store_double (bytes, p->maximum);
|
|
clapper_cache_store_double (bytes, p->default_value);
|
|
break;
|
|
}
|
|
case G_TYPE_STRING:{
|
|
GParamSpecString *p = (GParamSpecString *) pspec;
|
|
clapper_cache_store_string (bytes, p->default_value);
|
|
break;
|
|
}
|
|
default:{
|
|
if (is_enum) {
|
|
GParamSpecEnum *p = (GParamSpecEnum *) pspec;
|
|
clapper_cache_store_enum (bytes, pspec->value_type);
|
|
clapper_cache_store_int (bytes, p->default_value);
|
|
break;
|
|
} else if (is_flags) {
|
|
GParamSpecFlags *p = (GParamSpecFlags *) pspec;
|
|
clapper_cache_store_flags (bytes, pspec->value_type);
|
|
clapper_cache_store_uint (bytes, p->default_value);
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
clapper_cache_write (const gchar *filename, GByteArray *bytes, GError **error)
|
|
{
|
|
gchar *dirname = g_path_get_dirname (filename);
|
|
gboolean has_dir;
|
|
|
|
has_dir = (g_mkdir_with_parents (dirname, 0755) == 0);
|
|
g_free (dirname);
|
|
|
|
if (!has_dir) {
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"Could not create directory to store cache content");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Using "g_file_set_contents" to replace file atomically */
|
|
return g_file_set_contents (filename, (const gchar *) bytes->data, bytes->len, error);
|
|
}
|