GFileMonitor - g_signal_handler_block"已更改"信号不会阻止处理程序?

时间:2017-03-31 03:39:53

标签: c callback gtk gio

所有

这可能需要一些时间来设置。我有一个小编辑项目,我在过去的几个月里一直在工作[1]。我原本想在当前编辑器文件上实现inotify监视,以防止通过foreing进程进行修改。我创建了一个自定义信号,以及addremovemonitorpselect}例程blockunblock的例程发出自定义信号以允许正常save/save as而不触发回调。我遇到的问题是如何将monitor添加到gtk_event_loop,以便在循环的每次迭代中执行检查。我决定使用g_idle_add并转到 gtk-app-devel 列表,以确定方法是否合理或是否有更好的方法。共识是使用GIO / GFileMonitor而不是直接使用inotify

快进到当前的问题。我重写了实现以使用GFileMonitor/g_file_monitor_file并重写了blockunblock例程来阻止"changed"信号的处理,以允许正常save/save as而不触发回调。问题是当我block保存文件之前回调的实例和handler_id时,回调仍然会触发。将inotify实现与自定义信号一起使用时,阻止信号发射效果很好。我已经将这个发布回 gtk-app-devel 列表,但没有得到任何回报 - 这就是我在这里问的原因。为什么g_signal_handler_block与GIO / GFileMonitor无法阻止处理"changed"信号回调? (更重要的是,我该如何修复它)

