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