diff --git a/gio/glocalfileinfo.c b/gio/glocalfileinfo.c index ecbc629c6..b2a497a44 100644 --- a/gio/glocalfileinfo.c +++ b/gio/glocalfileinfo.c @@ -1426,8 +1426,10 @@ get_thumbnail_attributes (const char *path, { GChecksum *checksum; char *uri; - char *filename; + char *filename = NULL; char *basename; + const char *size_dirs[4] = { "xx-large", "x-large", "large", "normal" }; + gsize i; uri = g_filename_to_uri (path, NULL, NULL); @@ -1437,11 +1439,18 @@ get_thumbnail_attributes (const char *path, basename = g_strconcat (g_checksum_get_string (checksum), ".png", NULL); g_checksum_free (checksum); - filename = g_build_filename (g_get_user_cache_dir (), - "thumbnails", "large", basename, - NULL); + for (i = 0; i < G_N_ELEMENTS (size_dirs); i++) + { + filename = g_build_filename (g_get_user_cache_dir (), + "thumbnails", size_dirs[i], basename, + NULL); + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) + break; - if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) + g_clear_pointer (&filename, g_free); + } + + if (filename) { _g_file_info_set_attribute_byte_string_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAIL_PATH, filename); _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAIL_IS_VALID, @@ -1449,33 +1458,18 @@ get_thumbnail_attributes (const char *path, } else { - g_free (filename); filename = g_build_filename (g_get_user_cache_dir (), - "thumbnails", "normal", basename, + "thumbnails", "fail", + "gnome-thumbnail-factory", + basename, NULL); if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { - _g_file_info_set_attribute_byte_string_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAIL_PATH, filename); + _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAILING_FAILED, TRUE); _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAIL_IS_VALID, thumbnail_verify (filename, uri, stat_buf)); } - else - { - g_free (filename); - filename = g_build_filename (g_get_user_cache_dir (), - "thumbnails", "fail", - "gnome-thumbnail-factory", - basename, - NULL); - - if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) - { - _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAILING_FAILED, TRUE); - _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAIL_IS_VALID, - thumbnail_verify (filename, uri, stat_buf)); - } - } } g_free (basename); g_free (filename); diff --git a/gio/tests/file-thumbnail.c b/gio/tests/file-thumbnail.c new file mode 100644 index 000000000..6e8768a73 --- /dev/null +++ b/gio/tests/file-thumbnail.c @@ -0,0 +1,426 @@ +/* Unit tests for GFile thumbnails + * GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2022 Marco Trevisan + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: Marco Trevisan + */ + +#include + +#define THUMBNAIL_FAIL_SIZE "fail" + +#define THUMBNAILS_ATTRIBS ( \ + G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," \ + G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," \ + G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID "," \ +) + +/* Must be kept in order, for priority */ +static const char * SIZES_NAMES[] = { + "normal", + "large", + "x-large", + "xx-large", +}; + +static GFile * +get_thumbnail_src_file (const gchar *name) +{ + const gchar *thumbnail_path; + thumbnail_path = g_test_get_filename (G_TEST_DIST, "thumbnails", + name, NULL); + + g_assert_true (g_file_test (thumbnail_path, G_FILE_TEST_IS_REGULAR)); + + return g_file_new_for_path (thumbnail_path); +} + +static gchar * +get_thumbnail_basename (GFile *source) +{ + GChecksum *checksum; + gchar *uri = g_file_get_uri (source); + gchar *basename; + + checksum = g_checksum_new (G_CHECKSUM_MD5); + g_checksum_update (checksum, (const guchar *) uri, strlen (uri)); + + basename = g_strconcat (g_checksum_get_string (checksum), ".png", NULL); + + g_checksum_free (checksum); + g_free (uri); + + return basename; +} + +static GFile * +get_expected_thumbnail_file (GFile *source, + const gchar *size) +{ + GFile *file; + gchar *basename; + + basename = get_thumbnail_basename (source); + file = g_file_new_build_filename (g_get_user_cache_dir (), + "thumbnails", + size, + basename, + NULL); + g_free (basename); + return file; +} + +static GFile * +get_failed_thumbnail_file (GFile *source) +{ + GFile *file; + gchar *basename; + + basename = get_thumbnail_basename (source); + file = g_file_new_build_filename (g_get_user_cache_dir (), + "thumbnails", THUMBNAIL_FAIL_SIZE, + "gnome-thumbnail-factory", + basename, + NULL); + g_free (basename); + return file; +} + +static gboolean +check_thumbnail_exists (GFile *source, + const gchar *size) +{ + GFile *thumbnail; + gboolean ret; + + thumbnail = get_expected_thumbnail_file (source, size); + g_assert_nonnull (thumbnail); + + ret = g_file_query_exists (thumbnail, NULL); + g_clear_object (&thumbnail); + + return ret; +} + +static gboolean +check_failed_thumbnail_exists (GFile *source) +{ + GFile *thumbnail; + gboolean ret; + + thumbnail = get_failed_thumbnail_file (source); + g_assert_nonnull (thumbnail); + + ret = g_file_query_exists (thumbnail, NULL); + g_clear_object (&thumbnail); + + return ret; +} + +static GFile * +create_thumbnail (GFile *source, + const gchar *size) +{ + GFile *thumbnail; + GFile *thumbnail_dir; + GError *error = NULL; + gchar *thumbnail_path; + + /* TODO: This is just a stub implementation to create a fake thumbnail file + * We should implement a real thumbnail generator, but we don't care here. + */ + + if (!size || g_strcmp0 (size, THUMBNAIL_FAIL_SIZE) == 0) + thumbnail = get_failed_thumbnail_file (source); + else + thumbnail = get_expected_thumbnail_file (source, size); + + thumbnail_dir = g_file_get_parent (thumbnail); + + if (!g_file_query_exists (thumbnail_dir, NULL)) + { + g_file_make_directory_with_parents (thumbnail_dir, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + } + + g_file_copy (source, thumbnail, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error); + g_assert_no_error (error); + + g_assert_true (g_file_query_exists (thumbnail, NULL)); + thumbnail_path = g_file_get_path (thumbnail); + g_test_message ("Created test thumbnail at %s", thumbnail_path); + + g_clear_object (&thumbnail_dir); + g_clear_error (&error); + g_free (thumbnail_path); + + return thumbnail; +} + +static GFile * +create_thumbnail_from_test_file (const gchar *source_name, + const gchar *size, + GFile **out_source) +{ + GFile *thumbnail; + GFile *source = get_thumbnail_src_file (source_name); + + thumbnail = create_thumbnail (source, size); + + if (!size || g_strcmp0 (size, THUMBNAIL_FAIL_SIZE) == 0) + { + g_assert_true (check_failed_thumbnail_exists (source)); + } + else + { + g_assert_false (check_failed_thumbnail_exists (source)); + g_assert_true (check_thumbnail_exists (source, size)); + } + + if (out_source) + *out_source = g_steal_pointer (&source); + + g_clear_object (&source); + + return thumbnail; +} + +static void +test_valid_thumbnail_size (gconstpointer data) +{ + GFile *source; + GFile *thumbnail; + GError *error = NULL; + GFileInfo *info; + const gchar *size = data; + char *thumbnail_path; + + thumbnail = create_thumbnail_from_test_file ("valid.png", size, &source); + info = g_file_query_info (source, THUMBNAILS_ATTRIBS, G_FILE_QUERY_INFO_NONE, + NULL, &error); + g_assert_no_error (error); + + thumbnail_path = g_file_get_path (thumbnail); + + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH)); + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)); + + g_assert_cmpstr ( + g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH), + ==, + thumbnail_path + ); + + /* TODO: We can't really test this without having a proper thumbnail created + g_assert_true ( + g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + */ + + g_clear_object (&source); + g_clear_object (&thumbnail); + g_clear_error (&error); + g_clear_object (&info); + g_free (thumbnail_path); +} + +static void +test_unknown_thumbnail_size (gconstpointer data) +{ + GFile *source; + GFile *thumbnail; + GError *error = NULL; + GFileInfo *info; + const gchar *size = data; + + thumbnail = create_thumbnail_from_test_file ("valid.png", size, &source); + info = g_file_query_info (source, THUMBNAILS_ATTRIBS, G_FILE_QUERY_INFO_NONE, + NULL, &error); + g_assert_no_error (error); + + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH)); + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)); + + g_clear_object (&source); + g_clear_object (&thumbnail); + g_clear_error (&error); + g_clear_object (&info); +} + +static void +test_failed_thumbnail (void) +{ + GFile *source; + GFile *thumbnail; + GError *error = NULL; + GFileInfo *info; + + thumbnail = create_thumbnail_from_test_file ("valid.png", NULL, &source); + info = g_file_query_info (source, THUMBNAILS_ATTRIBS, G_FILE_QUERY_INFO_NONE, + NULL, &error); + g_assert_no_error (error); + + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH)); + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)); + + g_assert_false ( + g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + g_assert_true ( + g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)); + + g_clear_object (&source); + g_clear_object (&thumbnail); + g_clear_error (&error); + g_clear_object (&info); +} + +static void +test_thumbnails_size_priority (void) +{ + GPtrArray *sized_thumbnails; + GError *error = NULL; + GFileInfo *info; + GFile *source; + GFile *failed_thumbnail; + gsize i; + + failed_thumbnail = create_thumbnail_from_test_file ("valid.png", NULL, &source); + sized_thumbnails = g_ptr_array_new_with_free_func (g_object_unref); + + /* Checking that each thumbnail with higher priority override the previous */ + for (i = 0; i < G_N_ELEMENTS (SIZES_NAMES); i++) + { + GFile *thumbnail = create_thumbnail (source, SIZES_NAMES[i]); + gchar *thumbnail_path = g_file_get_path (thumbnail); + + g_ptr_array_add (sized_thumbnails, thumbnail); + + info = g_file_query_info (source, THUMBNAILS_ATTRIBS, + G_FILE_QUERY_INFO_NONE, NULL, &error); + g_assert_no_error (error); + + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH)); + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)); + + g_assert_cmpstr ( + g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH), + ==, + thumbnail_path + ); + + g_free (thumbnail_path); + g_clear_object (&info); + } + + g_assert_cmpuint (sized_thumbnails->len, ==, G_N_ELEMENTS (SIZES_NAMES)); + + /* Now removing them in the inverse order, to check this again */ + for (i = G_N_ELEMENTS (SIZES_NAMES); i > 1; i--) + { + GFile *thumbnail = g_ptr_array_index (sized_thumbnails, i - 1); + GFile *less_priority_thumbnail = g_ptr_array_index (sized_thumbnails, i - 2); + gchar *thumbnail_path = g_file_get_path (less_priority_thumbnail); + + g_file_delete (thumbnail, NULL, &error); + g_assert_no_error (error); + + info = g_file_query_info (source, THUMBNAILS_ATTRIBS, + G_FILE_QUERY_INFO_NONE, NULL, &error); + g_assert_no_error (error); + + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH)); + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)); + + g_assert_cmpstr ( + g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH), + ==, + thumbnail_path + ); + + g_free (thumbnail_path); + g_clear_object (&info); + } + + /* And now let's remove the last valid one, so that failed should have priority */ + g_file_delete (G_FILE (g_ptr_array_index (sized_thumbnails, 0)), NULL, &error); + g_assert_no_error (error); + + info = g_file_query_info (source, THUMBNAILS_ATTRIBS, G_FILE_QUERY_INFO_NONE, + NULL, &error); + g_assert_no_error (error); + + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH)); + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + g_assert_true (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)); + + g_assert_false ( + g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + g_assert_true ( + g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)); + + g_clear_object (&info); + + /* Removing the failed thumbnail too, so no thumbnail should be available */ + g_file_delete (failed_thumbnail, NULL, &error); + g_assert_no_error (error); + + info = g_file_query_info (source, THUMBNAILS_ATTRIBS, G_FILE_QUERY_INFO_NONE, + NULL, &error); + g_assert_no_error (error); + + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH)); + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID)); + g_assert_false (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)); + + g_clear_object (&source); + g_clear_pointer (&sized_thumbnails, g_ptr_array_unref); + g_clear_object (&failed_thumbnail); + g_clear_error (&error); + g_clear_object (&info); +} + + +int +main (int argc, + char *argv[]) +{ + gsize i; + + g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); + + for (i = 0; i < G_N_ELEMENTS (SIZES_NAMES); i++) + { + gchar *test_path; + + test_path = g_strconcat ("/file-thumbnail/valid/", SIZES_NAMES[i], NULL); + g_test_add_data_func (test_path, SIZES_NAMES[i], test_valid_thumbnail_size); + g_free (test_path); + } + + g_test_add_data_func ("/file-thumbnail/unknown/super-large", "super-large", test_unknown_thumbnail_size); + g_test_add_func ("/file-thumbnail/fail", test_failed_thumbnail); + g_test_add_func ("/file-thumbnail/size-priority", test_thumbnails_size_priority); + + return g_test_run (); +} diff --git a/gio/tests/meson.build b/gio/tests/meson.build index ee939ddfa..92e09a494 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -63,6 +63,7 @@ gio_tests = { 'data-input-stream' : {}, 'data-output-stream' : {}, 'error': {}, + 'file-thumbnail' : {}, 'fileattributematcher' : {}, 'filter-streams' : {}, 'giomodule' : {