Commit eaf3c3ec authored by Ernestas Kulik's avatar Ernestas Kulik 🦑

Implement renaming with change notifications

This is fairly bare-bones in that there is still a couple of
problems to solve before this is actually useful.

1. Rename task: error signaling? Possible solutions are:
    1. Block thread on each error, fire a signal and wait for return
    value.
    2. Collect all errors, fire a signal, wait for return value.
    3. Pass errors to the “finished” signal.
2. Although unlikely, but, in theory, the changes queue could never get
   flushed, as the timer is reset with each push (a hardcoded limit can
   be used, which would force a flush - sound familiar? :p).
3. Rename task (again): the “finished” signal will usually arrive before
   changed files and their parents are notified, which presents a
   glorious race to work around in the test executable (quitting the
   main loop, namely).

There is probably something else that I forgot to mention.
parent 6fe2ce60
#include <locale.h>
#include <stdlib.h>
#include <glib.h>
......@@ -5,6 +6,7 @@
#include "nautilus-directory.h"
#include "nautilus-file.h"
#include "nautilus-task-manager.h"
#include "tasks/nautilus-rename-task.h"
static void
got_info (NautilusFile *file,
......@@ -12,12 +14,12 @@ got_info (NautilusFile *file,
GError *error,
gpointer user_data)
{
g_message ("Got info for %p",
(gpointer) file);
g_message ("\tDisplay name: %s",
g_file_info_get_display_name (info));
g_message ("\tFile is directory: %s\n",
NAUTILUS_IS_DIRECTORY (file)? "yes" : "no");
g_print ("Got info for %p\n"
"\tDisplay name: %s\n"
"\tFile is directory: %s\n\n",
(gpointer) file,
g_file_info_get_display_name (info),
NAUTILUS_IS_DIRECTORY (file)? "yes" : "no");
g_object_unref (info);
......@@ -33,7 +35,7 @@ got_children (NautilusDirectory *directory,
GError *error,
gpointer user_data)
{
g_message ("Got children for %p", (gpointer) directory);
g_print ("Got children for %p\n", (gpointer) directory);
if (children == NULL)
{
......@@ -61,34 +63,25 @@ got_children (NautilusDirectory *directory,
g_list_free (children);
}
int
main (int argc,
char **argv)
static void
perform_self_test_checks (const gchar *path)
{
g_autoptr (NautilusTaskManager) manager = NULL;
g_autoptr (GFile) location = NULL;
g_autoptr (NautilusFile) file = NULL;
g_autoptr (NautilusFile) duplicate_file = NULL;
GMainLoop *loop;
if (!(argc > 1))
{
g_message ("No file provided, exiting");
return EXIT_SUCCESS;
}
manager = nautilus_task_manager_dup_singleton ();
location = g_file_new_for_commandline_arg (argv[1]);
location = g_file_new_for_path (path);
g_message ("Creating NautilusFile");
g_print ("Creating NautilusFile\n");
file = nautilus_file_new (location);
g_message ("\tGot %p\n", (gpointer) file);
g_print ("\tGot %p\n\n", (gpointer) file);
g_message ("Creating another NautilusFile for the same location");
g_print ("Creating another NautilusFile for the same location\n");
duplicate_file = nautilus_file_new (location);
g_message ("\tGot %p, which is %s\n",
(gpointer) duplicate_file,
file == duplicate_file? "the same" : "not the same");
g_print ("\tGot %p, which is %s\n\n",
(gpointer) duplicate_file,
file == duplicate_file? "the same" : "not the same");
loop = g_main_loop_new (NULL, TRUE);
......@@ -110,6 +103,94 @@ main (int argc,
}
g_main_loop_run (loop);
}
static void
rename (const gchar *target,
const gchar *name)
{
g_autoptr (GFile) location = NULL;
g_autoptr (NautilusFile) file = NULL;
g_autoptr (NautilusTaskManager) manager = NULL;
g_autoptr (NautilusTask) task = NULL;
GMainLoop *loop;
location = g_file_new_for_path (target);
g_message ("Constructed GFile %p for path %s",
(gpointer) location, target);
file = nautilus_file_new (location);
g_message ("Constructed NautilusFile %p for location %p",
(gpointer) file, (gpointer) location);
manager = nautilus_task_manager_dup_singleton ();
task = nautilus_rename_task_new ();
loop = g_main_loop_new (NULL, TRUE);
nautilus_rename_task_add_target (NAUTILUS_RENAME_TASK (task),
location, name);
nautilus_task_manager_queue_task (manager, task);
g_main_loop_run (loop);
}
int
main (int argc,
char **argv)
{
g_autoptr (GOptionContext) option_context = NULL;
gchar **files = NULL;
gboolean check = FALSE;
gchar *new_name = NULL;
const GOptionEntry option_entries[] =
{
{
G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE,
G_OPTION_ARG_FILENAME_ARRAY, &files, NULL, NULL
},
{
"check", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &check,
"Perform self-test checks with FILE as input", NULL
},
{
"rename", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &new_name,
"Rename FILE to NAME", "NAME"
},
{ NULL }
};
GError *error = NULL;
g_autoptr (NautilusTaskManager) manager = NULL;
setlocale (LC_ALL, "");
option_context = g_option_context_new ("[FILE]");
g_option_context_add_main_entries (option_context, option_entries, NULL);
if (!g_option_context_parse (option_context, &argc, &argv, &error))
{
g_print ("%s\n", error->message);
return EXIT_FAILURE;
}
if (files == NULL)
{
g_print ("No input file specified\n");
return EXIT_FAILURE;
}
manager = nautilus_task_manager_dup_singleton ();
if (check)
{
perform_self_test_checks (files[0]);
}
if (new_name != NULL && new_name[0] != '\0')
{
rename (files[0], new_name);
}
return EXIT_SUCCESS;
}
nautilus_ng_sources = ['nautilus-task.c',
'nautilus-task.h',
'nautilus-task-private.h',
'nautilus-task-manager.c',
'nautilus-task-manager.h',
'nautilus-file.c',
......@@ -12,6 +13,12 @@ nautilus_ng_sources = ['nautilus-task.c',
'tasks/nautilus-enumerate-children-task.h',
'nautilus-cache.c',
'nautilus-cache.h',
'tasks/nautilus-rename-task.c',
'tasks/nautilus-rename-task.h',
'nautilus-file-changes.c',
'nautilus-file-changes.h',
'nautilus-signal-utilities.c',
'nautilus-signal-utilities.h',
'main.c']
nautilus_ng_dependencies = [gio, glib]
......
......@@ -13,7 +13,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nautilus. If not, see <http://www.gnu.org/licenses/>.
* along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nautilus-cache.h"
......
......@@ -13,7 +13,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nautilus. If not, see <http://www.gnu.org/licenses/>.
* along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
*/
#include <glib.h>
......
......@@ -13,7 +13,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nautilus. If not, see <http://www.gnu.org/licenses/>.
* along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nautilus-directory.h"
......@@ -37,9 +37,34 @@ typedef struct
G_DEFINE_TYPE_WITH_PRIVATE (NautilusDirectory, nautilus_directory,
NAUTILUS_TYPE_FILE)
enum
{
CHILDREN_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
children_changed (NautilusDirectory *directory)
{
g_message ("Children changed in NautilusDirectory %p",
(gpointer) directory);
}
static void
nautilus_directory_class_init (NautilusDirectoryClass *klass)
{
klass->children_changed = children_changed;
signals[CHILDREN_CHANGED] = g_signal_new ("children-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusDirectoryClass, children_changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
static void
......
......@@ -13,7 +13,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nautilus. If not, see <http://www.gnu.org/licenses/>.
* along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nautilus-file.h"
......@@ -32,6 +32,8 @@ typedef void (*NautilusEnumerateChildrenCallback) (NautilusDirectory *directory,
struct _NautilusDirectoryClass
{
NautilusFileClass parent_class;
void (*children_changed) (NautilusDirectory *directory);
};
void nautilus_directory_enumerate_children (NautilusDirectory *directory,
......
/* Copyright (C) 2017 Ernestas Kulik <ernestask@gnome.org>
*
* This file is part of Nautilus.
*
* Nautilus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Nautilus 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nautilus-file-changes.h"
#include "nautilus-file.h"
#include "nautilus-signal-utilities.h"
typedef struct
{
NautilusFileChange type;
GFile *location;
} Change;
typedef struct
{
NautilusFileChange type;
GFile *location_from;
GFile *location_to;
} MoveChange;
static void move_change_free (MoveChange *change);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MoveChange, move_change_free)
static guint source = 0;
static GMutex source_mutex;
static void
move_change_free (MoveChange *change)
{
g_clear_object (&change->location_to);
g_clear_object (&change->location_from);
g_free (change);
}
static gpointer
init_default_queue (gpointer data)
{
return g_async_queue_new ();
}
static GAsyncQueue *
get_default_queue (void)
{
static GOnce once = G_ONCE_INIT;
g_once (&once, init_default_queue, NULL);
return once.retval;
}
static gboolean
emit_signals (gpointer user_data)
{
GAsyncQueue *queue;
Change *change;
queue = user_data;
g_async_queue_lock (queue);
while ((change = g_async_queue_try_pop_unlocked (queue)) != NULL)
{
g_autoptr (NautilusFile) file = NULL;
g_autoptr (NautilusFile) parent = NULL;
file = nautilus_file_new (change->location);
if (file == NULL)
{
continue;
}
parent = nautilus_file_get_parent (file);
switch (change->type)
{
case NAUTILUS_FILE_CHANGE_RENAMED:
{
g_autoptr (MoveChange) move_change = NULL;
move_change = (MoveChange *) change;
nautilus_emit_signal_in_main_context_by_name (file,
NULL,
"renamed",
move_change->location_to);
if (parent == NULL)
{
break;
}
nautilus_emit_signal_in_main_context_by_name (parent,
NULL,
"children-changed");
}
break;
}
}
g_async_queue_unlock (queue);
g_mutex_lock (&source_mutex);
source = 0;
g_mutex_unlock (&source_mutex);
return G_SOURCE_REMOVE;
}
static void
schedule_signal_emission (void)
{
g_mutex_lock (&source_mutex);
if (source == 0)
{
source = g_timeout_add (100, emit_signals, get_default_queue ());
}
else
{
g_source_remove (source);
}
g_mutex_unlock (&source_mutex);
}
static void
notify_file_moved_or_renamed (GFile *from,
GFile *to,
gboolean move_is_rename)
{
MoveChange *change;
GAsyncQueue *queue;
change = g_new0 (MoveChange, 1);
queue = get_default_queue ();
change->type = move_is_rename? NAUTILUS_FILE_CHANGE_RENAMED
: NAUTILUS_FILE_CHANGE_MOVED;
change->location_from = g_object_ref (from);
change->location_to = g_object_ref (to);
g_async_queue_push (queue, change);
schedule_signal_emission ();
}
void
nautilus_notify_file_renamed (GFile *location,
GFile *new_location)
{
notify_file_moved_or_renamed (location, new_location, TRUE);
}
/* Copyright (C) 2017 Ernestas Kulik <ernestask@gnome.org>
*
* This file is part of Nautilus.
*
* Nautilus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Nautilus 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef NAUTILUS_FILE_CHANGES_H_INCLUDED
#define NAUTILUS_FILE_CHANGES_H_INCLUDED
#include <gio/gio.h>
void nautilus_notify_file_renamed (GFile *location,
GFile *new_location);
#endif
......@@ -13,7 +13,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nautilus. If not, see <http://www.gnu.org/licenses/>.
* along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nautilus-file.h"
......@@ -45,9 +45,16 @@ enum
N_PROPERTIES
};
enum
{
RENAMED,
LAST_SIGNAL
};
static GParamSpec *properties[N_PROPERTIES] = { NULL };
static GHashTable *files = NULL;
static GMutex files_mutex;
static guint signals[LAST_SIGNAL] = { 0 };
static GHashTable *files = NULL;
static GMutex files_mutex;
static GObject *
constructor (GType type,
......@@ -136,6 +143,32 @@ finalize (GObject *object)
G_OBJECT_CLASS (nautilus_file_parent_class)->finalize (object);
}
static void
renamed (NautilusFile *file,
GFile *new_location)
{
NautilusFilePrivate *priv;
priv = nautilus_file_get_instance_private (file);
g_message ("NautilusFile %p renamed; changing location: %p -> %p",
(gpointer) file, (gpointer) priv->location,
(gpointer) new_location);
g_mutex_lock (&files_mutex);
g_hash_table_remove (files, priv->location);
priv->location = g_object_ref (new_location);
g_assert (g_hash_table_insert (files, new_location, file));
g_mutex_unlock (&files_mutex);
nautilus_cache_item_invalidate (priv->cache, priv->cache_items[INFO],
FALSE);
}
static void
nautilus_file_class_init (NautilusFileClass *klass)
{
......@@ -147,12 +180,24 @@ nautilus_file_class_init (NautilusFileClass *klass)
object_class->set_property = set_property;
object_class->finalize = finalize;
klass->renamed = renamed;
properties[PROP_LOCATION] =
g_param_spec_object ("location", "Location", "Location",
G_TYPE_FILE,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME);
g_object_class_install_properties (object_class, N_PROPERTIES, properties);
signals[RENAMED] = g_signal_new ("renamed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusFileClass, renamed),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
}
static void
......@@ -278,6 +323,29 @@ nautilus_file_query_info (NautilusFile *file,
nautilus_task_manager_queue_task (manager, task);
}
NautilusFile *
nautilus_file_get_existing (GFile *location)
{
NautilusFile *file = NULL;
g_return_val_if_fail (G_IS_FILE (location), NULL);
g_mutex_lock (&files_mutex);
if (files != NULL)
{
file = g_hash_table_lookup (files, location);
if (file != NULL)
{
file = g_object_ref (file);
}
}
g_mutex_unlock (&files_mutex);
return file;
}
GFile *
nautilus_file_get_location (NautilusFile *file)
{
......@@ -290,6 +358,26 @@ nautilus_file_get_location (NautilusFile *file)
return g_object_ref (priv->location);
}
NautilusFile *
nautilus_file_get_parent (NautilusFile *file)
{
NautilusFilePrivate *priv;
g_autoptr (GFile) parent_location = NULL;
NautilusFile *parent = NULL;
g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
priv = nautilus_file_get_instance_private (file);
parent_location = g_file_get_parent (priv->location);
if (parent_location != NULL)
{
parent = nautilus_file_new (parent_location);
}
return parent;
}
NautilusFile *
nautilus_file_new_with_info (GFile *location,
GFileInfo *info)
......@@ -314,10 +402,17 @@ nautilus_file_new_with_info (GFile *location,
NautilusFile *
nautilus_file_new (GFile *location)
{
NautilusFile *file;
GFileType file_type;
g_return_val_if_fail (G_IS_FILE (location), NULL);
file = nautilus_file_get_existing (location);
if (file != NULL)
{
return g_object_ref (file);
}
/* TODO: extension points? */
file_type = g_file_query_file_type (location, G_FILE_QUERY_INFO_NONE,
NULL);
......
......@@ -13,7 +13,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nautilus. If not, see <http://www.gnu.org/licenses/>.
* along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef NAUTILUS_FILE_H_INCLUDED
......@@ -32,9 +32,18 @@ typedef void (*NautilusFileInfoCallback) (NautilusFile *file,
GError *error,
gpointer user_data);
typedef enum
{
NAUTILUS_FILE_CHANGE_MOVED,
NAUTILUS_FILE_CHANGE_RENAMED
} NautilusFileChange;
struct _NautilusFileClass
{
GObjectClass parent_class;
void (*renamed) (NautilusFile *file,
GFile *new_location);
};
void nautilus_file_query_info (NautilusFile *file,
......@@ -42,7 +51,9 @@ void nautilus_file_query_info (NautilusFile *file,
NautilusFileInfoCallback callback,
gpointer user_data);
GFile *nautilus_file_get_location (NautilusFile *file);
NautilusFile *nautilus_file_get_existing (GFile *location);
GFile *nautilus_file_get_location (NautilusFile *file);
NautilusFile *nautilus_file_get_parent (NautilusFile *file);
/* Overwrites the info if the file exists in cache.
* Used by NautilusDirectory when enumerating children.
......
/* Copyright (C) 2017 Ernestas Kulik <ernestask@gnome.org>
*
* This file is part of Nautilus.
*
* Nautilus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Nautilus 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nautilus. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nautilus-signal-utilities.h"
#include <gobject/gvaluecollector.h>
typedef struct
{
GValue *instance_and_params;
guint signal_id;
GQuark detail;
gint n_values;
} EmissionData;
static void
emission_data_free (EmissionData *data)
{
for (int i = 0; i < data->n_values; i++)
{
g_value_unset (&data->instance_and_params[i]);
}
g_free (data->instance_and_params);
g_free (data);
}
static gboolean
emit_signal (gpointer data)
{
EmissionData *emission_data;
emission_data = data;
g_signal_emitv (emission_data->instance_and_params,
emission_data->signal_id,
emission_data->detail,
NULL);
emission_data_free (emission_data);
return FALSE;
}
void nautilus_emit_signal_in_main_context_va_list (gpointer instance,
GMainContext *main_context,
guint signal_id,
GQuark detail,
va_list ap)
{
GSignalQuery query;
EmissionData *emission_data;
g_autofree gchar *error = NULL;
g_signal_query (signal_id, &query);
if (query.signal_id == 0)
{
return;
}
emission_data = g_new0 (EmissionData, 1);