diff options
| author | Přemysl Eric Janouch <p@janouch.name> | 2022-08-05 06:52:01 +0200 | 
|---|---|---|
| committer | Přemysl Eric Janouch <p@janouch.name> | 2022-08-05 08:03:37 +0200 | 
| commit | 857917aa925f48ad1d7ed4be6f263f3402eb5178 (patch) | |
| tree | ce7b2a11b5b43603b6e43c7cf4e231aafca3e98a | |
| parent | 51dc56c9dfa26b560f367d2060a7147084cb7138 (diff) | |
| download | fiv-857917aa925f48ad1d7ed4be6f263f3402eb5178.tar.gz fiv-857917aa925f48ad1d7ed4be6f263f3402eb5178.tar.xz fiv-857917aa925f48ad1d7ed4be6f263f3402eb5178.zip  | |
Make file information retrieval asynchronous
Also, make error output scrollable.
| -rw-r--r-- | fiv-view.c | 237 | 
1 files changed, 157 insertions, 80 deletions
@@ -87,6 +87,8 @@ struct _FivView {  G_DEFINE_TYPE_EXTENDED(FivView, fiv_view, GTK_TYPE_WIDGET, 0,  	G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL)) +G_DEFINE_QUARK(fiv-view-cancellable-quark, fiv_view_cancellable) +  typedef struct _Dimensions {  	double width, height;  } Dimensions; @@ -1229,27 +1231,36 @@ info_parse(char *tsv)  	return vbox;  } -// Several GVfs schemes contain pseudo-symlinks that don't give out -// filesystem paths directly. -static gchar * -get_target_path(GFile *file, GCancellable *cancellable) +static GtkWidget * +info_make_bar(const char *message)  { -	GFileInfo *info = -		g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, -			G_FILE_QUERY_INFO_NONE, cancellable, NULL); -	if (!info) -		return NULL; +	GtkWidget *info = gtk_info_bar_new(); +	gtk_info_bar_set_message_type(GTK_INFO_BAR(info), GTK_MESSAGE_WARNING); +	GtkWidget *info_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info)); +	gtk_container_add(GTK_CONTAINER(info_area), gtk_label_new(message)); +	return info; +} -	gchar *path = NULL; -	const char *target_uri = g_file_info_get_attribute_string( -		info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); -	if (target_uri) { -		GFile *target = g_file_new_for_uri(target_uri); -		path = g_file_get_path(target); -		g_object_unref(target); +static void +info_redirect_error(gpointer dialog, GError *error) +{ +	// The dialog has been closed and destroyed. +	if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { +		g_error_free(error); +		return;  	} -	g_object_unref(info); -	return path; + +	GtkContainer *content_area = +		GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))); +	gtk_container_foreach(content_area, (GtkCallback) gtk_widget_destroy, NULL); +	gtk_container_add(content_area, info_make_bar(error->message)); +	if (g_error_matches(error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT)) { +		gtk_box_pack_start(GTK_BOX(content_area), +			gtk_label_new("Please install ExifTool."), TRUE, FALSE, 12); +	} + +	g_error_free(error); +	gtk_widget_show_all(GTK_WIDGET(dialog));  }  static gchar * @@ -1263,87 +1274,153 @@ bytes_to_utf8(GBytes *bytes)  }  static void -info(FivView *self) +on_info_finished(GObject *source_object, GAsyncResult *res, gpointer user_data)  { -	// TODO(p): Add a fallback to internal capabilities. -	// The simplest is to specify the filename and the resolution. -	GtkWindow *window = get_toplevel(GTK_WIDGET(self)); -	int flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE; - -	// TODO(p): Make this all cancellable, especially considering downloading. -	// Do this by showing the dialog window immediately. -	GFile *file = g_file_new_for_uri(self->uri); -	gchar *path = g_file_get_path(file); -  	GError *error = NULL; -	GBytes *bytes_in = NULL, *bytes_out = NULL, *bytes_err = NULL; -	gchar *file_data = NULL; -	gsize file_len = 0; -	if (!path && !(path = get_target_path(file, NULL)) && -		g_file_load_contents(file, NULL, &file_data, &file_len, NULL, &error)) { -		flags |= G_SUBPROCESS_FLAGS_STDIN_PIPE; -		path = g_strdup("-"); -		bytes_in = g_bytes_new_take(file_data, file_len); +	GBytes *bytes_out = NULL, *bytes_err = NULL; +	if (!g_subprocess_communicate_finish( +			G_SUBPROCESS(source_object), res, &bytes_out, &bytes_err, &error)) { +		info_redirect_error(user_data, error); +		return;  	} -	g_object_unref(file); -	if (error) -		goto out; - -	GSubprocess *subprocess = g_subprocess_new(flags, &error, "exiftool", -		"-tab", "-groupNames", "-duplicates", "-extractEmbedded", "--binary", -		"-quiet", "--", path, NULL); -	g_free(path); -	if (error || !g_subprocess_communicate( -		subprocess, bytes_in, NULL, &bytes_out, &bytes_err, &error)) -		goto out; -  	gchar *out = bytes_to_utf8(bytes_out);  	gchar *err = bytes_to_utf8(bytes_err); -	GtkWidget *dialog = gtk_widget_new(GTK_TYPE_DIALOG, -		"use-header-bar", TRUE, -		"title", "Information", -		"transient-for", window, -		"destroy-with-parent", TRUE, NULL); +	GtkWidget *dialog = GTK_WIDGET(user_data);  	GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); -	if (*err) { -		GtkWidget *info = gtk_info_bar_new(); -		GtkInfoBar *info_bar = GTK_INFO_BAR(info); -		gtk_info_bar_set_message_type(info_bar, GTK_MESSAGE_WARNING); - -		GtkWidget *info_area = gtk_info_bar_get_content_area(info_bar); -		GtkWidget *label = gtk_label_new(g_strstrip(err)); -		gtk_container_add(GTK_CONTAINER(info_area), label); - -		gtk_container_add(GTK_CONTAINER(content_area), info); -	} +	gtk_container_foreach( +		GTK_CONTAINER(content_area), (GtkCallback) gtk_widget_destroy, NULL);  	GtkWidget *scroller = gtk_scrolled_window_new(NULL, NULL); -	GtkScrolledWindow *sw = GTK_SCROLLED_WINDOW(scroller); -	gtk_scrolled_window_set_max_content_width(sw, 600); -	gtk_scrolled_window_set_max_content_height(sw, 800); -	gtk_scrolled_window_set_propagate_natural_width(sw, TRUE); -	gtk_scrolled_window_set_propagate_natural_height(sw, TRUE); +	gtk_box_pack_start(GTK_BOX(content_area), scroller, TRUE, TRUE, 0); +	GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); +	gtk_container_add(GTK_CONTAINER(scroller), vbox); +	if (*err) +		gtk_container_add(GTK_CONTAINER(vbox), info_make_bar(g_strstrip(err)));  	GtkWidget *info = info_parse(out); -	gtk_widget_set_hexpand(info, TRUE); -	gtk_widget_set_vexpand(info, TRUE);  	gtk_style_context_add_class( -		gtk_widget_get_style_context(GTK_WIDGET(info)), "fiv-information"); -	gtk_container_add(GTK_CONTAINER(scroller), info); -	gtk_container_add(GTK_CONTAINER(content_area), scroller); +		gtk_widget_get_style_context(info), "fiv-information"); +	gtk_box_pack_start(GTK_BOX(vbox), info, TRUE, TRUE, 0);  	g_free(out);  	g_free(err); -	g_object_unref(subprocess);  	gtk_widget_show_all(dialog); +} -out: -	if (error) -		show_error_dialog(window, error); +static void +info_spawn(GtkWidget *dialog, const char *path, GBytes *bytes_in) +{ +	int flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE;  	if (bytes_in) -		g_bytes_unref(bytes_in); +		flags |= G_SUBPROCESS_FLAGS_STDIN_PIPE; + +	// TODO(p): Add a fallback to internal capabilities. +	// The simplest is to specify the filename and the resolution. +	GError *error = NULL; +	GSubprocess *subprocess = g_subprocess_new(flags, &error, "exiftool", +		"-tab", "-groupNames", "-duplicates", "-extractEmbedded", "--binary", +		"-quiet", "--", path, NULL); +	if (error) { +		info_redirect_error(dialog, error); +		return; +	} + +	GCancellable *cancellable = +		g_object_get_qdata(G_OBJECT(dialog), fiv_view_cancellable_quark()); +	g_subprocess_communicate_async( +		subprocess, bytes_in, cancellable, on_info_finished, dialog); +	g_object_unref(subprocess); +} + +static void +on_info_loaded(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ +	gchar *file_data = NULL; +	gsize file_len = 0; +	GError *error = NULL; +	if (!g_file_load_contents_finish( +			G_FILE(source_object), res, &file_data, &file_len, NULL, &error)) { +		info_redirect_error(user_data, error); +		return; +	} + +	GtkWidget *dialog = GTK_WIDGET(user_data); +	GBytes *bytes_in = g_bytes_new_take(file_data, file_len); +	info_spawn(dialog, "-", bytes_in); +	g_bytes_unref(bytes_in); +} + +static void +on_info_queried(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ +	GFile *file = G_FILE(source_object); +	GError *error = NULL; +	GFileInfo *info = g_file_query_info_finish(file, res, &error); +	gboolean cancelled = +		error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED); +	g_clear_error(&error); +	if (cancelled) +		return; + +	gchar *path = NULL; +	const char *target_uri = g_file_info_get_attribute_string( +		info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); +	if (target_uri) { +		GFile *target = g_file_new_for_uri(target_uri); +		path = g_file_get_path(target); +		g_object_unref(target); +	} +	g_object_unref(info); + +	GtkWidget *dialog = GTK_WIDGET(user_data); +	GCancellable *cancellable = +		g_object_get_qdata(G_OBJECT(dialog), fiv_view_cancellable_quark()); +	if (path) { +		info_spawn(dialog, path, NULL); +		g_free(path); +	} else { +		g_file_load_contents_async(file, cancellable, on_info_loaded, dialog); +	} +} + +static void +info(FivView *self) +{ +	GtkWidget *dialog = gtk_widget_new(GTK_TYPE_DIALOG, +		"use-header-bar", TRUE, +		"title", "Information", +		"transient-for", get_toplevel(GTK_WIDGET(self)), +		"destroy-with-parent", TRUE, NULL); + +	// When the window closes, we cancel all asynchronous calls. +	GCancellable *cancellable = g_cancellable_new(); +	g_object_set_qdata_full(G_OBJECT(dialog), fiv_view_cancellable_quark(), +		cancellable, g_object_unref); +	g_signal_connect_swapped( +		dialog, "destroy", G_CALLBACK(g_cancellable_cancel), cancellable); + +	GtkWidget *spinner = gtk_spinner_new(); +	gtk_spinner_start(GTK_SPINNER(spinner)); +	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), +		spinner, TRUE, TRUE, 12); +	gtk_window_set_default_size(GTK_WINDOW(dialog), 600, 800); +	gtk_widget_show_all(dialog); + +	GFile *file = g_file_new_for_uri(self->uri); +	gchar *path = g_file_get_path(file); +	if (path) { +		info_spawn(dialog, path, NULL); +		g_free(path); +	} else { +		// Several GVfs schemes contain pseudo-symlinks +		// that don't give out filesystem paths directly. +		g_file_query_info_async(file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, +			G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, cancellable, +			on_info_queried, dialog); +	} +	g_object_unref(file);  }  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  | 
