Compare commits

..

No commits in common. "8bba456b1470b0e7f2c8291e50bff37c001d8f44" and "efc13db66e890a712d8ffa7a2e4289a285137d60" have entirely different histories.

7 changed files with 63 additions and 98 deletions

View File

@ -72,8 +72,6 @@ struct _FivBrowser {
double drag_begin_x; ///< Viewport start X coordinate or -1 double drag_begin_x; ///< Viewport start X coordinate or -1
double drag_begin_y; ///< Viewport start Y coordinate or -1 double drag_begin_y; ///< Viewport start Y coordinate or -1
GHashTable *thumbnail_cache; ///< [URI]cairo_surface_t, for item_size
Thumbnailer *thumbnailers; ///< Parallelized thumbnailers Thumbnailer *thumbnailers; ///< Parallelized thumbnailers
size_t thumbnailers_len; ///< Thumbnailers array size size_t thumbnailers_len; ///< Thumbnailers array size
GList *thumbnailers_queue; ///< Queued up Entry pointers GList *thumbnailers_queue; ///< Queued up Entry pointers
@ -86,12 +84,8 @@ struct _FivBrowser {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// The "last modified" timestamp of source images for thumbnails.
static cairo_user_data_key_t fiv_browser_key_mtime_msec;
struct entry { struct entry {
char *uri; ///< GIO URI char *uri; ///< GIO URI
gint64 mtime_msec; ///< Modification time in milliseconds
cairo_surface_t *thumbnail; ///< Prescaled thumbnail cairo_surface_t *thumbnail; ///< Prescaled thumbnail
GIcon *icon; ///< If no thumbnail, use this icon GIcon *icon; ///< If no thumbnail, use this icon
}; };
@ -433,34 +427,18 @@ entry_add_thumbnail(gpointer data, gpointer user_data)
g_clear_pointer(&self->thumbnail, cairo_surface_destroy); g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
FivBrowser *browser = FIV_BROWSER(user_data); FivBrowser *browser = FIV_BROWSER(user_data);
cairo_surface_t *cached = GFile *file = g_file_new_for_uri(self->uri);
g_hash_table_lookup(browser->thumbnail_cache, self->uri); self->thumbnail = rescale_thumbnail(
if (cached && fiv_thumbnail_lookup(file, browser->item_size), browser->item_height);
(intptr_t) cairo_surface_get_user_data( if (self->thumbnail)
cached, &fiv_browser_key_mtime_msec) == self->mtime_msec) { goto out;
self->thumbnail = cairo_surface_reference(cached);
} else {
cairo_surface_t *found = fiv_thumbnail_lookup(
self->uri, self->mtime_msec, browser->item_size);
self->thumbnail = rescale_thumbnail(found, browser->item_height);
}
if (self->thumbnail) {
// This choice of mtime favours unnecessary thumbnail reloading.
cairo_surface_set_user_data(self->thumbnail,
&fiv_browser_key_mtime_msec, (void *) (intptr_t) self->mtime_msec,
NULL);
return;
}
// Fall back to symbolic icons, though there's only so much we can do // Fall back to symbolic icons, though there's only so much we can do
// in parallel--GTK+ isn't thread-safe. // in parallel--GTK+ isn't thread-safe.
GFile *file = g_file_new_for_uri(self->uri);
GFileInfo *info = g_file_query_info(file, GFileInfo *info = g_file_query_info(file,
G_FILE_ATTRIBUTE_STANDARD_NAME G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON, "," G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON,
G_FILE_QUERY_INFO_NONE, NULL, NULL); G_FILE_QUERY_INFO_NONE, NULL, NULL);
g_object_unref(file);
if (info) { if (info) {
GIcon *icon = g_file_info_get_symbolic_icon(info); GIcon *icon = g_file_info_get_symbolic_icon(info);
if (icon) if (icon)
@ -471,6 +449,8 @@ entry_add_thumbnail(gpointer data, gpointer user_data)
// The GVfs backend may not be friendly. // The GVfs backend may not be friendly.
if (!self->icon) if (!self->icon)
self->icon = g_icon_new_for_string("text-x-generic-symbolic", NULL); self->icon = g_icon_new_for_string("text-x-generic-symbolic", NULL);
out:
g_object_unref(file);
} }
static void static void
@ -526,18 +506,8 @@ reload_thumbnails(FivBrowser *self)
g_thread_pool_push(pool, &g_array_index(self->entries, Entry, i), NULL); g_thread_pool_push(pool, &g_array_index(self->entries, Entry, i), NULL);
g_thread_pool_free(pool, FALSE, TRUE); g_thread_pool_free(pool, FALSE, TRUE);
// Once a URI disappears from the model, its thumbnail is forgotten. for (guint i = 0; i < self->entries->len; i++)
g_hash_table_remove_all(self->thumbnail_cache); materialize_icon(self, &g_array_index(self->entries, Entry, i));
for (guint i = 0; i < self->entries->len; i++) {
Entry *entry = &g_array_index(self->entries, Entry, i);
if (entry->thumbnail) {
g_hash_table_insert(self->thumbnail_cache, g_strdup(entry->uri),
cairo_surface_reference(entry->thumbnail));
}
materialize_icon(self, entry);
}
gtk_widget_queue_resize(GTK_WIDGET(self)); gtk_widget_queue_resize(GTK_WIDGET(self));
} }
@ -559,11 +529,6 @@ thumbnailer_reprocess_entry(FivBrowser *self, GBytes *output, Entry *entry)
fiv_io_deserialize(output), self->item_height))) { fiv_io_deserialize(output), self->item_height))) {
entry_add_thumbnail(entry, self); entry_add_thumbnail(entry, self);
materialize_icon(self, entry); materialize_icon(self, entry);
} else {
// This choice of mtime favours unnecessary thumbnail reloading.
cairo_surface_set_user_data(entry->thumbnail,
&fiv_browser_key_mtime_msec, (void *) (intptr_t) entry->mtime_msec,
NULL);
} }
gtk_widget_queue_resize(GTK_WIDGET(self)); gtk_widget_queue_resize(GTK_WIDGET(self));
@ -949,8 +914,6 @@ fiv_browser_finalize(GObject *gobject)
g_clear_object(&self->model); g_clear_object(&self->model);
} }
g_hash_table_destroy(self->thumbnail_cache);
cairo_surface_destroy(self->glow); cairo_surface_destroy(self->glow);
g_clear_object(&self->pointer); g_clear_object(&self->pointer);
@ -995,8 +958,6 @@ set_item_size(FivBrowser *self, FivThumbnailSize size)
if (size != self->item_size) { if (size != self->item_size) {
self->item_size = size; self->item_size = size;
self->item_height = fiv_thumbnail_sizes[self->item_size].size; self->item_height = fiv_thumbnail_sizes[self->item_size].size;
g_hash_table_remove_all(self->thumbnail_cache);
reload_thumbnails(self); reload_thumbnails(self);
thumbnailers_start(self); thumbnailers_start(self);
@ -1713,9 +1674,6 @@ fiv_browser_init(FivBrowser *self)
g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free); g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
abort_button_tracking(self); abort_button_tracking(self);
self->thumbnail_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, (GDestroyNotify) cairo_surface_destroy);
self->thumbnailers_len = g_get_num_processors(); self->thumbnailers_len = g_get_num_processors();
self->thumbnailers = self->thumbnailers =
g_malloc0_n(self->thumbnailers_len, sizeof *self->thumbnailers); g_malloc0_n(self->thumbnailers_len, sizeof *self->thumbnailers);
@ -1731,7 +1689,6 @@ fiv_browser_init(FivBrowser *self)
// --- Public interface -------------------------------------------------------- // --- Public interface --------------------------------------------------------
// TODO(p): Later implement any arguments of this FivIoModel signal.
static void static void
on_model_files_changed(FivIoModel *model, FivBrowser *self) on_model_files_changed(FivIoModel *model, FivBrowser *self)
{ {
@ -1741,16 +1698,18 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self)
if (self->selected) if (self->selected)
selected_uri = g_strdup(self->selected->uri); selected_uri = g_strdup(self->selected->uri);
// TODO(p): Later implement arguments.
thumbnailers_abort(self); thumbnailers_abort(self);
g_array_set_size(self->entries, 0); g_array_set_size(self->entries, 0);
g_array_set_size(self->layouted_rows, 0); g_array_set_size(self->layouted_rows, 0);
gsize len = 0; GPtrArray *files = fiv_io_model_get_files(self->model);
const FivIoModelEntry *files = fiv_io_model_get_files(self->model, &len); for (guint i = 0; i < files->len; i++) {
for (gsize i = 0; i < len; i++) { g_array_append_val(self->entries,
g_array_append_val(self->entries, ((Entry) {.thumbnail = NULL, ((Entry) {.thumbnail = NULL, .uri = files->pdata[i]}));
.uri = g_strdup(files[i].uri), .mtime_msec = files[i].mtime_msec})); files->pdata[i] = NULL;
} }
g_ptr_array_free(files, TRUE);
fiv_browser_select(self, selected_uri); fiv_browser_select(self, selected_uri);
g_free(selected_uri); g_free(selected_uri);

View File

@ -2795,8 +2795,14 @@ fiv_io_deserialize(GBytes *bytes)
#include <fnmatch.h> #include <fnmatch.h>
typedef struct {
gchar *uri; ///< GIO URI
gchar *collate_key; ///< Collate key for the filename
gint64 mtime_msec; ///< Modification time in milliseconds
} ModelEntry;
static void static void
model_entry_finalize(FivIoModelEntry *entry) model_entry_finalize(ModelEntry *entry)
{ {
g_free(entry->uri); g_free(entry->uri);
g_free(entry->collate_key); g_free(entry->collate_key);
@ -2861,9 +2867,8 @@ model_supports(FivIoModel *self, const gchar *filename)
} }
static inline int static inline int
model_compare_entries(FivIoModel *self, model_compare_entries(FivIoModel *self, const ModelEntry *entry1, GFile *file1,
const FivIoModelEntry *entry1, GFile *file1, const ModelEntry *entry2, GFile *file2)
const FivIoModelEntry *entry2, GFile *file2)
{ {
if (g_file_has_prefix(file1, file2)) if (g_file_has_prefix(file1, file2))
return +1; return +1;
@ -2889,8 +2894,8 @@ model_compare_entries(FivIoModel *self,
static gint static gint
model_compare(gconstpointer a, gconstpointer b, gpointer user_data) model_compare(gconstpointer a, gconstpointer b, gpointer user_data)
{ {
const FivIoModelEntry *entry1 = a; const ModelEntry *entry1 = a;
const FivIoModelEntry *entry2 = b; const ModelEntry *entry2 = b;
GFile *file1 = g_file_new_for_uri(entry1->uri); GFile *file1 = g_file_new_for_uri(entry1->uri);
GFile *file2 = g_file_new_for_uri(entry2->uri); GFile *file2 = g_file_new_for_uri(entry2->uri);
int result = model_compare_entries(user_data, entry1, file1, entry2, file2); int result = model_compare_entries(user_data, entry1, file1, entry2, file2);
@ -2934,7 +2939,7 @@ model_reload(FivIoModel *self, GError **error)
if (self->filtering && g_file_info_get_is_hidden(info)) if (self->filtering && g_file_info_get_is_hidden(info))
continue; continue;
FivIoModelEntry entry = {.uri = g_file_get_uri(child)}; ModelEntry entry = {.uri = g_file_get_uri(child)};
GDateTime *mtime = g_file_info_get_modification_date_time(info); GDateTime *mtime = g_file_info_get_modification_date_time(info);
if (mtime) { if (mtime) {
entry.mtime_msec = g_date_time_to_unix(mtime) * 1000 + entry.mtime_msec = g_date_time_to_unix(mtime) * 1000 +
@ -3071,8 +3076,8 @@ fiv_io_model_init(FivIoModel *self)
self->supported_globs = extract_mime_globs((const char **) types); self->supported_globs = extract_mime_globs((const char **) types);
g_strfreev(types); g_strfreev(types);
self->files = g_array_new(FALSE, TRUE, sizeof(FivIoModelEntry)); self->files = g_array_new(FALSE, TRUE, sizeof(ModelEntry));
self->subdirs = g_array_new(FALSE, TRUE, sizeof(FivIoModelEntry)); self->subdirs = g_array_new(FALSE, TRUE, sizeof(ModelEntry));
g_array_set_clear_func( g_array_set_clear_func(
self->subdirs, (GDestroyNotify) model_entry_finalize); self->subdirs, (GDestroyNotify) model_entry_finalize);
g_array_set_clear_func( g_array_set_clear_func(
@ -3102,18 +3107,24 @@ fiv_io_model_get_location(FivIoModel *self)
return self->directory; return self->directory;
} }
const FivIoModelEntry * GPtrArray *
fiv_io_model_get_files(FivIoModel *self, gsize *len) fiv_io_model_get_files(FivIoModel *self)
{ {
*len = self->files->len; GPtrArray *a = g_ptr_array_new_full(self->files->len, g_free);
return (const FivIoModelEntry *) self->files->data; for (guint i = 0; i < self->files->len; i++)
g_ptr_array_add(
a, g_strdup(g_array_index(self->files, ModelEntry, i).uri));
return a;
} }
const FivIoModelEntry * GPtrArray *
fiv_io_model_get_subdirs(FivIoModel *self, gsize *len) fiv_io_model_get_subdirectories(FivIoModel *self)
{ {
*len = self->subdirs->len; GPtrArray *a = g_ptr_array_new_full(self->subdirs->len, g_free);
return (const FivIoModelEntry *) self->subdirs->data; for (guint i = 0; i < self->subdirs->len; i++)
g_ptr_array_add(
a, g_strdup(g_array_index(self->subdirs, ModelEntry, i).uri));
return a;
} }
// --- Export ------------------------------------------------------------------ // --- Export ------------------------------------------------------------------

View File

@ -122,14 +122,8 @@ gboolean fiv_io_model_open(FivIoModel *self, GFile *directory, GError **error);
/// There is no ownership transfer, and the object may be NULL. /// There is no ownership transfer, and the object may be NULL.
GFile *fiv_io_model_get_location(FivIoModel *self); GFile *fiv_io_model_get_location(FivIoModel *self);
typedef struct { GPtrArray *fiv_io_model_get_files(FivIoModel *self);
char *uri; ///< GIO URI GPtrArray *fiv_io_model_get_subdirectories(FivIoModel *self);
char *collate_key; ///< Collate key for the filename
gint64 mtime_msec; ///< Modification time in milliseconds
} FivIoModelEntry;
const FivIoModelEntry *fiv_io_model_get_files(FivIoModel *self, gsize *len);
const FivIoModelEntry *fiv_io_model_get_subdirs(FivIoModel *self, gsize *len);
// --- Export ------------------------------------------------------------------ // --- Export ------------------------------------------------------------------

View File

@ -241,15 +241,14 @@ update_location(FivSidebar *self)
if ((row = create_row(self, location, "circle-filled-symbolic"))) if ((row = create_row(self, location, "circle-filled-symbolic")))
gtk_container_add(GTK_CONTAINER(self->listbox), row); gtk_container_add(GTK_CONTAINER(self->listbox), row);
gsize len = 0; GPtrArray *subdirs = fiv_io_model_get_subdirectories(self->model);
const FivIoModelEntry *subdirs = for (guint i = 0; i < subdirs->len; i++) {
fiv_io_model_get_subdirs(self->model, &len); GFile *file = g_file_new_for_uri(subdirs->pdata[i]);
for (gsize i = 0; i < len; i++) {
GFile *file = g_file_new_for_uri(subdirs[i].uri);
if ((row = create_row(self, file, "go-down-symbolic"))) if ((row = create_row(self, file, "go-down-symbolic")))
gtk_container_add(GTK_CONTAINER(self->listbox), row); gtk_container_add(GTK_CONTAINER(self->listbox), row);
g_object_unref(file); g_object_unref(file);
} }
g_ptr_array_free(subdirs, TRUE);
} }
static void static void

View File

@ -579,11 +579,18 @@ fail_init:
} }
cairo_surface_t * cairo_surface_t *
fiv_thumbnail_lookup(char *uri, gint64 mtime_msec, FivThumbnailSize size) fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size)
{ {
g_return_val_if_fail(size >= FIV_THUMBNAIL_SIZE_MIN && g_return_val_if_fail(size >= FIV_THUMBNAIL_SIZE_MIN &&
size <= FIV_THUMBNAIL_SIZE_MAX, NULL); size <= FIV_THUMBNAIL_SIZE_MAX, NULL);
// Local files only, at least for now.
GStatBuf st = {};
const gchar *path = g_file_peek_path(target);
if (!path || g_stat(path, &st))
return NULL;
gchar *uri = g_file_get_uri(target);
gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1); gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1);
gchar *thumbnails_dir = fiv_thumbnail_get_root(); gchar *thumbnails_dir = fiv_thumbnail_get_root();
@ -598,7 +605,7 @@ fiv_thumbnail_lookup(char *uri, gint64 mtime_msec, FivThumbnailSize size)
const char *name = fiv_thumbnail_sizes[use].thumbnail_spec_name; const char *name = fiv_thumbnail_sizes[use].thumbnail_spec_name;
gchar *wide = gchar *wide =
g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir, name, sum); g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir, name, sum);
result = read_wide_thumbnail(wide, uri, mtime_msec / 1000, &error); result = read_wide_thumbnail(wide, uri, st.st_mtim.tv_sec, &error);
if (error) { if (error) {
g_debug("%s: %s", wide, error->message); g_debug("%s: %s", wide, error->message);
g_clear_error(&error); g_clear_error(&error);
@ -614,7 +621,7 @@ fiv_thumbnail_lookup(char *uri, gint64 mtime_msec, FivThumbnailSize size)
gchar *path = gchar *path =
g_strdup_printf("%s/%s/%s.png", thumbnails_dir, name, sum); g_strdup_printf("%s/%s/%s.png", thumbnails_dir, name, sum);
result = read_spng_thumbnail(path, uri, mtime_msec / 1000, &error); result = read_spng_thumbnail(path, uri, st.st_mtim.tv_sec, &error);
if (error) { if (error) {
g_debug("%s: %s", path, error->message); g_debug("%s: %s", path, error->message);
g_clear_error(&error); g_clear_error(&error);
@ -632,6 +639,7 @@ fiv_thumbnail_lookup(char *uri, gint64 mtime_msec, FivThumbnailSize size)
g_free(thumbnails_dir); g_free(thumbnails_dir);
g_free(sum); g_free(sum);
g_free(uri);
return result; return result;
} }

View File

@ -62,8 +62,7 @@ gboolean fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size,
/// Retrieves a thumbnail of the most appropriate quality and resolution /// Retrieves a thumbnail of the most appropriate quality and resolution
/// for the target file. /// for the target file.
cairo_surface_t *fiv_thumbnail_lookup( cairo_surface_t *fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size);
char *uri, gint64 mtime_msec, FivThumbnailSize size);
/// Invalidate the wide thumbnail cache. May write to standard streams. /// Invalidate the wide thumbnail cache. May write to standard streams.
void fiv_thumbnail_invalidate(void); void fiv_thumbnail_invalidate(void);

7
fiv.c
View File

@ -702,13 +702,8 @@ on_model_files_changed(FivIoModel *model, G_GNUC_UNUSED gpointer user_data)
{ {
g_return_if_fail(model == g.model); g_return_if_fail(model == g.model);
gsize len = 0;
const FivIoModelEntry *files = fiv_io_model_get_files(g.model, &len);
g_ptr_array_free(g.files, TRUE); g_ptr_array_free(g.files, TRUE);
g.files = g_ptr_array_new_full(len, g_free); g.files = fiv_io_model_get_files(g.model);
for (gsize i = 0; i < len; i++)
g_ptr_array_add(g.files, g_strdup(files[i].uri));
update_files_index(); update_files_index();
gtk_widget_set_sensitive( gtk_widget_set_sensitive(