From 380ddd540bd93850e4df254200cba5b1fdf05cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?=
Date: Thu, 30 Dec 2021 21:47:28 +0100 Subject: [PATCH] Convert all loading to use GFile Now we're able to make use of GVfs, with some caveats. --- fastiv-browse.desktop | 2 +- fastiv.c | 246 ++++++++++++++++++++++-------------------- fastiv.desktop | 2 +- fiv-browser.c | 33 +++--- fiv-io-benchmark.c | 4 +- fiv-io.c | 46 ++++---- fiv-io.h | 4 +- fiv-sidebar.c | 23 ++-- fiv-thumbnail.c | 24 ++--- fiv-view.c | 41 ++++--- fiv-view.h | 2 +- 11 files changed, 230 insertions(+), 197 deletions(-) diff --git a/fastiv-browse.desktop b/fastiv-browse.desktop index a381aee..fa0df9d 100644 --- a/fastiv-browse.desktop +++ b/fastiv-browse.desktop @@ -3,7 +3,7 @@ Type=Application Name=fastiv GenericName=File Browser Icon=fastiv -Exec=fastiv --browse -- %f +Exec=fastiv --browse -- %u NoDisplay=true Terminal=false StartupNotify=true diff --git a/fastiv.c b/fastiv.c index 0a8b426..88dd764 100644 --- a/fastiv.c +++ b/fastiv.c @@ -263,13 +263,12 @@ struct { gchar **supported_globs; gboolean filtering; - gchar *directory; ///< Full path to the currently browsed directory + gchar *uri; ///< Current image URI + gchar *directory; ///< URI of the currently browsed directory GList *directory_back; ///< History paths going backwards GList *directory_forward; ///< History paths going forwards - GPtrArray *files; - gint files_index; - - gchar *path; + GPtrArray *files; ///< "directory" contents + gint files_index; ///< Where "uri" is within "files" GtkWidget *window; GtkWidget *stack; @@ -322,15 +321,25 @@ show_error_dialog(GError *error) static void switch_to_browser(void) { - gtk_window_set_title(GTK_WINDOW(g.window), g.directory); + // TODO(p): Use g_file_get_parse_name() or something. + GFile *file = g_file_new_for_uri(g.directory); + const char *path = g_file_peek_path(file); + gtk_window_set_title(GTK_WINDOW(g.window), path ? path : g.directory); + g_object_unref(file); + gtk_stack_set_visible_child(GTK_STACK(g.stack), g.browser_paned); gtk_widget_grab_focus(g.browser_scroller); } static void -switch_to_view(const char *path) +switch_to_view(const char *uri) { - gtk_window_set_title(GTK_WINDOW(g.window), path); + // TODO(p): Use g_file_get_parse_name() or something. + GFile *file = g_file_new_for_uri(uri); + const char *path = g_file_peek_path(file); + gtk_window_set_title(GTK_WINDOW(g.window), path ? path : uri); + g_object_unref(file); + gtk_stack_set_visible_child(GTK_STACK(g.stack), g.view_box); gtk_widget_grab_focus(g.view); } @@ -338,38 +347,37 @@ switch_to_view(const char *path) static gint files_compare(gconstpointer a, gconstpointer b) { - gchar *path1 = g_canonicalize_filename(*(gchar **) a, g.directory); - gchar *path2 = g_canonicalize_filename(*(gchar **) b, g.directory); - GFile *location1 = g_file_new_for_path(path1); - GFile *location2 = g_file_new_for_path(path2); - g_free(path1); - g_free(path2); - gint result = fiv_io_filecmp(location1, location2); - g_object_unref(location1); - g_object_unref(location2); + GFile *file1 = g_file_new_for_uri(*(gchar **) a); + GFile *file2 = g_file_new_for_uri(*(gchar **) b); + gint result = fiv_io_filecmp(file1, file2); + g_object_unref(file1); + g_object_unref(file2); return result; } +static gchar * +parent_uri(GFile *child_file) +{ + GFile *parent = g_file_get_parent(child_file); + gchar *parent_uri = g_file_get_uri(parent); + g_object_unref(parent); + return parent_uri; +} + static void update_files_index(void) { g.files_index = -1; - - // FIXME: We presume that this basename is from the same directory. - gchar *basename = g.path ? g_path_get_basename(g.path) : NULL; - for (guint i = 0; i < g.files->len; i++) { - if (!g_strcmp0(basename, g_ptr_array_index(g.files, i))) + for (guint i = 0; i < g.files->len; i++) + if (!g_strcmp0(g.uri, g_ptr_array_index(g.files, i))) g.files_index = i; - } - g_free(basename); } static void -load_directory_without_reload(const gchar *dirname) +load_directory_without_reload(const gchar *uri) { - gchar *dirname_duplicated = g_strdup(dirname); - if (g.directory_back && - !strcmp(dirname, g.directory_back->data)) { + gchar *uri_duplicated = g_strdup(uri); + if (g.directory_back && !strcmp(uri, g.directory_back->data)) { // We're going back in history. if (g.directory) { g.directory_forward = @@ -380,8 +388,7 @@ load_directory_without_reload(const gchar *dirname) GList *link = g.directory_back; g.directory_back = g_list_remove_link(g.directory_back, link); g_list_free_full(link, g_free); - } else if (g.directory_forward && - !strcmp(dirname, g.directory_forward->data)) { + } else if (g.directory_forward && !strcmp(uri, g.directory_forward->data)) { // We're going forward in history. if (g.directory) { g.directory_back = @@ -392,7 +399,7 @@ load_directory_without_reload(const gchar *dirname) GList *link = g.directory_forward; g.directory_forward = g_list_remove_link(g.directory_forward, link); g_list_free_full(link, g_free); - } else if (g.directory && strcmp(dirname, g.directory)) { + } else if (g.directory && strcmp(uri, g.directory)) { // We're on a new subpath. g_list_free_full(g.directory_forward, g_free); g.directory_forward = NULL; @@ -402,14 +409,14 @@ load_directory_without_reload(const gchar *dirname) } g_free(g.directory); - g.directory = dirname_duplicated; + g.directory = uri_duplicated; } static void -load_directory(const gchar *dirname) +load_directory(const gchar *uri) { - if (dirname) { - load_directory_without_reload(dirname); + if (uri) { + load_directory_without_reload(uri); GtkAdjustment *vadjustment = gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(g.browser_scroller)); @@ -420,30 +427,35 @@ load_directory(const gchar *dirname) g_ptr_array_set_size(g.files, 0); g.files_index = -1; - GFile *file = g_file_new_for_path(g.directory); + GFile *file = g_file_new_for_uri(g.directory); fiv_sidebar_set_location(FIV_SIDEBAR(g.browser_sidebar), file); - g_object_unref(file); fiv_browser_load( FIV_BROWSER(g.browser), g.filtering ? is_supported : NULL, g.directory); GError *error = NULL; - GDir *dir = g_dir_open(g.directory, 0, &error); - if (dir) { - for (const gchar *name = NULL; (name = g_dir_read_name(dir)); ) { - // This really wants to make you use readdir() directly. - char *absolute = g_canonicalize_filename(name, g.directory); - gboolean is_dir = g_file_test(absolute, G_FILE_TEST_IS_DIR); - g_free(absolute); - if (!is_dir && is_supported(name)) - g_ptr_array_add(g.files, g_strdup(name)); + GFileEnumerator *enumerator = g_file_enumerate_children(file, + G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, NULL, &error); + if (enumerator) { + GFileInfo *info = NULL; + GFile *child = NULL; + while ( + g_file_enumerator_iterate(enumerator, &info, &child, NULL, NULL) && + info) { + // TODO(p): What encoding does g_file_info_get_name() return? + if (g_file_info_get_file_type(info) != G_FILE_TYPE_DIRECTORY && + is_supported(g_file_info_get_name(info))) + g_ptr_array_add(g.files, g_file_get_uri(child)); } - g_dir_close(dir); + g_object_unref(enumerator); g_ptr_array_sort(g.files, files_compare); update_files_index(); } else { + // TODO(p): Handle "Operation not supported" silently. show_error_dialog(error); } + g_object_unref(file); gtk_widget_set_sensitive( g.toolbar[TOOLBAR_FILE_PREVIOUS], g.files->len > 1); @@ -454,7 +466,7 @@ load_directory(const gchar *dirname) // XXX: When something outside the filtered entries is open, the index is // kept at -1, and browsing doesn't work. How to behave here? // Should we add it to the pointer array as an exception? - if (dirname) + if (uri) switch_to_browser(); } @@ -467,41 +479,40 @@ on_filtering_toggled(GtkToggleButton *button, G_GNUC_UNUSED gpointer user_data) } static void -open(const gchar *path) +open(const gchar *uri) { - g_return_if_fail(g_path_is_absolute(path)); + GFile *file = g_file_new_for_uri(uri); GError *error = NULL; - if (!fiv_view_open(FIV_VIEW(g.view), path, &error)) { - char *base = g_filename_display_basename(path); + if (!fiv_view_open(FIV_VIEW(g.view), uri, &error)) { + const char *path = g_file_peek_path(file); + // TODO(p): Use g_file_info_get_display_name(). + gchar *base = g_filename_display_basename(path ? path : uri); + g_object_unref(file); + g_prefix_error(&error, "%s: ", base); show_error_dialog(error); g_free(base); return; } - gchar *uri = g_filename_to_uri(path, NULL, NULL); - if (uri) { - gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri); - g_free(uri); - } - + gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri); g_list_free_full(g.directory_forward, g_free); g.directory_forward = NULL; - g_free(g.path); - g.path = g_strdup(path); + g_free(g.uri); + g.uri = g_strdup(uri); // So that load_directory() itself can be used for reloading. - gchar *dirname = g_path_get_dirname(path); + gchar *parent = parent_uri(file); if (!g.files->len /* hack to always load the directory after launch */ || - !g.directory || strcmp(dirname, g.directory)) - load_directory(dirname); + !g.directory || strcmp(parent, g.directory)) + load_directory(parent); else update_files_index(); - g_free(dirname); + g_free(parent); - switch_to_view(path); + switch_to_view(uri); } static GtkWidget * @@ -511,6 +522,7 @@ create_open_dialog(void) GTK_WINDOW(g.window), GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL); + gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE); GtkFileFilter *filter = gtk_file_filter_new(); for (const char **p = fiv_io_supported_media_types; *p; p++) @@ -539,13 +551,13 @@ on_open(void) // that it will remember its last location. gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), g.directory); - // The default is local-only, single item. Paths are returned absolute. + // The default is local-only, single item. switch (gtk_dialog_run(GTK_DIALOG(dialog))) { - gchar *path; + gchar *uri; case GTK_RESPONSE_ACCEPT: - path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - open(path); - g_free(path); + uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog)); + open(uri); + g_free(uri); break; case GTK_RESPONSE_NONE: dialog = NULL; @@ -561,10 +573,7 @@ on_previous(void) if (g.files_index >= 0) { int previous = (g.files->len - 1 + g.files_index - 1) % (g.files->len - 1); - char *absolute = g_canonicalize_filename( - g_ptr_array_index(g.files, previous), g.directory); - open(absolute); - g_free(absolute); + open(g_ptr_array_index(g.files, previous)); } } @@ -573,17 +582,14 @@ on_next(void) { if (g.files_index >= 0) { int next = (g.files_index + 1) % (g.files->len - 1); - char *absolute = g_canonicalize_filename( - g_ptr_array_index(g.files, next), g.directory); - open(absolute); - g_free(absolute); + open(g_ptr_array_index(g.files, next)); } } static void -spawn_path(const char *path) +spawn_uri(const char *uri) { - char *argv[] = {PROJECT_NAME, (char *) path, NULL}; + char *argv[] = {PROJECT_NAME, (char *) uri, NULL}; GError *error = NULL; g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error); @@ -594,37 +600,35 @@ static void on_item_activated(G_GNUC_UNUSED FivBrowser *browser, GFile *location, GtkPlacesOpenFlags flags, G_GNUC_UNUSED gpointer data) { - gchar *path = g_file_get_path(location); - if (path) { - if (flags == GTK_PLACES_OPEN_NEW_WINDOW) - spawn_path(path); - else - open(path); - g_free(path); - } + gchar *uri = g_file_get_uri(location); + if (flags == GTK_PLACES_OPEN_NEW_WINDOW) + spawn_uri(uri); + else + open(uri); + g_free(uri); } static gboolean -open_any_path(const char *path, gboolean force_browser) +open_any_uri(const char *uri, gboolean force_browser) { - GStatBuf st; - gchar *canonical = g_canonicalize_filename(path, g.directory); - gboolean success = !g_stat(canonical, &st); + GFile *file = g_file_new_for_uri(uri); + GFileType type = g_file_query_file_type(file, G_FILE_QUERY_INFO_NONE, NULL); + gboolean success = type != G_FILE_TYPE_UNKNOWN; if (!success) { show_error_dialog(g_error_new(G_FILE_ERROR, - g_file_error_from_errno(errno), "%s: %s", path, g_strerror(errno))); - } else if (S_ISDIR(st.st_mode)) { - load_directory(canonical); + g_file_error_from_errno(errno), "%s: %s", uri, g_strerror(ENOENT))); + } else if (type == G_FILE_TYPE_DIRECTORY) { + load_directory(uri); } else if (force_browser) { // GNOME, e.g., invokes this as a hint to focus the particular file, // which we can't currently do yet. - gchar *directory = g_path_get_dirname(canonical); - load_directory(directory); - g_free(directory); + gchar *parent = parent_uri(file); + load_directory(parent); + g_free(parent); } else { - open(canonical); + open(uri); } - g_free(canonical); + g_object_unref(file); return success; } @@ -632,14 +636,12 @@ static void on_open_location(G_GNUC_UNUSED GtkPlacesSidebar *sidebar, GFile *location, GtkPlacesOpenFlags flags, G_GNUC_UNUSED gpointer user_data) { - gchar *path = g_file_get_path(location); - if (path) { - if (flags & GTK_PLACES_OPEN_NEW_WINDOW) - spawn_path(path); - else - open_any_path(path, FALSE); - g_free(path); - } + gchar *uri = g_file_get_uri(location); + if (flags & GTK_PLACES_OPEN_NEW_WINDOW) + spawn_uri(uri); + else + open_any_uri(uri, FALSE); + g_free(uri); } static void @@ -750,7 +752,7 @@ on_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event, fiv_sidebar_show_enter_location(FIV_SIDEBAR(g.browser_sidebar)); return TRUE; case GDK_KEY_n: - spawn_path(g.directory); + spawn_uri(g.directory); return TRUE; case GDK_KEY_r: // TODO(p): Reload the image instead, if it's currently visible. @@ -778,12 +780,13 @@ on_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event, if (g.directory_forward) load_directory(g.directory_forward->data); else - switch_to_view(g.path); + switch_to_view(g.uri); return TRUE; case GDK_KEY_Up: if (gtk_stack_get_visible_child(GTK_STACK(g.stack)) != g.view_box) { - // This isn't exact, trailing slashes should be ignored. - gchar *parent = g_path_get_dirname(g.directory); + GFile *directory = g_file_new_for_uri(g.directory); + gchar *parent = parent_uri(directory); + g_object_unref(directory); load_directory(parent); g_free(parent); } @@ -896,7 +899,7 @@ on_button_press_browser_paned( if (g.directory_forward) load_directory(g.directory_forward->data); else - switch_to_view(g.path); + switch_to_view(g.uri); return TRUE; default: return FALSE; @@ -1245,7 +1248,7 @@ main(int argc, char *argv[]) if (size >= FIV_THUMBNAIL_SIZE_COUNT) exit_fatal("unknown thumbnail size: %s", thumbnail_size); - GFile *target = g_file_new_for_path(path_arg); + GFile *target = g_file_new_for_commandline_arg(path_arg); if (!fiv_thumbnail_produce(target, size, &error)) exit_fatal("%s", error->message); g_object_unref(target); @@ -1389,12 +1392,21 @@ main(int argc, char *argv[]) gtk_window_set_default_size(GTK_WINDOW(g.window), 4 * unit, 3 * unit); g.files = g_ptr_array_new_full(16, g_free); - g.directory = g_get_current_dir(); + gchar *cwd = g_get_current_dir(); + g.directory = g_filename_to_uri(cwd, NULL, NULL /* error */); + g_free(cwd); // XXX: The widget wants to read the display's profile. The realize is ugly. gtk_widget_realize(g.view); - if (!path_arg || !open_any_path(path_arg, browse)) - open_any_path(g.directory, FALSE); + + gchar *uri = NULL; + if (path_arg) { + GFile *file = g_file_new_for_commandline_arg(path_arg); + uri = g_file_get_uri(file); + g_object_unref(file); + } + if (!uri || !open_any_uri(uri, browse)) + open_any_uri(g.directory, FALSE); gtk_widget_show_all(g.window); gtk_main(); diff --git a/fastiv.desktop b/fastiv.desktop index cc92cb6..e1a50a9 100644 --- a/fastiv.desktop +++ b/fastiv.desktop @@ -3,7 +3,7 @@ Type=Application Name=fastiv GenericName=Image Viewer Icon=fastiv -Exec=fastiv -- %f +Exec=fastiv -- %u Terminal=false StartupNotify=true Categories=Graphics;2DGraphics;Viewer; diff --git a/fiv-browser.c b/fiv-browser.c index 8296e67..39e2528 100644 --- a/fiv-browser.c +++ b/fiv-browser.c @@ -48,7 +48,7 @@ struct _FivBrowser { int item_height; ///< Thumbnail height in pixels int item_spacing; ///< Space between items in pixels - char *path; ///< Current path + char *uri; ///< Current URI GArray *entries; ///< []Entry GArray *layouted_rows; ///< []Row int selected; @@ -502,9 +502,9 @@ thumbnailer_next(FivBrowser *self) const Entry *entry = link->data; GFile *file = g_file_new_for_uri(entry->uri); - gchar *path = g_file_get_path(file); + gchar *uri = g_file_get_uri(file); g_object_unref(file); - if (!path) { + if (!uri) { // TODO(p): Support thumbnailing non-local URIs in some manner. self->thumbnail_queue = g_list_delete_link(self->thumbnail_queue, link); return; @@ -513,9 +513,9 @@ thumbnailer_next(FivBrowser *self) GError *error = NULL; self->thumbnailer = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error, PROJECT_NAME, "--thumbnail", - fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", path, + fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", uri, NULL); - g_free(path); + g_free(uri); if (error) { g_warning("%s", error->message); g_error_free(error); @@ -550,7 +550,7 @@ thumbnailer_start(FivBrowser *self) gchar *thumbnails_dir = fiv_thumbnail_get_root(); GFile *thumbnails = g_file_new_for_path(thumbnails_dir); g_free(thumbnails_dir); - GFile *current = g_file_new_for_path(self->path); + GFile *current = g_file_new_for_uri(self->uri); gboolean is_a_thumbnail = g_file_has_prefix(current, thumbnails); g_object_unref(current); g_object_unref(thumbnails); @@ -759,7 +759,7 @@ fiv_browser_finalize(GObject *gobject) { FivBrowser *self = FIV_BROWSER(gobject); thumbnailer_abort(self); - g_free(self->path); + g_free(self->uri); g_array_free(self->entries, TRUE); g_array_free(self->layouted_rows, TRUE); cairo_surface_destroy(self->glow); @@ -940,9 +940,7 @@ fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event) const Entry *entry = entry_at(self, event->x, event->y); if (!entry && event->button == GDK_BUTTON_SECONDARY) { - gchar *uri = g_filename_to_uri(self->path, NULL, NULL); - show_context_menu(widget, uri); - g_free(uri); + show_context_menu(widget, self->uri); return TRUE; } if (!entry) @@ -1175,22 +1173,27 @@ entry_compare(gconstpointer a, gconstpointer b) void fiv_browser_load( - FivBrowser *self, FivBrowserFilterCallback cb, const char *path) + FivBrowser *self, FivBrowserFilterCallback cb, const char *uri) { g_return_if_fail(FIV_IS_BROWSER(self)); thumbnailer_abort(self); g_array_set_size(self->entries, 0); g_array_set_size(self->layouted_rows, 0); - g_clear_pointer(&self->path, g_free); + g_clear_pointer(&self->uri, g_free); - GFile *file = g_file_new_for_path((self->path = g_strdup(path))); + GFile *file = g_file_new_for_uri((self->uri = g_strdup(uri))); + + GError *error = NULL; GFileEnumerator *enumerator = g_file_enumerate_children(file, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE, - G_FILE_QUERY_INFO_NONE, NULL, NULL); + G_FILE_QUERY_INFO_NONE, NULL, &error); g_object_unref(file); - if (!enumerator) + if (!enumerator) { + // Note that this has had a side-effect of clearing all entries. + g_error_free(error); return; + } while (TRUE) { GFileInfo *info = NULL; diff --git a/fiv-io-benchmark.c b/fiv-io-benchmark.c index d70d1c9..48c5b79 100644 --- a/fiv-io-benchmark.c +++ b/fiv-io-benchmark.c @@ -33,7 +33,9 @@ static void one_file(const char *filename) { double since_us = timestamp(); - cairo_surface_t *loaded_by_us = fiv_io_open(filename, NULL); + gchar *uri = g_filename_to_uri(filename, NULL, NULL); + cairo_surface_t *loaded_by_us = fiv_io_open(uri, NULL, FALSE, NULL); + g_free(uri); if (!loaded_by_us) return; diff --git a/fiv-io.c b/fiv-io.c index 2faecb1..5f2e10f 100644 --- a/fiv-io.c +++ b/fiv-io.c @@ -1329,9 +1329,9 @@ open_libraw(const gchar *data, gsize len, GError **error) // FIXME: librsvg rasterizes filters, so this method isn't fully appropriate. static cairo_surface_t * -open_librsvg(const gchar *data, gsize len, const gchar *path, GError **error) +open_librsvg(const gchar *data, gsize len, const gchar *uri, GError **error) { - GFile *base_file = g_file_new_for_path(path); + GFile *base_file = g_file_new_for_uri(uri); GInputStream *is = g_memory_input_stream_new_from_data(data, len, NULL); RsvgHandle *handle = rsvg_handle_new_from_stream_sync( is, base_file, RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA, NULL, error); @@ -1694,7 +1694,7 @@ fail: } static cairo_surface_t * -open_libwebp(const gchar *data, gsize len, const gchar *path, +open_libwebp(const gchar *data, gsize len, const gchar *uri, FivIoProfile target, GError **error) { // It is wholly zero-initialized by libwebp. @@ -1723,7 +1723,7 @@ open_libwebp(const gchar *data, gsize len, const gchar *path, WebPDemuxState state = WEBP_DEMUX_PARSE_ERROR; WebPDemuxer *demux = WebPDemuxPartial(&wd, &state); if (!demux) { - g_warning("%s: %s", path, "demux failure"); + g_warning("%s: %s", uri, "demux failure"); goto fail; } @@ -1876,7 +1876,7 @@ fail: } static void -load_libheif_aux_images(const gchar *path, struct heif_image_handle *top, +load_libheif_aux_images(const gchar *uri, struct heif_image_handle *top, cairo_surface_t **result, cairo_surface_t **result_tail) { // Include the depth image, we have no special processing for it now. @@ -1890,14 +1890,14 @@ load_libheif_aux_images(const gchar *path, struct heif_image_handle *top, struct heif_error err = heif_image_handle_get_auxiliary_image_handle(top, ids[i], &handle); if (err.code != heif_error_Ok) { - g_warning("%s: %s", path, err.message); + g_warning("%s: %s", uri, err.message); continue; } GError *e = NULL; if (!try_append_page( load_libheif_image(handle, &e), result, result_tail)) { - g_warning("%s: %s", path, e->message); + g_warning("%s: %s", uri, e->message); g_error_free(e); } @@ -1908,7 +1908,7 @@ load_libheif_aux_images(const gchar *path, struct heif_image_handle *top, } static cairo_surface_t * -open_libheif(const gchar *data, gsize len, const gchar *path, +open_libheif(const gchar *data, gsize len, const gchar *uri, FivIoProfile profile, GError **error) { // libheif will throw C++ exceptions on allocation failures. @@ -1930,19 +1930,19 @@ open_libheif(const gchar *data, gsize len, const gchar *path, struct heif_image_handle *handle = NULL; err = heif_context_get_image_handle(ctx, ids[i], &handle); if (err.code != heif_error_Ok) { - g_warning("%s: %s", path, err.message); + g_warning("%s: %s", uri, err.message); continue; } GError *e = NULL; if (!try_append_page( load_libheif_image(handle, &e), &result, &result_tail)) { - g_warning("%s: %s", path, e->message); + g_warning("%s: %s", uri, e->message); g_error_free(e); } // TODO(p): Possibly add thumbnail images as well. - load_libheif_aux_images(path, handle, &result, &result_tail); + load_libheif_aux_images(uri, handle, &result, &result_tail); heif_image_handle_release(handle); } if (!result) { @@ -2136,7 +2136,7 @@ fail: } static cairo_surface_t * -open_libtiff(const gchar *data, gsize len, const gchar *path, +open_libtiff(const gchar *data, gsize len, const gchar *uri, FivIoProfile target, GError **error) { // Both kinds of handlers are called, redirect everything. @@ -2151,7 +2151,7 @@ open_libtiff(const gchar *data, gsize len, const gchar *path, }; cairo_surface_t *result = NULL, *result_tail = NULL; - TIFF *tiff = TIFFClientOpen(path, "rm" /* Avoid mmap. */, &h, + TIFF *tiff = TIFFClientOpen(uri, "rm" /* Avoid mmap. */, &h, fiv_io_tiff_read, fiv_io_tiff_write, fiv_io_tiff_seek, fiv_io_tiff_close, fiv_io_tiff_size, NULL, NULL); if (!tiff) @@ -2186,7 +2186,7 @@ open_libtiff(const gchar *data, gsize len, const gchar *path, GError *err = NULL; if (!try_append_page( load_libtiff_directory(tiff, &err), &result, &result_tail)) { - g_warning("%s: %s", path, err->message); + g_warning("%s: %s", uri, err->message); g_error_free(err); } } while (TIFFReadDirectory(tiff)); @@ -2316,7 +2316,7 @@ cairo_user_data_key_t fiv_io_key_page_previous; cairo_surface_t * fiv_io_open( - const gchar *path, FivIoProfile profile, gboolean enhance, GError **error) + const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error) { // TODO(p): Don't always load everything into memory, test type first, // so that we can reject non-pictures early. Wuffs only needs the first @@ -2332,19 +2332,21 @@ fiv_io_open( // // gdk-pixbuf exposes its detection data through gdk_pixbuf_get_formats(). // This may also be unbounded, as per format_check(). + GFile *file = g_file_new_for_uri(uri); + gchar *data = NULL; gsize len = 0; - if (!g_file_get_contents(path, &data, &len, error)) + if (!g_file_load_contents(file, NULL, &data, &len, NULL, error)) return NULL; cairo_surface_t *surface = - fiv_io_open_from_data(data, len, path, profile, enhance, error); + fiv_io_open_from_data(data, len, uri, profile, enhance, error); free(data); return surface; } cairo_surface_t * -fiv_io_open_from_data(const char *data, size_t len, const gchar *path, +fiv_io_open_from_data(const char *data, size_t len, const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error) { wuffs_base__slice_u8 prefix = @@ -2376,7 +2378,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path, break; default: // TODO(p): https://github.com/google/wuffs/commit/4c04ac1 - if ((surface = open_libwebp(data, len, path, profile, error))) + if ((surface = open_libwebp(data, len, uri, profile, error))) break; if (error) { g_debug("%s", (*error)->message); @@ -2395,7 +2397,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path, } #endif // HAVE_LIBRAW --------------------------------------------------------- #ifdef HAVE_LIBRSVG // -------------------------------------------------------- - if ((surface = open_librsvg(data, len, path, error))) + if ((surface = open_librsvg(data, len, uri, error))) break; // XXX: It doesn't look like librsvg can return sensible errors. @@ -2413,7 +2415,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path, } #endif // HAVE_XCURSOR -------------------------------------------------------- #ifdef HAVE_LIBHEIF //--------------------------------------------------------- - if ((surface = open_libheif(data, len, path, profile, error))) + if ((surface = open_libheif(data, len, uri, profile, error))) break; if (error) { g_debug("%s", (*error)->message); @@ -2422,7 +2424,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path, #endif // HAVE_LIBHEIF -------------------------------------------------------- #ifdef HAVE_LIBTIFF //--------------------------------------------------------- // This needs to be positioned after LibRaw. - if ((surface = open_libtiff(data, len, path, profile, error))) + if ((surface = open_libtiff(data, len, uri, profile, error))) break; if (error) { g_debug("%s", (*error)->message); diff --git a/fiv-io.h b/fiv-io.h index 35eff32..7e1ee9a 100644 --- a/fiv-io.h +++ b/fiv-io.h @@ -73,9 +73,9 @@ extern cairo_user_data_key_t fiv_io_key_page_next; extern cairo_user_data_key_t fiv_io_key_page_previous; cairo_surface_t *fiv_io_open( - const gchar *path, FivIoProfile profile, gboolean enhance, GError **error); + const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error); cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len, - const gchar *path, FivIoProfile profile, gboolean enhance, GError **error); + const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error); int fiv_io_filecmp(GFile *f1, GFile *f2); diff --git a/fiv-sidebar.c b/fiv-sidebar.c index 5a01194..7712eb9 100644 --- a/fiv-sidebar.c +++ b/fiv-sidebar.c @@ -85,11 +85,15 @@ static GtkWidget * create_row(GFile *file, const char *icon_name) { // TODO(p): Handle errors better. + GError *error = NULL; GFileInfo *info = g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL); - if (!info) + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error); + if (!info) { + g_debug("%s", error->message); + g_error_free(error); return NULL; + } const char *name = g_file_info_get_display_name(info); GtkWidget *rowbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); @@ -147,20 +151,21 @@ update_location(FivSidebar *self, GFile *location) g_return_if_fail(self->location != NULL); GFile *iter = g_object_ref(self->location); + GtkWidget *row = NULL; while (TRUE) { GFile *parent = g_file_get_parent(iter); g_object_unref(iter); if (!(iter = parent)) break; - gtk_list_box_prepend(GTK_LIST_BOX(self->listbox), - create_row(parent, "go-up-symbolic")); + if ((row = create_row(parent, "go-up-symbolic"))) + gtk_list_box_prepend(GTK_LIST_BOX(self->listbox), row); } // Other options are "folder-{visiting,open}-symbolic", though the former // is mildly inappropriate (means: open in another window). - gtk_container_add(GTK_CONTAINER(self->listbox), - create_row(self->location, "circle-filled-symbolic")); + if ((row = create_row(self->location, "circle-filled-symbolic"))) + gtk_container_add(GTK_CONTAINER(self->listbox), row); GFileEnumerator *enumerator = g_file_enumerate_children(self->location, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME @@ -181,9 +186,9 @@ update_location(FivSidebar *self, GFile *location) break; if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY && - !g_file_info_get_is_hidden(info)) - gtk_container_add(GTK_CONTAINER(self->listbox), - create_row(child, "go-down-symbolic")); + !g_file_info_get_is_hidden(info) && + (row = create_row(child, "go-down-symbolic"))) + gtk_container_add(GTK_CONTAINER(self->listbox), row); } g_object_unref(enumerator); } diff --git a/fiv-thumbnail.c b/fiv-thumbnail.c index 1d7ad2b..60df749 100644 --- a/fiv-thumbnail.c +++ b/fiv-thumbnail.c @@ -223,20 +223,17 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error) max_size <= FIV_THUMBNAIL_SIZE_MAX, FALSE); // Local files only, at least for now. - gchar *path = g_file_get_path(target); + const gchar *path = g_file_peek_path(target); if (!path) return FALSE; GMappedFile *mf = g_mapped_file_new(path, FALSE, error); - if (!mf) { - g_free(path); + if (!mf) return FALSE; - } GStatBuf st = {}; if (g_stat(path, &st)) { set_error(error, g_strerror(errno)); - g_free(path); return FALSE; } @@ -246,7 +243,6 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error) cairo_surface_t *surface = fiv_io_open_from_data( g_mapped_file_get_contents(mf), filesize, path, sRGB, FALSE, error); - g_free(path); g_mapped_file_unref(mf); if (sRGB) fiv_io_profile_free(sRGB); @@ -329,7 +325,12 @@ static cairo_surface_t * read_wide_thumbnail( const gchar *path, const gchar *uri, time_t mtime, GError **error) { - cairo_surface_t *surface = fiv_io_open(path, NULL, FALSE, error); + gchar *thumbnail_uri = g_filename_to_uri(path, NULL, error); + if (!thumbnail_uri) + return NULL; + + cairo_surface_t *surface = fiv_io_open(thumbnail_uri, NULL, FALSE, error); + g_free(thumbnail_uri); if (!surface) return NULL; @@ -496,14 +497,9 @@ fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size) size <= FIV_THUMBNAIL_SIZE_MAX, NULL); // Local files only, at least for now. - gchar *path = g_file_get_path(target); - if (!path) - return NULL; - GStatBuf st = {}; - int err = g_stat(path, &st); - g_free(path); - if (err) + const gchar *path = g_file_peek_path(target); + if (!path || g_stat(path, &st)) return NULL; gchar *uri = g_file_get_uri(target); diff --git a/fiv-view.c b/fiv-view.c index 5162f32..6c47e74 100644 --- a/fiv-view.c +++ b/fiv-view.c @@ -33,7 +33,7 @@ struct _FivView { GtkWidget parent_instance; - gchar *path; ///< Path to the current image (if any) + gchar *uri; ///< Path to the current image (if any) cairo_surface_t *image; ///< The loaded image (sequence) cairo_surface_t *page; ///< Current page within image, weak cairo_surface_t *frame; ///< Current frame within page, weak @@ -119,7 +119,7 @@ fiv_view_finalize(GObject *gobject) g_clear_pointer(&self->screen_cms_profile, fiv_io_profile_free); g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); g_clear_pointer(&self->image, cairo_surface_destroy); - g_free(self->path); + g_free(self->uri); G_OBJECT_CLASS(fiv_view_parent_class)->finalize(gobject); } @@ -754,19 +754,25 @@ save_as(FivView *self, cairo_surface_t *frame) GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog); gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); + GFile *file = g_file_new_for_uri(self->uri); + const gchar *path = g_file_peek_path(file); + // TODO(p): Use g_file_info_get_display_name(). + gchar *basename = g_filename_display_basename(path ? path : self->uri); + // Note that GTK+'s save dialog is too stupid to automatically change // the extension when user changes the filter. Presumably, // gtk_file_chooser_set_extra_widget() can be used to circumvent this. - gchar *basename = g_filename_display_basename(self->path); gchar *name = g_strdup_printf(frame ? "%s.frame.webp" : "%s.webp", basename); - g_free(basename); gtk_file_chooser_set_current_name(chooser, name); g_free(name); - - gchar *dirname = g_path_get_dirname(self->path); - gtk_file_chooser_set_current_folder(chooser, dirname); - g_free(dirname); + if (path) { + gchar *dirname = g_path_get_dirname(path); + gtk_file_chooser_set_current_folder(chooser, dirname); + g_free(dirname); + } + g_free(basename); + g_object_unref(file); // This is the best general format: supports lossless encoding, animations, // alpha channel, and Exif and ICC profile metadata. @@ -892,9 +898,16 @@ info(FivView *self) int flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE; GError *error = NULL; + gchar *path = g_filename_from_uri(self->uri, NULL, &error); + if (!path) { + show_error_dialog(window, error); + return; + } + GSubprocess *subprocess = g_subprocess_new(flags, &error, "exiftool", "-tab", "-groupNames", "-duplicates", "-extractEmbedded", "--binary", - "-quiet", "--", self->path, NULL); + "-quiet", "--", path, NULL); + g_free(path); if (error) { show_error_dialog(window, error); return; @@ -1123,10 +1136,10 @@ fiv_view_init(FivView *self) // TODO(p): Progressive picture loading, or at least async/cancellable. gboolean -fiv_view_open(FivView *self, const gchar *path, GError **error) +fiv_view_open(FivView *self, const gchar *uri, GError **error) { cairo_surface_t *surface = fiv_io_open( - path, self->enable_cms ? self->screen_cms_profile : NULL, FALSE, error); + uri, self->enable_cms ? self->screen_cms_profile : NULL, FALSE, error); if (!surface) return FALSE; if (self->image) @@ -1145,8 +1158,8 @@ fiv_view_open(FivView *self, const gchar *path, GError **error) switch_page(self, self->image); set_scale_to_fit(self, true); - g_free(self->path); - self->path = g_strdup(path); + g_free(self->uri); + self->uri = g_strdup(uri); g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_HAS_IMAGE]); return TRUE; @@ -1177,7 +1190,7 @@ static gboolean reload(FivView *self) { GError *error = NULL; - cairo_surface_t *surface = fiv_io_open(self->path, + cairo_surface_t *surface = fiv_io_open(self->uri, self->enable_cms ? self->screen_cms_profile : NULL, self->enhance, &error); if (!surface) { diff --git a/fiv-view.h b/fiv-view.h index 03db6bd..c487d91 100644 --- a/fiv-view.h +++ b/fiv-view.h @@ -23,7 +23,7 @@ G_DECLARE_FINAL_TYPE(FivView, fiv_view, FIV, VIEW, GtkWidget) /// Try to open the given file, synchronously, to be displayed by the widget. -gboolean fiv_view_open(FivView *self, const gchar *path, GError **error); +gboolean fiv_view_open(FivView *self, const gchar *uri, GError **error); typedef enum _FivViewCommand { FIV_VIEW_COMMAND_ROTATE_LEFT = 1,