注意:(MCVE - 完整的测试代码位于https://github.com/drankinatty/gtktest)。要使用GtkSourceView2进行构建,只需键入make with=-DWGTKSOURCEVIEW2,它就会构建为bin/gtkwrite,否则无需构建,只需键入make,它就会构建为bin/gtkedit

相关代码的逻辑如下(appstruct保存相关编辑器变量/信息和设置的实例.GIO / GFileMonitor实现位于gtk_filemon.[ch]并且保存函数周围的包装器位于gtk_filebuf.c

typedef struct {
    ...
    gchar           *filename;
    GFileMonitor    *filemon;
    gulong          mfp_handler;
    ...
} kwinst;

kwinst *app = g_slice_new (kwinst);

我将手表设置为:

GFile *gfile = g_file_new_for_path (app->filename);
...

/* create monitor for app->filename */
app->filemon = g_file_monitor_file (gfile,
                        G_FILE_MONITOR_NONE,
                        cancellable, &err);
...

/* connect changed signal to monitored file saving ID */
app->mfp_handler = g_signal_connect (G_OBJECT(app->filemon), "changed",
                        G_CALLBACK (file_monitor_on_changed), data);

保存实例(app->filemon)和handler_id(app->mfp_handler)。 (mfp只是由外部进程修改的缩写)为了防止在正常保存/保存操作期间处理更改,我创建了阻止和取消阻止功能以防止对文件进行更改从触发回调,例如使用debug g_print调用显示如下:

void file_monitor_block_changed (gpointer data)
{
    kwinst *app = (kwinst *)data;

    if (!app->filemon || !app->mfp_handler) return;

    g_print ("blocking changed (%lu)\n", app->mfp_handler);

    g_signal_handler_block (app->filemon, app->mfp_handler);
}

void file_monitor_unblock_changed (gpointer data)
{
    kwinst *app = (kwinst *)data;

    if (!app->filemon || !app->mfp_handler) return;

    g_print ("unblocking changed (%lu)\n", app->mfp_handler);

    g_signal_handler_unblock (app->filemon, app->mfp_handler);
}

要实施block/unblock,我将文件'保存/另存为'使用block函数,然后保存[2],然后保存unblock,但回调仍然在正常保存时触发。例如保存功能的相关部分是:

if (app->mfp_handler)                   /* current file monitor on file */
    file_monitor_block_changed (app);   /* block "changed" signal */

g_print ("  buffer_write_file (app, filename)\n");
buffer_write_file (app, filename);      /* write to file app->filename */

if (filename)
    file_monitor_add (app);             /* setup monitoring on new name */
else if (app->mfp_handler)
    file_monitor_unblock_changed (app); /* unblock "changed" signal */

使用上面的调试g_print语句,发出save会产生以下输出:

$ ./bin/gtkwrite
blocking changed (669)
  buffer_write_file (app, filename)
unblocking changed (669)
Monitor Event: File = /home/david/tmp/foo.txt.UY9IXY
G_FILE_MONITOR_EVENT_DELETED
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_CREATED
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED

我是否包含block/unblock没有区别,回调的触发没有变化。我怀疑问题在于"changed"信号和它的处理是通过GIO而不是GTK实现的(因为inotify实现的自定义信号是)并且在使用"changed"阻止处理g_signal_handler_block信号的abiliby方面存在一些差异 - 尽管我在文档中找不到区别。文档说明:

To get informed about changes to the file or directory you are monitoring, 
connect to the “changed” signal. The signal will be emitted in the thread-default 
main context of the thread that the monitor was created in (though if the global 
default main context is blocked, this may cause notifications to be blocked 
even if the thread-default context is still running).

https://developer.gnome.org/gio/stable/GFile.html#g-file-monitor-file

应用程序本身不使用任何显式线程,也不fork保存的任何部分。因此,block/unblock无法阻止处理"changed"信号,我感到很遗憾。保存完全包含在block/unblock中,除非g_file_set_contents调用是异步的,否则不应该出现任何时序问题。

为什么对g_signal_handler_block/g_signal_handler_unblock的调用无法阻止处理当前文件更改时发出的"changed"信号?我可以g_signal_handler_disconnect并且没有任何事情发生,但我不应该disconnect暂时阻止处理。我错过了什么?

为了完整性,下面包含file_monitor_on_changed函数以及脚注:

void file_monitor_on_changed (GFileMonitor *mon, 
                                GFile *file, GFile *other,
                                GFileMonitorEvent evtype,
                                gpointer data)
{
    kwinst *app = (kwinst *)data;

    g_print ("Monitor Event: File = %s\n", g_file_get_parse_name (file));

    switch (evtype)
    {
        case G_FILE_MONITOR_EVENT_CHANGED:
            /* prompt or emit custom signal modified by foreign process */
            g_print ("G_FILE_MONITOR_EVENT_CHANGED\n");
            break;
        case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
            g_print ("G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT\n");
            break;
        case G_FILE_MONITOR_EVENT_DELETED:
            /* avoid firing on normal '.tmp' file delete */
            if (g_strcmp0 (g_file_get_parse_name (file), app->filename)) {
                g_print ("  ignoring 'tmp' file delete.\n");
                break;
            }
            /* prompt or emit custom signal modified by foreign process */
            g_print ("G_FILE_MONITOR_EVENT_DELETED\n");
            /* prompt save file */
            break;
        case G_FILE_MONITOR_EVENT_CREATED:
            g_print ("G_FILE_MONITOR_EVENT_CREATED\n");
            break;
        case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
            g_print ("G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED\n");
            break;
        case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
            g_print ("G_FILE_MONITOR_EVENT_PRE_UNMOUNT\n");
            break;
        case G_FILE_MONITOR_EVENT_UNMOUNTED:
            g_print ("G_FILE_MONITOR_EVENT_UNMOUNTED\n");
            break;
        case G_FILE_MONITOR_EVENT_MOVED:
            g_print ("G_FILE_MONITOR_EVENT_MOVED\n");
            /* prompt save file */
            break;
        case G_FILE_MONITOR_EVENT_RENAMED:
            /* prompt save file */
            g_print ("G_FILE_MONITOR_EVENT_RENAMED\n");
            break;
        case G_FILE_MONITOR_EVENT_MOVED_IN:
            g_print ("G_FILE_MONITOR_EVENT_MOVED_IN\n");
            break;
        case G_FILE_MONITOR_EVENT_MOVED_OUT:
            g_print ("G_FILE_MONITOR_EVENT_MOVED_OUT\n");
            break;
        default:
            g_print ("unknown EVENT on changed signal.\n");
    }

    if (mon || other) {}
}

在其他情况下使用g_signal_handler_block可以正常工作

作为评论中提到的一点,我可以确认我可以轻松blockunblock其他信号处理程序没有问题。具体来说,在使用inotify实现时,我创建了以下自定义信号:

/* create signal to monitor file modified by foreign process */
app->SIG_MODFP = g_signal_new ("modified-foreign",
                                GTK_TYPE_TEXT_BUFFER,
                                GTK_RUN_ACTION,
                                0,
                                NULL,
                                NULL,
                                NULL,
                                G_TYPE_NONE,
                                1,
                                G_TYPE_POINTER);

然后将信号连接到处理程序并保存handler_id,如下所示:

/* custom signals */
app->mfp_handler2 = g_signal_connect (GTK_TEXT_BUFFER(app->buffer), 
                  "modified-foreign",
                  G_CALLBACK (on_modified_foreign), app);

on_modified_foreign回调是一个简单的测试回调,用于测试阻止/解除阻塞:

void on_modified_foreign (GtkTextBuffer *buffer,
                        kwinst *app)
{
    dlg_info ("File has changed on disk, reload?", "Modified by Foreign Process");

    if (buffer || app) {}
}

上面dlg_info只是gtk_message_dialog_new的包装,可以在发出"modified-foreign"信号时弹出对话框。

然后实现一个简单的测试,其中一个menuitem导致发出信号,例如:

void menu_status_bigredbtn_activate (GtkMenuItem *menuitem, kwinst *app)
{
    g_signal_emit_by_name (G_OBJECT(app->buffer), "modified-foreign::", app);
}

最后,阻止/解除阻塞的工作正常:

void menu_status_block_activate (GtkMenuItem *menuitem, kwinst *app)
{
    if (!app->mfp_handler2) return;
    GtkTextBuffer *buffer = GTK_TEXT_BUFFER(app->buffer);
    g_signal_handler_block (buffer, app->mfp_handler2);
}

void menu_status_unblock_activate (GtkMenuItem *menuitem, kwinst *app)
{
    if (!app->mfp_handler2) return;
    GtkTextBuffer *buffer = GTK_TEXT_BUFFER(app->buffer);
    g_signal_handler_unblock (buffer, app->mfp_handler2);
}

一切正常。选择bigredbtn菜单项,发出信号并弹出对话框。然后选择block菜单项,然后再次尝试bigredbtn - 没有任何反应,没有对话,没有任何内容。然后选择unblock菜单项并再次选择bigredbtn,每个选项上都会弹出对话框。 (并且处理程序被阻塞时发出的信号没有排队,一旦解除阻塞就不会调用处理程序)

这就是我被困的地方。问题的很大一部分是没有选择GIO源逐行,它在很大程度上是一个很大的黑盒子。一切都在GTK方面运行良好,但在做同样的事情涉及GIO功能时,结果似乎没有按预期工作。

感谢您对此问题的任何其他见解。

脚注1: https://github.com/drankinatty/gtkwrite

脚注2: buffer_write_file调用g_file_set_contents写入磁盘。

1 个答案:

答案 0 :(得分:0)

好的,我已经玩了足够长的时间,我找到了解决方案。无论出于什么原因,GIO的关键似乎都是在信号最初连接的同一个源内调用blockunblock。例如,添加块/解锁gtk_filemon.c源中的函数然后从任何地方调用(如上面的测试菜单项所做的)工作正常,例如:

void file_monitor_block_changed (gpointer data)
{
    kwinst *app = (kwinst *)data;

    if (!app->filemon || !app->mfp_handler) return;

    g_signal_handler_block (app->filemon, app->mfp_handler);
}

void file_monitor_unblock_changed (gpointer data)
{
    kwinst *app = (kwinst *)data;

    if (!app->filemon || !app->mfp_handler) return;

    g_signal_handler_unblock (app->filemon, app->mfp_handler);
}

这允许menu_status_block_activatemenu_status_unblock_activate中上述两个函数的调用按预期工作,并在阻塞时成功阻止"changed"信号的处理,并在解除阻塞时恢复。为什么不能通过使用实例 handler_id 直接调用g_signal_handler_blockg_signal_handler_unblock来完成,只需要留下时间的文档谜是

注意:如果有人想玩它,我会将github.com gtktest代码保留到4月中旬,之后工作代码将存在于https://github.com/drankinatty/gtkwrite