aboutsummaryrefslogtreecommitdiff
path: root/fiv-thumbnail.c
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2022-02-20 13:13:49 +0100
committerPřemysl Eric Janouch <p@janouch.name>2022-02-20 15:44:42 +0100
commita28fbf25bcec03ae8cb8e6b22537883f78541d66 (patch)
treeed88ed07de49c496df933f63942bc4f1bd8f4d8d /fiv-thumbnail.c
parent6c748439ed0274d844c7634f0d60f872e3008630 (diff)
downloadfiv-a28fbf25bcec03ae8cb8e6b22537883f78541d66.tar.gz
fiv-a28fbf25bcec03ae8cb8e6b22537883f78541d66.tar.xz
fiv-a28fbf25bcec03ae8cb8e6b22537883f78541d66.zip
Implement wide thumbnail cache invalidation
Diffstat (limited to 'fiv-thumbnail.c')
-rw-r--r--fiv-thumbnail.c179
1 files changed, 178 insertions, 1 deletions
diff --git a/fiv-thumbnail.c b/fiv-thumbnail.c
index ef84079..a57d016 100644
--- a/fiv-thumbnail.c
+++ b/fiv-thumbnail.c
@@ -17,10 +17,11 @@
#include "config.h"
+#include <glib/gstdio.h>
#include <spng.h>
+#include <webp/demux.h>
#include <webp/encode.h>
#include <webp/mux.h>
-#include <glib/gstdio.h>
#include <errno.h>
#include <math.h>
@@ -582,3 +583,179 @@ fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size)
g_free(uri);
return result;
}
+
+// --- Invalidation ------------------------------------------------------------
+
+static void
+print_error(GFile *file, GError *error)
+{
+ gchar *name = g_file_get_parse_name(file);
+ g_printerr("%s: %s\n", name, error->message);
+ g_free(name);
+ g_error_free(error);
+}
+
+static gchar *
+identify_wide_thumbnail(GMappedFile *mf, time_t *mtime, GError **error)
+{
+ WebPDemuxer *demux = WebPDemux(&(WebPData) {
+ .bytes = (const uint8_t *) g_mapped_file_get_contents(mf),
+ .size = g_mapped_file_get_length(mf),
+ });
+ if (!demux) {
+ set_error(error, "demux failure while reading metadata");
+ return NULL;
+ }
+
+ WebPChunkIterator chunk_iter = {};
+ gchar *uri = NULL;
+ if (!WebPDemuxGetChunk(demux, "THUM", 1, &chunk_iter)) {
+ set_error(error, "missing THUM chunk");
+ goto fail;
+ }
+
+ // Similar to check_wide_thumbnail_texts(), but with a different purpose.
+ const char *p = (const char *) chunk_iter.chunk.bytes,
+ *end = p + chunk_iter.chunk.size, *key = NULL, *nul = NULL;
+ for (; (nul = memchr(p, '\0', end - p)); p = ++nul)
+ if (key) {
+ if (!strcmp(key, THUMB_URI) && !uri)
+ uri = g_strdup(p);
+ if (!strcmp(key, THUMB_MTIME))
+ *mtime = atol(p);
+ key = NULL;
+ } else {
+ key = p;
+ }
+ if (!uri)
+ set_error(error, "missing target URI");
+
+ WebPDemuxReleaseChunkIterator(&chunk_iter);
+fail:
+ WebPDemuxDelete(demux);
+ return uri;
+}
+
+static void
+check_wide_thumbnail(GFile *thumbnail, GError **error)
+{
+ // Not all errors are enough of a reason for us to delete something.
+ GError *tolerable = NULL;
+ const char *path = g_file_peek_path(thumbnail);
+ GMappedFile *mf = g_mapped_file_new(path, FALSE, &tolerable);
+ if (!mf) {
+ print_error(thumbnail, tolerable);
+ return;
+ }
+
+ time_t target_mtime = 0;
+ gchar *target_uri = identify_wide_thumbnail(mf, &target_mtime, error);
+ g_mapped_file_unref(mf);
+ if (!target_uri)
+ return;
+
+ // This should not occur at all, we're being pedantic.
+ gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, target_uri, -1);
+ gchar *expected_basename = g_strdup_printf("%s.webp", sum);
+ gchar *basename = g_path_get_basename(path);
+ gboolean match = !strcmp(basename, expected_basename);
+ g_free(basename);
+ g_free(expected_basename);
+ g_free(sum);
+ if (!match) {
+ set_error(error, "URI checksum mismatch");
+ g_free(target_uri);
+ return;
+ }
+
+ GFile *target = g_file_new_for_uri(target_uri);
+ g_free(target_uri);
+ GFileInfo *info = g_file_query_info(target,
+ G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE, NULL, &tolerable);
+ g_object_unref(target);
+ if (g_error_matches(tolerable, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+ g_propagate_error(error, tolerable);
+ return;
+ } else if (tolerable) {
+ print_error(thumbnail, tolerable);
+ return;
+ }
+
+ GDateTime *mdatetime = g_file_info_get_modification_date_time(info);
+ g_object_unref(info);
+ if (!mdatetime) {
+ set_error(&tolerable, "cannot retrieve file modification time");
+ print_error(thumbnail, tolerable);
+ return;
+ }
+ if (g_date_time_to_unix(mdatetime) != target_mtime)
+ set_error(error, "mtime mismatch");
+ g_date_time_unref(mdatetime);
+}
+
+static void
+invalidate_wide_thumbnail(GFile *thumbnail)
+{
+ // It's possible to lift that restriction in the future,
+ // but we need to codify how the modification time should be checked.
+ const char *path = g_file_peek_path(thumbnail);
+ if (!path) {
+ print_error(thumbnail,
+ g_error_new_literal(G_IO_ERROR, G_IO_ERROR_FAILED,
+ "thumbnails are expected to be local files"));
+ return;
+ }
+
+ // You cannot kill what you did not create.
+ if (!g_str_has_suffix(path, ".webp"))
+ return;
+
+ GError *error = NULL;
+ check_wide_thumbnail(thumbnail, &error);
+ if (error) {
+ g_debug("Deleting %s: %s", path, error->message);
+ g_clear_error(&error);
+ if (!g_file_delete(thumbnail, NULL, &error))
+ print_error(thumbnail, error);
+ }
+}
+
+static void
+invalidate_wide_thumbnail_directory(GFile *directory)
+{
+ GError *error = NULL;
+ GFileEnumerator *enumerator = g_file_enumerate_children(directory,
+ G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE, NULL, &error);
+ if (!enumerator) {
+ print_error(directory, error);
+ return;
+ }
+
+ GFileInfo *info = NULL;
+ GFile *child = NULL;
+ while (g_file_enumerator_iterate(enumerator, &info, &child, NULL, &error) &&
+ info != NULL) {
+ if (g_file_info_get_file_type(info) == G_FILE_TYPE_REGULAR)
+ invalidate_wide_thumbnail(child);
+ }
+ g_object_unref(enumerator);
+ if (error)
+ print_error(directory, error);
+}
+
+void
+fiv_thumbnail_invalidate(void)
+{
+ gchar *thumbnails_dir = fiv_thumbnail_get_root();
+ for (int i = 0; i < FIV_THUMBNAIL_SIZE_COUNT; i++) {
+ const char *name = fiv_thumbnail_sizes[i].thumbnail_spec_name;
+ gchar *dirname = g_strdup_printf("wide-%s", name);
+ GFile *dir = g_file_new_build_filename(thumbnails_dir, dirname, NULL);
+ g_free(dirname);
+ invalidate_wide_thumbnail_directory(dir);
+ g_object_unref(dir);
+ }
+ g_free(thumbnails_dir);
+}