Compare commits
4 Commits
efc13db66e
...
8bba456b14
Author | SHA1 | Date | |
---|---|---|---|
8bba456b14 | |||
bb97445a96 | |||
e2adac72cc | |||
3ddb0cf205 |
@ -72,6 +72,8 @@ struct _FivBrowser {
|
||||
double drag_begin_x; ///< Viewport start X 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
|
||||
size_t thumbnailers_len; ///< Thumbnailers array size
|
||||
GList *thumbnailers_queue; ///< Queued up Entry pointers
|
||||
@ -84,8 +86,12 @@ struct _FivBrowser {
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
/// The "last modified" timestamp of source images for thumbnails.
|
||||
static cairo_user_data_key_t fiv_browser_key_mtime_msec;
|
||||
|
||||
struct entry {
|
||||
char *uri; ///< GIO URI
|
||||
gint64 mtime_msec; ///< Modification time in milliseconds
|
||||
cairo_surface_t *thumbnail; ///< Prescaled thumbnail
|
||||
GIcon *icon; ///< If no thumbnail, use this icon
|
||||
};
|
||||
@ -427,18 +433,34 @@ entry_add_thumbnail(gpointer data, gpointer user_data)
|
||||
g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
|
||||
|
||||
FivBrowser *browser = FIV_BROWSER(user_data);
|
||||
GFile *file = g_file_new_for_uri(self->uri);
|
||||
self->thumbnail = rescale_thumbnail(
|
||||
fiv_thumbnail_lookup(file, browser->item_size), browser->item_height);
|
||||
if (self->thumbnail)
|
||||
goto out;
|
||||
cairo_surface_t *cached =
|
||||
g_hash_table_lookup(browser->thumbnail_cache, self->uri);
|
||||
if (cached &&
|
||||
(intptr_t) cairo_surface_get_user_data(
|
||||
cached, &fiv_browser_key_mtime_msec) == self->mtime_msec) {
|
||||
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
|
||||
// in parallel--GTK+ isn't thread-safe.
|
||||
GFile *file = g_file_new_for_uri(self->uri);
|
||||
GFileInfo *info = g_file_query_info(file,
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME
|
||||
"," G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON,
|
||||
G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
||||
g_object_unref(file);
|
||||
if (info) {
|
||||
GIcon *icon = g_file_info_get_symbolic_icon(info);
|
||||
if (icon)
|
||||
@ -449,8 +471,6 @@ entry_add_thumbnail(gpointer data, gpointer user_data)
|
||||
// The GVfs backend may not be friendly.
|
||||
if (!self->icon)
|
||||
self->icon = g_icon_new_for_string("text-x-generic-symbolic", NULL);
|
||||
out:
|
||||
g_object_unref(file);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -506,8 +526,18 @@ reload_thumbnails(FivBrowser *self)
|
||||
g_thread_pool_push(pool, &g_array_index(self->entries, Entry, i), NULL);
|
||||
g_thread_pool_free(pool, FALSE, TRUE);
|
||||
|
||||
for (guint i = 0; i < self->entries->len; i++)
|
||||
materialize_icon(self, &g_array_index(self->entries, Entry, i));
|
||||
// Once a URI disappears from the model, its thumbnail is forgotten.
|
||||
g_hash_table_remove_all(self->thumbnail_cache);
|
||||
|
||||
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));
|
||||
}
|
||||
@ -529,6 +559,11 @@ thumbnailer_reprocess_entry(FivBrowser *self, GBytes *output, Entry *entry)
|
||||
fiv_io_deserialize(output), self->item_height))) {
|
||||
entry_add_thumbnail(entry, self);
|
||||
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));
|
||||
@ -914,6 +949,8 @@ fiv_browser_finalize(GObject *gobject)
|
||||
g_clear_object(&self->model);
|
||||
}
|
||||
|
||||
g_hash_table_destroy(self->thumbnail_cache);
|
||||
|
||||
cairo_surface_destroy(self->glow);
|
||||
g_clear_object(&self->pointer);
|
||||
|
||||
@ -958,6 +995,8 @@ set_item_size(FivBrowser *self, FivThumbnailSize size)
|
||||
if (size != self->item_size) {
|
||||
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);
|
||||
thumbnailers_start(self);
|
||||
|
||||
@ -1674,6 +1713,9 @@ fiv_browser_init(FivBrowser *self)
|
||||
g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
|
||||
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 =
|
||||
g_malloc0_n(self->thumbnailers_len, sizeof *self->thumbnailers);
|
||||
@ -1689,6 +1731,7 @@ fiv_browser_init(FivBrowser *self)
|
||||
|
||||
// --- Public interface --------------------------------------------------------
|
||||
|
||||
// TODO(p): Later implement any arguments of this FivIoModel signal.
|
||||
static void
|
||||
on_model_files_changed(FivIoModel *model, FivBrowser *self)
|
||||
{
|
||||
@ -1698,18 +1741,16 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self)
|
||||
if (self->selected)
|
||||
selected_uri = g_strdup(self->selected->uri);
|
||||
|
||||
// TODO(p): Later implement arguments.
|
||||
thumbnailers_abort(self);
|
||||
g_array_set_size(self->entries, 0);
|
||||
g_array_set_size(self->layouted_rows, 0);
|
||||
|
||||
GPtrArray *files = fiv_io_model_get_files(self->model);
|
||||
for (guint i = 0; i < files->len; i++) {
|
||||
g_array_append_val(self->entries,
|
||||
((Entry) {.thumbnail = NULL, .uri = files->pdata[i]}));
|
||||
files->pdata[i] = NULL;
|
||||
gsize len = 0;
|
||||
const FivIoModelEntry *files = fiv_io_model_get_files(self->model, &len);
|
||||
for (gsize i = 0; i < len; i++) {
|
||||
g_array_append_val(self->entries, ((Entry) {.thumbnail = NULL,
|
||||
.uri = g_strdup(files[i].uri), .mtime_msec = files[i].mtime_msec}));
|
||||
}
|
||||
g_ptr_array_free(files, TRUE);
|
||||
|
||||
fiv_browser_select(self, selected_uri);
|
||||
g_free(selected_uri);
|
||||
|
45
fiv-io.c
45
fiv-io.c
@ -2795,14 +2795,8 @@ fiv_io_deserialize(GBytes *bytes)
|
||||
|
||||
#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
|
||||
model_entry_finalize(ModelEntry *entry)
|
||||
model_entry_finalize(FivIoModelEntry *entry)
|
||||
{
|
||||
g_free(entry->uri);
|
||||
g_free(entry->collate_key);
|
||||
@ -2867,8 +2861,9 @@ model_supports(FivIoModel *self, const gchar *filename)
|
||||
}
|
||||
|
||||
static inline int
|
||||
model_compare_entries(FivIoModel *self, const ModelEntry *entry1, GFile *file1,
|
||||
const ModelEntry *entry2, GFile *file2)
|
||||
model_compare_entries(FivIoModel *self,
|
||||
const FivIoModelEntry *entry1, GFile *file1,
|
||||
const FivIoModelEntry *entry2, GFile *file2)
|
||||
{
|
||||
if (g_file_has_prefix(file1, file2))
|
||||
return +1;
|
||||
@ -2894,8 +2889,8 @@ model_compare_entries(FivIoModel *self, const ModelEntry *entry1, GFile *file1,
|
||||
static gint
|
||||
model_compare(gconstpointer a, gconstpointer b, gpointer user_data)
|
||||
{
|
||||
const ModelEntry *entry1 = a;
|
||||
const ModelEntry *entry2 = b;
|
||||
const FivIoModelEntry *entry1 = a;
|
||||
const FivIoModelEntry *entry2 = b;
|
||||
GFile *file1 = g_file_new_for_uri(entry1->uri);
|
||||
GFile *file2 = g_file_new_for_uri(entry2->uri);
|
||||
int result = model_compare_entries(user_data, entry1, file1, entry2, file2);
|
||||
@ -2939,7 +2934,7 @@ model_reload(FivIoModel *self, GError **error)
|
||||
if (self->filtering && g_file_info_get_is_hidden(info))
|
||||
continue;
|
||||
|
||||
ModelEntry entry = {.uri = g_file_get_uri(child)};
|
||||
FivIoModelEntry entry = {.uri = g_file_get_uri(child)};
|
||||
GDateTime *mtime = g_file_info_get_modification_date_time(info);
|
||||
if (mtime) {
|
||||
entry.mtime_msec = g_date_time_to_unix(mtime) * 1000 +
|
||||
@ -3076,8 +3071,8 @@ fiv_io_model_init(FivIoModel *self)
|
||||
self->supported_globs = extract_mime_globs((const char **) types);
|
||||
g_strfreev(types);
|
||||
|
||||
self->files = g_array_new(FALSE, TRUE, sizeof(ModelEntry));
|
||||
self->subdirs = g_array_new(FALSE, TRUE, sizeof(ModelEntry));
|
||||
self->files = g_array_new(FALSE, TRUE, sizeof(FivIoModelEntry));
|
||||
self->subdirs = g_array_new(FALSE, TRUE, sizeof(FivIoModelEntry));
|
||||
g_array_set_clear_func(
|
||||
self->subdirs, (GDestroyNotify) model_entry_finalize);
|
||||
g_array_set_clear_func(
|
||||
@ -3107,24 +3102,18 @@ fiv_io_model_get_location(FivIoModel *self)
|
||||
return self->directory;
|
||||
}
|
||||
|
||||
GPtrArray *
|
||||
fiv_io_model_get_files(FivIoModel *self)
|
||||
const FivIoModelEntry *
|
||||
fiv_io_model_get_files(FivIoModel *self, gsize *len)
|
||||
{
|
||||
GPtrArray *a = g_ptr_array_new_full(self->files->len, g_free);
|
||||
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;
|
||||
*len = self->files->len;
|
||||
return (const FivIoModelEntry *) self->files->data;
|
||||
}
|
||||
|
||||
GPtrArray *
|
||||
fiv_io_model_get_subdirectories(FivIoModel *self)
|
||||
const FivIoModelEntry *
|
||||
fiv_io_model_get_subdirs(FivIoModel *self, gsize *len)
|
||||
{
|
||||
GPtrArray *a = g_ptr_array_new_full(self->subdirs->len, g_free);
|
||||
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;
|
||||
*len = self->subdirs->len;
|
||||
return (const FivIoModelEntry *) self->subdirs->data;
|
||||
}
|
||||
|
||||
// --- Export ------------------------------------------------------------------
|
||||
|
10
fiv-io.h
10
fiv-io.h
@ -122,8 +122,14 @@ gboolean fiv_io_model_open(FivIoModel *self, GFile *directory, GError **error);
|
||||
/// There is no ownership transfer, and the object may be NULL.
|
||||
GFile *fiv_io_model_get_location(FivIoModel *self);
|
||||
|
||||
GPtrArray *fiv_io_model_get_files(FivIoModel *self);
|
||||
GPtrArray *fiv_io_model_get_subdirectories(FivIoModel *self);
|
||||
typedef struct {
|
||||
char *uri; ///< GIO URI
|
||||
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 ------------------------------------------------------------------
|
||||
|
||||
|
@ -241,14 +241,15 @@ update_location(FivSidebar *self)
|
||||
if ((row = create_row(self, location, "circle-filled-symbolic")))
|
||||
gtk_container_add(GTK_CONTAINER(self->listbox), row);
|
||||
|
||||
GPtrArray *subdirs = fiv_io_model_get_subdirectories(self->model);
|
||||
for (guint i = 0; i < subdirs->len; i++) {
|
||||
GFile *file = g_file_new_for_uri(subdirs->pdata[i]);
|
||||
gsize len = 0;
|
||||
const FivIoModelEntry *subdirs =
|
||||
fiv_io_model_get_subdirs(self->model, &len);
|
||||
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")))
|
||||
gtk_container_add(GTK_CONTAINER(self->listbox), row);
|
||||
g_object_unref(file);
|
||||
}
|
||||
g_ptr_array_free(subdirs, TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -579,18 +579,11 @@ fail_init:
|
||||
}
|
||||
|
||||
cairo_surface_t *
|
||||
fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size)
|
||||
fiv_thumbnail_lookup(char *uri, gint64 mtime_msec, FivThumbnailSize size)
|
||||
{
|
||||
g_return_val_if_fail(size >= FIV_THUMBNAIL_SIZE_MIN &&
|
||||
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 *thumbnails_dir = fiv_thumbnail_get_root();
|
||||
|
||||
@ -605,7 +598,7 @@ fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size)
|
||||
const char *name = fiv_thumbnail_sizes[use].thumbnail_spec_name;
|
||||
gchar *wide =
|
||||
g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir, name, sum);
|
||||
result = read_wide_thumbnail(wide, uri, st.st_mtim.tv_sec, &error);
|
||||
result = read_wide_thumbnail(wide, uri, mtime_msec / 1000, &error);
|
||||
if (error) {
|
||||
g_debug("%s: %s", wide, error->message);
|
||||
g_clear_error(&error);
|
||||
@ -621,7 +614,7 @@ fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size)
|
||||
|
||||
gchar *path =
|
||||
g_strdup_printf("%s/%s/%s.png", thumbnails_dir, name, sum);
|
||||
result = read_spng_thumbnail(path, uri, st.st_mtim.tv_sec, &error);
|
||||
result = read_spng_thumbnail(path, uri, mtime_msec / 1000, &error);
|
||||
if (error) {
|
||||
g_debug("%s: %s", path, error->message);
|
||||
g_clear_error(&error);
|
||||
@ -639,7 +632,6 @@ fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size)
|
||||
|
||||
g_free(thumbnails_dir);
|
||||
g_free(sum);
|
||||
g_free(uri);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,8 @@ gboolean fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size,
|
||||
|
||||
/// Retrieves a thumbnail of the most appropriate quality and resolution
|
||||
/// for the target file.
|
||||
cairo_surface_t *fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size);
|
||||
cairo_surface_t *fiv_thumbnail_lookup(
|
||||
char *uri, gint64 mtime_msec, FivThumbnailSize size);
|
||||
|
||||
/// Invalidate the wide thumbnail cache. May write to standard streams.
|
||||
void fiv_thumbnail_invalidate(void);
|
||||
|
7
fiv.c
7
fiv.c
@ -702,8 +702,13 @@ on_model_files_changed(FivIoModel *model, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
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.files = fiv_io_model_get_files(g.model);
|
||||
g.files = g_ptr_array_new_full(len, g_free);
|
||||
for (gsize i = 0; i < len; i++)
|
||||
g_ptr_array_add(g.files, g_strdup(files[i].uri));
|
||||
|
||||
update_files_index();
|
||||
|
||||
gtk_widget_set_sensitive(
|
||||
|
Loading…
x
Reference in New Issue
Block a user