From 1cb9d9919cf1cc193ec032825dc365c0eeb942a5 Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Tue, 28 Jan 2025 18:06:43 +0300 Subject: [PATCH 01/10] properties-window: Use existing function for file list attribute query --- src/nautilus-properties-window.c | 77 +++++++++----------------------- 1 file changed, 20 insertions(+), 57 deletions(-) diff --git a/src/nautilus-properties-window.c b/src/nautilus-properties-window.c index 2067f972dd..f5e9fbefd3 100644 --- a/src/nautilus-properties-window.c +++ b/src/nautilus-properties-window.c @@ -414,9 +414,9 @@ typedef struct GtkWindow *parent_window; char *startup_id; char *pending_key; - GHashTable *pending_files; NautilusPropertiesWindowCallback callback; gpointer callback_data; + NautilusFileListHandle *handle; NautilusPropertiesWindow *window; gboolean cancelled; } StartupData; @@ -448,8 +448,8 @@ static void value_row_update (AdwActionRow *row, NautilusPropertiesWindow *self); static void properties_window_update (NautilusPropertiesWindow *self, GList *files); -static void is_directory_ready_callback (NautilusFile *file, - gpointer data); +static void is_directory_ready_callback (GList *file_list, + gpointer data); static void cancel_group_change_callback (GroupChange *change); static void cancel_owner_change_callback (OwnerChange *change); static gboolean all_can_set_permissions (GList *file_list); @@ -3643,7 +3643,6 @@ startup_data_new (GList *files, NautilusPropertiesWindow *window) { StartupData *data; - GList *l; data = g_new0 (StartupData, 1); data->files = nautilus_file_list_copy (files); @@ -3651,17 +3650,10 @@ startup_data_new (GList *files, data->parent_window = parent_window; data->startup_id = g_strdup (startup_id); data->pending_key = g_strdup (pending_key); - data->pending_files = g_hash_table_new (g_direct_hash, - g_direct_equal); data->callback = callback; data->callback_data = callback_data; data->window = window; - for (l = data->files; l != NULL; l = l->next) - { - g_hash_table_insert (data->pending_files, l->data, l->data); - } - return data; } @@ -3669,7 +3661,6 @@ static void startup_data_free (StartupData *data) { nautilus_file_list_free (data->files); - g_hash_table_destroy (data->pending_files); g_free (data->pending_key); g_free (data->startup_id); g_free (data); @@ -3783,17 +3774,6 @@ parent_widget_destroyed_callback (GtkWidget *widget, properties_window_finish ((StartupData *) callback_data); } -static void -cancel_call_when_ready_callback (gpointer key, - gpointer value, - gpointer user_data) -{ - nautilus_file_cancel_call_when_ready - (NAUTILUS_FILE (key), - is_directory_ready_callback, - user_data); -} - static void remove_pending (StartupData *startup_data, gboolean cancel_call_when_ready, @@ -3801,9 +3781,7 @@ remove_pending (StartupData *startup_data, { if (cancel_call_when_ready) { - g_hash_table_foreach (startup_data->pending_files, - cancel_call_when_ready_callback, - startup_data); + nautilus_file_list_cancel_call_when_ready (startup_data->handle); } if (cancel_timed_wait) { @@ -3834,32 +3812,22 @@ widget_on_destroy (GtkWidget *widget, } static void -is_directory_ready_callback (NautilusFile *file, - gpointer data) +is_directory_ready_callback (GList *file_list, + gpointer data) { - StartupData *startup_data; + StartupData *startup_data = data; + NautilusPropertiesWindow *new_window = create_properties_window (startup_data); - startup_data = data; + startup_data->window = new_window; - g_hash_table_remove (startup_data->pending_files, file); + remove_pending (startup_data, FALSE, TRUE); - if (g_hash_table_size (startup_data->pending_files) == 0) - { - NautilusPropertiesWindow *new_window; - - new_window = create_properties_window (startup_data); - - startup_data->window = new_window; - - remove_pending (startup_data, FALSE, TRUE); + adw_dialog_present (ADW_DIALOG (new_window), GTK_WIDGET (startup_data->parent_window)); + g_signal_connect (GTK_WIDGET (new_window), "destroy", + G_CALLBACK (widget_on_destroy), startup_data); - adw_dialog_present (ADW_DIALOG (new_window), GTK_WIDGET (startup_data->parent_window)); - g_signal_connect (GTK_WIDGET (new_window), "destroy", - G_CALLBACK (widget_on_destroy), startup_data); - - /* We wish the label to be selectable, but not selected by default. */ - gtk_label_select_region (GTK_LABEL (new_window->name_value_label), -1, -1); - } + /* We wish the label to be selectable, but not selected by default. */ + gtk_label_select_region (GTK_LABEL (new_window->name_value_label), -1, -1); } void @@ -3869,7 +3837,6 @@ nautilus_properties_window_present (GList *files, NautilusPropertiesWindowCallback callback, gpointer callback_data) { - GList *l, *next; GtkWindow *parent_window; StartupData *startup_data; g_autofree char *pending_key = NULL; @@ -3928,15 +3895,11 @@ nautilus_properties_window_present (GList *files, _("Creating Properties window."), parent_window == NULL ? NULL : GTK_WINDOW (parent_window)); - for (l = startup_data->files; l != NULL; l = next) - { - next = l->next; - nautilus_file_call_when_ready - (NAUTILUS_FILE (l->data), - NAUTILUS_FILE_ATTRIBUTE_INFO, - is_directory_ready_callback, - startup_data); - } + nautilus_file_list_call_when_ready (startup_data->files, + NAUTILUS_FILE_ATTRIBUTE_INFO, + &startup_data->handle, + is_directory_ready_callback, + startup_data); } static void -- GitLab From d7b6b434e99e14b0dde57ab9f147faa198eeb93c Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Tue, 28 Jan 2025 18:06:01 +0300 Subject: [PATCH 02/10] file: Use hash set for file tracking --- src/nautilus-file.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/nautilus-file.c b/src/nautilus-file.c index 962c271254..e3410497df 100644 --- a/src/nautilus-file.c +++ b/src/nautilus-file.c @@ -8226,7 +8226,7 @@ static GList *ready_data_list = NULL; typedef struct { GList *file_list; - GList *remaining_files; + GHashTable *remaining_files; NautilusFileListCallback callback; gpointer callback_data; } FileListReadyData; @@ -8242,7 +8242,7 @@ file_list_ready_data_free (FileListReadyData *data) ready_data_list = g_list_delete_link (ready_data_list, l); nautilus_file_list_free (data->file_list); - g_list_free (data->remaining_files); + g_hash_table_unref (data->remaining_files); g_free (data); } } @@ -8256,10 +8256,15 @@ file_list_ready_data_new (GList *file_list, data = g_new0 (FileListReadyData, 1); data->file_list = nautilus_file_list_copy (file_list); - data->remaining_files = g_list_copy (file_list); + data->remaining_files = g_hash_table_new (NULL, NULL); data->callback = callback; data->callback_data = callback_data; + for (GList *l = file_list; l != NULL; l = l->next) + { + g_hash_table_add (data->remaining_files, l->data); + } + ready_data_list = g_list_prepend (ready_data_list, data); return data; @@ -8272,9 +8277,9 @@ file_list_file_ready_callback (NautilusFile *file, FileListReadyData *data; data = user_data; - data->remaining_files = g_list_remove (data->remaining_files, file); + g_hash_table_remove (data->remaining_files, file); - if (data->remaining_files == NULL) + if (g_hash_table_size (data->remaining_files) == 0) { if (data->callback) { @@ -8334,9 +8339,11 @@ nautilus_file_list_cancel_call_when_ready (NautilusFileListHandle *handle) l = g_list_find (ready_data_list, data); if (l != NULL) { - for (l = data->remaining_files; l != NULL; l = l->next) + g_autoptr (GPtrArray) remaining_files = g_hash_table_steal_all_keys (data->remaining_files); + + for (guint i = 0; i < remaining_files->len; i++) { - file = NAUTILUS_FILE (l->data); + file = NAUTILUS_FILE (remaining_files->pdata[i]); NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->cancel_call_when_ready (file, file_list_file_ready_callback, data); -- GitLab From e78d9e270704e13b5e0c6a07adee65b01acc96a5 Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Fri, 31 Jan 2025 18:39:03 +0300 Subject: [PATCH 03/10] files-view: Fix incorrect size check --- src/nautilus-files-view.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c index b42530dce4..c25320af8c 100644 --- a/src/nautilus-files-view.c +++ b/src/nautilus-files-view.c @@ -4330,7 +4330,7 @@ real_remove_files (NautilusFilesView *self, } } - if (items != NULL) + if (g_hash_table_size (items) > 0) { nautilus_view_model_remove_items (priv->model, items, directory); } -- GitLab From cebd62562e2fee75a62826dcb2a1f9839adbfd71 Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Wed, 5 Feb 2025 22:10:37 +0300 Subject: [PATCH 04/10] files-view: Remove unnecessary reference count --- src/nautilus-files-view.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c index c25320af8c..6e4251f8b6 100644 --- a/src/nautilus-files-view.c +++ b/src/nautilus-files-view.c @@ -1217,7 +1217,7 @@ file_and_directory_get_file (FileAndDirectory *fad) { g_return_val_if_fail (fad != NULL, NULL); - return nautilus_file_ref (fad->file); + return fad->file; } static void @@ -4482,7 +4482,7 @@ process_pending_files (NautilusFilesView *view) files = g_list_copy_deep (files_changed, (GCopyFunc) file_and_directory_get_file, NULL); send_selection_change = _g_lists_sort_and_check_for_intersection (&files, &selection); - nautilus_file_list_free (files); + g_list_free (files); } if (send_selection_change) -- GitLab From 37416c6cfa4cf55d37d48480c2a489e0b33983ba Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Fri, 7 Feb 2025 14:33:28 +0300 Subject: [PATCH 05/10] files-view: Rework file addition Files can be marked as gone before the scheduled call to apply directory changes due to external async events passed by monitors. Rework file addition to tolerate that --- src/nautilus-files-view.c | 45 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c index 6e4251f8b6..b661f247be 100644 --- a/src/nautilus-files-view.c +++ b/src/nautilus-files-view.c @@ -4210,6 +4210,16 @@ still_should_show_file (NautilusFilesView *view, view_file_still_belongs (view, fad); } +static gboolean +still_should_add_file (NautilusFilesView *view, + FileAndDirectory *fad) +{ + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + + return still_should_show_file (view, fad) && + nautilus_view_model_get_item_for_file (priv->model, fad->file) == NULL; +} + static void real_end_file_changes (NautilusFilesView *view) { @@ -4391,32 +4401,29 @@ process_pending_files (NautilusFilesView *view) for (GList *node = files_added; node != NULL; node = node->next) { + gboolean should_add_file; pending = node->data; - if (nautilus_file_is_gone (pending->file)) - { - if (g_getenv ("G_MESSAGES_DEBUG") == NULL) - { - g_warning ("Attempted to add a non-existent file to the view."); - } - else - { - g_autofree char *uri = nautilus_file_get_uri (pending->file); - g_warning ("Attempted to add non-existent file \"%s\" to the view.", uri); - } - continue; - } - if (!nautilus_files_view_should_show_file (view, pending->file)) + should_add_file = still_should_add_file (view, pending); + if (should_add_file) { - continue; + pending_additions = g_list_prepend (pending_additions, pending->file); } - pending_additions = g_list_prepend (pending_additions, pending->file); + /* Acknowledge the files that were pending to be revealed */ if (g_hash_table_contains (priv->pending_reveal, pending->file)) { - g_hash_table_insert (priv->pending_reveal, - pending->file, - GUINT_TO_POINTER (TRUE)); + if (should_add_file) + { + g_hash_table_insert (priv->pending_reveal, + pending->file, + GUINT_TO_POINTER (TRUE)); + } + else + { + g_hash_table_remove (priv->pending_reveal, + pending->file); + } } } pending_additions = g_list_reverse (pending_additions); -- GitLab From f188b493092d5fec34f06cc06957e600bf2cdf77 Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Fri, 31 Jan 2025 19:09:48 +0300 Subject: [PATCH 06/10] data/leak-suppress: Add leaky GTK function --- data/leak-suppress.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/leak-suppress.txt b/data/leak-suppress.txt index c40bc9c021..ae4d1f9924 100644 --- a/data/leak-suppress.txt +++ b/data/leak-suppress.txt @@ -7,4 +7,5 @@ leak:gtk_init leak:adw_init leak:xdg_mime_init leak:gtk_at_context_create -leak:libim-ibus.so \ No newline at end of file +leak:libim-ibus.so +leak:gtk_label_set_mnemonic_widget -- GitLab From b42f12036fae200f849b889e7bda21addd927f64 Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Thu, 11 Jan 2024 16:40:17 +0300 Subject: [PATCH 07/10] test-utilities: Export hierarchy creation function --- test/automated/displayless/test-utilities.c | 2 +- test/automated/displayless/test-utilities.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/automated/displayless/test-utilities.c b/test/automated/displayless/test-utilities.c index f00359034f..5d138d56b2 100644 --- a/test/automated/displayless/test-utilities.c +++ b/test/automated/displayless/test-utilities.c @@ -57,7 +57,7 @@ empty_directory_by_prefix (GFile *parent, } } -static void +void create_hierarchy_from_template (const GStrv hier, const gchar *substitution) { diff --git a/test/automated/displayless/test-utilities.h b/test/automated/displayless/test-utilities.h index 3c1247814d..4e440e2ae1 100644 --- a/test/automated/displayless/test-utilities.h +++ b/test/automated/displayless/test-utilities.h @@ -16,6 +16,9 @@ void test_clear_tmp_dir (void); void empty_directory_by_prefix (GFile *parent, gchar *prefix); +void create_hierarchy_from_template (const GStrv hier, + const gchar *substitution); + void create_search_file_hierarchy (gchar *search_engine); void delete_search_file_hierarchy (gchar *search_engine); -- GitLab From 43678bdca2ea7b4c0a3c75117cc75ce5bb408538 Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Fri, 31 Jan 2025 19:02:51 +0300 Subject: [PATCH 08/10] test-utilities: Rework utilities to be a dependency --- test/automated/display/meson.build | 7 +------ test/automated/displayless/meson.build | 4 ++-- test/automated/meson.build | 16 ++++++++++++++++ .../automated/{displayless => }/test-utilities.c | 0 .../automated/{displayless => }/test-utilities.h | 0 5 files changed, 19 insertions(+), 8 deletions(-) rename test/automated/{displayless => }/test-utilities.c (100%) rename test/automated/{displayless => }/test-utilities.h (100%) diff --git a/test/automated/display/meson.build b/test/automated/display/meson.build index c0506f1422..2e42ee8e27 100644 --- a/test/automated/display/meson.build +++ b/test/automated/display/meson.build @@ -1,8 +1,3 @@ -test_env += [ - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()) -] - tests = [ ['test-nautilus-directory-async', [ 'test-nautilus-directory-async.c' @@ -12,7 +7,7 @@ tests = [ foreach t: tests test( t[0], - executable(t[0], t[1], dependencies: libnautilus_dep), + executable(t[0], t[1], dependencies: [libnautilus_dep, libtestutils_dep]), env: [ test_env, 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), diff --git a/test/automated/displayless/meson.build b/test/automated/displayless/meson.build index 0d18bf5134..4aaf36a9e1 100644 --- a/test/automated/displayless/meson.build +++ b/test/automated/displayless/meson.build @@ -54,7 +54,7 @@ tracker_tests = [ foreach t: tests test( t[0], - executable(t[0], t[1], files('test-utilities.c'), dependencies: libnautilus_dep), + executable(t[0], t[1], dependencies: [libnautilus_dep, libtestutils_dep]), env: [ test_env, 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), @@ -70,7 +70,7 @@ endforeach # Tests that read and write from the Tracker index are run using 'tracker-sandbox' # script to use a temporary instance of tracker-miner-fs instead of the session one. foreach t: tracker_tests - test_exe = executable(t[0], t[1], files('test-utilities.c'), dependencies: libnautilus_dep) + test_exe = executable(t[0], t[1], dependencies: [libnautilus_dep, libtestutils_dep]) test( t[0], tracker_sandbox, diff --git a/test/automated/meson.build b/test/automated/meson.build index a1122423a8..dd97bdc76e 100644 --- a/test/automated/meson.build +++ b/test/automated/meson.build @@ -1,3 +1,19 @@ +test_utils = files('test-utilities.c') + +libtestutils = static_library( + 'testutils', + test_utils, + dependencies: libnautilus_dep, + include_directories: include_directories('.') +) + +libtestutils_dep = declare_dependency( + link_with: libtestutils, + include_directories: include_directories('.'), + dependencies: libnautilus_dep, + sources: resources +) + subdir('displayless') if get_option('tests') == 'all' subdir('display') diff --git a/test/automated/displayless/test-utilities.c b/test/automated/test-utilities.c similarity index 100% rename from test/automated/displayless/test-utilities.c rename to test/automated/test-utilities.c diff --git a/test/automated/displayless/test-utilities.h b/test/automated/test-utilities.h similarity index 100% rename from test/automated/displayless/test-utilities.h rename to test/automated/test-utilities.h -- GitLab From d77a8577398e0ffa1b272f9d6c402e4a3d292941 Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Sat, 1 Feb 2025 20:08:03 +0300 Subject: [PATCH 09/10] test/utilities: Add config setup function --- test/automated/test-utilities.c | 40 +++++++++++++++++++++++++++++++++ test/automated/test-utilities.h | 2 ++ 2 files changed, 42 insertions(+) diff --git a/test/automated/test-utilities.c b/test/automated/test-utilities.c index 5d138d56b2..b04d28f90c 100644 --- a/test/automated/test-utilities.c +++ b/test/automated/test-utilities.c @@ -23,6 +23,46 @@ test_clear_tmp_dir (void) } } +static gboolean config_dir_initialized = FALSE; + +void +test_init_config_dir (void) +{ + if (config_dir_initialized == FALSE) + { + /* Initialize bookmarks */ + g_autofree gchar *gtk3_dir = g_build_filename (g_get_user_config_dir (), + "gtk-3.0", + NULL); + g_autofree gchar *bookmarks_path = g_build_filename (gtk3_dir, + "bookmarks", + NULL); + g_autoptr (GFile) bookmarks_file = g_file_new_for_path (bookmarks_path); + g_autoptr (GError) error = NULL; + + if (g_mkdir_with_parents (gtk3_dir, 0700) == -1) + { + int saved_errno = errno; + + g_error ("Failed to create bookmarks folder %s: %s", + gtk3_dir, g_strerror (saved_errno)); + return; + } + + g_autoptr (GFileOutputStream) stream = g_file_replace (bookmarks_file, + NULL, + FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, + NULL, &error); + g_assert_no_error (error); + g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &error); + g_assert_no_error (error); + + g_debug ("Initialized config folder %s", g_get_user_config_dir ()); + config_dir_initialized = TRUE; + } +} + void empty_directory_by_prefix (GFile *parent, gchar *prefix) diff --git a/test/automated/test-utilities.h b/test/automated/test-utilities.h index 4e440e2ae1..b36943ffa6 100644 --- a/test/automated/test-utilities.h +++ b/test/automated/test-utilities.h @@ -13,6 +13,8 @@ const gchar *test_get_tmp_dir (void); void test_clear_tmp_dir (void); +void test_init_config_dir (void); + void empty_directory_by_prefix (GFile *parent, gchar *prefix); -- GitLab From 17eef249b6c3f523060260f404ca029831ff772b Mon Sep 17 00:00:00 2001 From: Khalid Abu Shawarib Date: Fri, 31 Jan 2025 19:06:51 +0300 Subject: [PATCH 10/10] test/files-view: Add basic unit tests for NautilusFilesView This tests loading, addition and removal of files. --- test/automated/display/meson.build | 3 + test/automated/display/test-files-view.c | 370 +++++++++++++++++++++++ test/automated/test-utilities.h | 11 + 3 files changed, 384 insertions(+) create mode 100644 test/automated/display/test-files-view.c diff --git a/test/automated/display/meson.build b/test/automated/display/meson.build index 2e42ee8e27..c7742d7ede 100644 --- a/test/automated/display/meson.build +++ b/test/automated/display/meson.build @@ -1,4 +1,7 @@ tests = [ + ['test-files-view', [ + 'test-files-view.c' + ]], ['test-nautilus-directory-async', [ 'test-nautilus-directory-async.c' ]], diff --git a/test/automated/display/test-files-view.c b/test/automated/display/test-files-view.c new file mode 100644 index 0000000000..0a42927263 --- /dev/null +++ b/test/automated/display/test-files-view.c @@ -0,0 +1,370 @@ +/* + * Copyright © 2025 Khalid Abu Shawarib + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#undef G_LOG_DOMAIN +#include +#undef G_LOG_DOMAIN +#include + +#define G_LOG_DOMAIN "test-files-view" + +static gboolean +ptr_arrays_equal_unordered (GPtrArray *a, + GPtrArray *b) +{ + if (a->len != b->len) + { + return FALSE; + } + + for (guint i = 0; i < a->len; i++) + { + if (!g_ptr_array_find (b, a->pdata[i], NULL)) + { + return FALSE; + } + } + + return TRUE; +} + +static void +set_true (gboolean *data) +{ + *data = TRUE; +} + +static void +collect_renamed_files (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + GPtrArray *renamed_files_arr = callback_data; + + g_ptr_array_add (renamed_files_arr, nautilus_file_ref (file)); +} + +static void +collect_changed_files (NautilusFilesView *view, + NautilusFile *file, + NautilusDirectory *directory, + GPtrArray *data_files) +{ + /* Ignore duplicate change emissions originating from nautilus-directory */ + if (!g_ptr_array_find (data_files, file, NULL)) + { + g_ptr_array_add (data_files, nautilus_file_ref (file)); + } +} + +static void +test_rename_files (void) +{ + g_autoptr (NautilusWindowSlot) slot = g_object_ref_sink (nautilus_window_slot_new (NAUTILUS_MODE_BROWSE)); + g_autoptr (NautilusFilesView) files_view = nautilus_files_view_new (NAUTILUS_VIEW_GRID_ID, slot); + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (files_view); + g_autoptr (GFile) tmp_location = g_file_new_for_path (test_get_tmp_dir ()); + const guint file_count = 10, renamed_file_count = 5; + g_autoptr (GPtrArray) file_arr = g_ptr_array_new_full (file_count, + (GDestroyNotify) nautilus_file_unref); + g_autoptr (GPtrArray) renamed_files_arr = g_ptr_array_new_full (renamed_file_count, + (GDestroyNotify) nautilus_file_unref); + g_autoptr (GPtrArray) callback_arr = g_ptr_array_new_full (renamed_file_count, + (GDestroyNotify) nautilus_file_unref); + gboolean added_files = FALSE, removed_files = FALSE; + + /* Create the files before loading the view and keep them in an array. */ + for (guint i = 0; i < file_count; i++) + { + g_autofree gchar *file_name = g_strdup_printf ("test_file_%i", i); + g_autoptr (GFile) file = g_file_get_child (tmp_location, file_name); + g_autoptr (GFileOutputStream) out = g_file_create (file, G_FILE_CREATE_NONE, NULL, NULL); + + g_assert_nonnull (out); + g_ptr_array_add (file_arr, nautilus_file_get (file)); + } + + nautilus_view_set_location (NAUTILUS_VIEW (files_view), tmp_location); + ITER_CONTEXT_WHILE (nautilus_files_view_get_loading (files_view)); + + g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (priv->model)), ==, file_count); + + /* Rename only some of the files and verify that they are emmited, but + * assert that no files are "added" or "removed". */ + g_signal_connect_swapped (files_view, "add-files", G_CALLBACK (set_true), &added_files); + g_signal_connect_swapped (files_view, "remove-file", G_CALLBACK (set_true), &removed_files); + g_signal_connect (files_view, "file-changed", + G_CALLBACK (collect_changed_files), callback_arr); + + for (guint i = 0; i < renamed_file_count; i++) + { + NautilusFile *file = file_arr->pdata[i]; + g_autoptr (GFile) location = nautilus_file_get_location (file); + g_autofree gchar *file_name = g_strdup_printf ("test_file_%i.txt", i); + nautilus_file_rename (file, file_name, collect_renamed_files, renamed_files_arr); + } + + ITER_CONTEXT_WHILE (callback_arr->len != renamed_file_count && + renamed_files_arr->len != renamed_file_count && + !ptr_arrays_equal_unordered (callback_arr, renamed_files_arr)); + + g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (priv->model)), ==, file_count); + g_assert_false (added_files); + g_assert_false (removed_files); + + /* Both renamed and non-renamed nautilus file pointers from before renaming + * should still point to the same files in the view. Renaming should not + * make the old pointers invalid since we're using nautilus_file_rename(). */ + for (guint i = 0; i < file_arr->len; i++) + { + NautilusFile *file = file_arr->pdata[i]; + NautilusViewItem *item = nautilus_view_model_get_item_for_file (priv->model, file); + + g_assert_nonnull (item); + } + + test_clear_tmp_dir (); +} + +static void +collect_removed_files_cb (NautilusFilesView *view, + GList *removed_files, + NautilusDirectory *directory, + GPtrArray *data_files) +{ + for (GList *link = removed_files; link != NULL; link = link->next) + { + g_ptr_array_add (data_files, nautilus_file_ref (NAUTILUS_FILE (link->data))); + } +} + +static void +test_remove_files (void) +{ + g_autoptr (NautilusWindowSlot) slot = g_object_ref_sink (nautilus_window_slot_new (NAUTILUS_MODE_BROWSE)); + g_autoptr (NautilusFilesView) files_view = nautilus_files_view_new (NAUTILUS_VIEW_GRID_ID, slot); + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (files_view); + g_autoptr (GFile) tmp_location = g_file_new_for_path (test_get_tmp_dir ()); + g_autofree gchar *uri = g_file_get_uri (tmp_location); + const guint file_count = 10, deleted_file_count = 5; + g_autoptr (GPtrArray) file_arr = g_ptr_array_new_full (file_count, + (GDestroyNotify) nautilus_file_unref); + g_autoptr (GPtrArray) deleted_files_arr = g_ptr_array_new (); + g_autoptr (GPtrArray) callback_arr = g_ptr_array_new_full (file_count, + (GDestroyNotify) nautilus_file_unref); + + /* Create the files before loading the view and keep them in an array. */ + for (guint i = 0; i < file_count; i++) + { + g_autofree gchar *file_name = g_strdup_printf ("test_file_%i", i); + g_autoptr (GFile) file = g_file_get_child (tmp_location, file_name); + g_autoptr (GFileOutputStream) out = g_file_create (file, G_FILE_CREATE_NONE, NULL, NULL); + + g_assert_nonnull (out); + g_ptr_array_add (file_arr, nautilus_file_get (file)); + } + + nautilus_view_set_location (NAUTILUS_VIEW (files_view), tmp_location); + + ITER_CONTEXT_WHILE (nautilus_files_view_get_loading (files_view)); + + g_assert_true (g_file_equal (tmp_location, + nautilus_view_get_location (NAUTILUS_VIEW (files_view)))); + g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (priv->model)), ==, file_count); + + + /* Delete only some of the files and verify that they are emmited */ + g_signal_connect (files_view, "remove-file", + G_CALLBACK (collect_removed_files_cb), callback_arr); + + for (guint i = 0; i < deleted_file_count; i++) + { + g_autoptr (GFile) location = nautilus_file_get_location (file_arr->pdata[i]); + g_autoptr (GError) error = NULL; + g_file_delete (location, NULL, &error); + + g_assert_null (error); + g_ptr_array_add (deleted_files_arr, file_arr->pdata[i]); + } + + ITER_CONTEXT_WHILE (!ptr_arrays_equal_unordered (deleted_files_arr, callback_arr)); + + g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (priv->model)), + ==, + file_count - deleted_file_count); + + for (guint i = 0; i < file_arr->len; i++) + { + NautilusFile *file = file_arr->pdata[i]; + NautilusViewItem *item = nautilus_view_model_get_item_for_file (priv->model, file); + + if (g_ptr_array_find (deleted_files_arr, file, NULL)) + { + g_assert_null (item); + } + else + { + g_assert_nonnull (item); + } + } + + test_clear_tmp_dir (); +} + +static void +collect_added_files_cb (NautilusFilesView *view, + GList *added_files, + GPtrArray *data_files) +{ + for (GList *link = added_files; link != NULL; link = link->next) + { + g_ptr_array_add (data_files, nautilus_file_ref (NAUTILUS_FILE (link->data))); + } +} + +static void +test_add_files (void) +{ + g_autoptr (NautilusWindowSlot) slot = g_object_ref_sink (nautilus_window_slot_new (NAUTILUS_MODE_BROWSE)); + g_autoptr (NautilusFilesView) files_view = nautilus_files_view_new (NAUTILUS_VIEW_GRID_ID, slot); + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (files_view); + g_autoptr (GFile) tmp_location = g_file_new_for_path (test_get_tmp_dir ()); + g_autofree gchar *uri = g_file_get_uri (tmp_location); + const guint file_count = 10; + g_autoptr (GPtrArray) file_arr = g_ptr_array_new_full (file_count, + (GDestroyNotify) nautilus_file_unref); + g_autoptr (GPtrArray) callback_arr = g_ptr_array_new_full (file_count, + (GDestroyNotify) nautilus_file_unref); + + nautilus_view_set_location (NAUTILUS_VIEW (files_view), tmp_location); + + ITER_CONTEXT_WHILE (nautilus_files_view_get_loading (files_view)); + + g_assert_true (g_file_equal (tmp_location, + nautilus_view_get_location (NAUTILUS_VIEW (files_view)))); + g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (priv->model)), ==, 0); + + /* Keep a list of NautilusFiles */ + for (guint i = 0; i < file_count; i++) + { + g_autofree gchar *file_name = g_strdup_printf ("test_file_%i", i); + g_autoptr (GFile) location = g_file_get_child (tmp_location, file_name); + + g_ptr_array_add (file_arr, nautilus_file_get (location)); + } + + /* Create files and verify that the view added the correct files */ + g_signal_connect (files_view, "add-files", G_CALLBACK (collect_added_files_cb), callback_arr); + + for (guint i = 0; i < file_arr->len; i++) + { + g_autoptr (GFile) location = nautilus_file_get_location (file_arr->pdata[i]); + g_autoptr (GFileOutputStream) out = g_file_create (location, + G_FILE_CREATE_NONE, NULL, NULL); + + g_assert_nonnull (out); + } + + ITER_CONTEXT_WHILE (!ptr_arrays_equal_unordered (file_arr, callback_arr)); + + g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (priv->model)), ==, file_count); + + for (guint i = 0; i < file_arr->len; i++) + { + NautilusFile *file = file_arr->pdata[i]; + + g_assert_nonnull (nautilus_view_model_get_item_for_file (priv->model, file)); + } + + test_clear_tmp_dir (); +} + +static void +test_load_dir (void) +{ + g_autoptr (NautilusWindowSlot) slot = g_object_ref_sink (nautilus_window_slot_new (NAUTILUS_MODE_BROWSE)); + g_autoptr (NautilusFilesView) files_view = nautilus_files_view_new (NAUTILUS_VIEW_GRID_ID, slot); + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (files_view); + g_autoptr (GFile) tmp_location = g_file_new_for_path (test_get_tmp_dir ()); + g_autofree gchar *uri = g_file_get_uri (tmp_location); + gboolean loading_started = FALSE, loading_ended = FALSE; + const guint file_count = 10; + + create_multiple_files ("view_test", file_count); + + /* Verify loading signals and location */ + nautilus_view_set_location (NAUTILUS_VIEW (files_view), tmp_location); + g_signal_connect_swapped (files_view, "begin-loading", G_CALLBACK (set_true), &loading_started); + g_signal_connect_swapped (files_view, "end-loading", G_CALLBACK (set_true), &loading_ended); + + g_assert_true (nautilus_files_view_get_loading (files_view)); + + ITER_CONTEXT_WHILE (nautilus_files_view_get_loading (files_view)); + + g_autofree gchar *view_uri = nautilus_files_view_get_uri (files_view); + + g_assert_true (g_file_equal (tmp_location, + nautilus_view_get_location (NAUTILUS_VIEW (files_view)))); + g_assert_cmpstr (view_uri, ==, uri); + g_assert_true (loading_started); + g_assert_true (loading_ended); + + /* Verify that files exist exist in the model */ + g_autoptr (GFileEnumerator) enumerator; + g_autoptr (GError) error = NULL; + GFile *child; + guint counter = 0; + + enumerator = g_file_enumerate_children (tmp_location, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_TYPE_FILE_QUERY_INFO_FLAGS, + NULL, + &error); + g_assert_no_error (error); + + /* Account for the extra folder */ + g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (priv->model)), ==, file_count + 1); + while (g_file_enumerator_iterate (enumerator, NULL, &child, NULL, NULL) && child != NULL) + { + g_autoptr (NautilusFile) file = nautilus_file_get (child); + g_assert_nonnull (nautilus_view_model_get_item_for_file (priv->model, file)); + counter++; + } + g_assert_cmpint (counter, ==, file_count + 1); + + test_clear_tmp_dir (); +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr (NautilusTagManager) tag_manager = NULL; + + gtk_test_init (&argc, &argv, NULL); + + nautilus_register_resource (); + nautilus_ensure_extension_points (); + nautilus_global_preferences_init (); + tag_manager = nautilus_tag_manager_new_dummy (); + test_init_config_dir (); + + g_autoptr (NautilusApplication) app = nautilus_application_new (); + + g_test_add_func ("/view/load_dir", + test_load_dir); + g_test_add_func ("/view/add_files", + test_add_files); + g_test_add_func ("/view/remove_files", + test_remove_files); + g_test_add_func ("/view/change_files/rename", + test_rename_files); + + return g_test_run (); +} diff --git a/test/automated/test-utilities.h b/test/automated/test-utilities.h index b36943ffa6..359aed4fb4 100644 --- a/test/automated/test-utilities.h +++ b/test/automated/test-utilities.h @@ -10,6 +10,17 @@ #pragma once +#define MAX_CONTEXT_ITERATIONS 100 + +#define ITER_CONTEXT_WHILE(CONDITION) \ + for (guint i = 0; \ + i < MAX_CONTEXT_ITERATIONS && \ + CONDITION; \ + i++) \ + { \ + g_main_context_iteration (NULL, TRUE); \ + } + const gchar *test_get_tmp_dir (void); void test_clear_tmp_dir (void); -- GitLab