From 77713b92e02b659f87f32b5cdf812de763a517a8 Mon Sep 17 00:00:00 2001 From: Yevgen Muntyan <17531749+muntyan@users.noreply.github.com> Date: Fri, 8 Dec 2006 02:28:18 -0600 Subject: [PATCH] Implemented watching folders on win32 --- moo/mooedit/mooeditfileops.c | 9 +- moo/moofileview/moofile.c | 25 +- moo/moofileview/moofileview.c | 2 +- moo/mooutils/Makefile.am | 8 + moo/mooutils/moofilewatch.c | 618 ++++++++++++++++++++++++++++++--- moo/mooutils/moofilewatch.h | 10 +- moo/mooutils/mooutils-thread.c | 251 +++++++++++++ moo/mooutils/mooutils-thread.h | 41 +++ 8 files changed, 903 insertions(+), 61 deletions(-) create mode 100644 moo/mooutils/mooutils-thread.c create mode 100644 moo/mooutils/mooutils-thread.h diff --git a/moo/mooedit/mooeditfileops.c b/moo/mooedit/mooeditfileops.c index 9bbf3d20..d4e24445 100644 --- a/moo/mooedit/mooeditfileops.c +++ b/moo/mooedit/mooeditfileops.c @@ -923,17 +923,18 @@ file_watch_event (G_GNUC_UNUSED MooFileWatch *watch, switch (event->code) { - case MOO_FILE_WATCH_CHANGED: + case MOO_FILE_WATCH_EVENT_CHANGED: edit->priv->modified_on_disk = TRUE; break; - case MOO_FILE_WATCH_DELETED: + case MOO_FILE_WATCH_EVENT_DELETED: edit->priv->deleted_from_disk = TRUE; edit->priv->file_monitor_id = 0; break; - case MOO_FILE_WATCH_CREATED: - case MOO_FILE_WATCH_MOVED: + case MOO_FILE_WATCH_EVENT_CREATED: + case MOO_FILE_WATCH_EVENT_MOVED: + case MOO_FILE_WATCH_EVENT_ERROR: g_return_if_reached (); } diff --git a/moo/moofileview/moofile.c b/moo/moofileview/moofile.c index ec74e589..c61f0a3a 100644 --- a/moo/moofileview/moofile.c +++ b/moo/moofileview/moofile.c @@ -828,16 +828,20 @@ fam_event (MooFolder *folder, switch (event->code) { - case MOO_FILE_WATCH_CHANGED: + case MOO_FILE_WATCH_EVENT_CHANGED: file_changed (folder, event->filename); break; - case MOO_FILE_WATCH_DELETED: + case MOO_FILE_WATCH_EVENT_DELETED: file_deleted (folder, event->filename); break; - case MOO_FILE_WATCH_CREATED: + case MOO_FILE_WATCH_EVENT_CREATED: file_created (folder, event->filename); break; - default: + case MOO_FILE_WATCH_EVENT_ERROR: + /* XXX */ + file_deleted (folder, folder->priv->path); + break; + case MOO_FILE_WATCH_EVENT_MOVED: break; } } @@ -869,7 +873,7 @@ start_monitor (MooFolder *folder) if (!folder->priv->fam_request) { - g_warning ("%s: moo_fam_monitor_directory failed", G_STRLOC); + g_warning ("%s: moo_fam_monitor_directory failed for path '%s'", G_STRLOC, folder->priv->path); g_warning ("%s: %s", G_STRLOC, error->message); g_error_free (error); return; @@ -1259,10 +1263,6 @@ _moo_file_unref (MooFile *file) } -#ifdef __WIN32__ -#define lstat stat -#endif - static void moo_file_stat_unix (MooFile *file, const char *dirname) @@ -1279,7 +1279,9 @@ moo_file_stat_unix (MooFile *file, g_free (file->link_target); file->link_target = NULL; - if (lstat (fullname, &file->statbuf) != 0) + errno = 0; + + if (g_lstat (fullname, &file->statbuf) != 0) { if (errno == ENOENT) { @@ -1310,8 +1312,9 @@ moo_file_stat_unix (MooFile *file, gssize len; file->info |= MOO_FILE_INFO_IS_LINK; + errno = 0; - if (stat (fullname, &file->statbuf) != 0) + if (g_stat (fullname, &file->statbuf) != 0) { if (errno == ENOENT) { diff --git a/moo/moofileview/moofileview.c b/moo/moofileview/moofileview.c index d3651059..32e0e9b6 100644 --- a/moo/moofileview/moofileview.c +++ b/moo/moofileview/moofileview.c @@ -1217,7 +1217,7 @@ init_actions (MooFileView *fileview) "closure-callback", file_view_paste_clipboard, NULL); -#ifdef __WIN32__ +#if defined(__WIN32__) && 0 moo_action_group_add_action (group, "Reload", "label", _("Reload"), "tooltip", _("Reload"), diff --git a/moo/mooutils/Makefile.am b/moo/mooutils/Makefile.am index 9425d520..9354c425 100644 --- a/moo/mooutils/Makefile.am +++ b/moo/mooutils/Makefile.am @@ -22,6 +22,10 @@ gtk_2_4_sources = \ newgtk/gtkfontbutton.c \ newgtk/gtkfontbutton.h +win32_sources = \ + mooutils-thread.c \ + mooutils-thread.h + mooutils_include_headers = \ mooaction.h \ mooactionbase.h \ @@ -132,6 +136,9 @@ endif if !GTK_2_4 mooutils_sources += $(moonewgtk_gtk_2_4_sources) endif +if MOO_OS_MINGW +mooutils_sources += $(win32_sources) +endif nodist_mooutils_sources = \ moomarshals.c \ @@ -146,6 +153,7 @@ EXTRA_DIST = \ xml2h.sh \ $(gmappedfile_sources) \ $(gtk_2_4_sources) \ + $(win32_sources) \ glade/accelbutton.glade \ glade/accelprefs.glade \ glade/moologwindow.glade \ diff --git a/moo/mooutils/moofilewatch.c b/moo/mooutils/moofilewatch.c index 423d5de3..0d6bf561 100644 --- a/moo/mooutils/moofilewatch.c +++ b/moo/mooutils/moofilewatch.c @@ -20,6 +20,7 @@ #endif #ifdef __WIN32__ +#include "mooutils/mooutils-thread.h" #include #include #include @@ -29,6 +30,7 @@ #include #endif +#include #include #include #include @@ -40,7 +42,7 @@ #include "mooutils/moocompat.h" -#if 0 +#if defined(__WIN32__) && 0 #define DEBUG_PRINT g_message #else static void DEBUG_PRINT (G_GNUC_UNUSED const char *format, ...) @@ -53,7 +55,8 @@ static const char *event_code_strings[] = { "CHANGED", "DELETED", "CREATED", - "MOVED" + "MOVED", + "ERROR" }; typedef struct _Monitor Monitor; @@ -87,7 +90,8 @@ typedef struct { GError **error); void (*suspend) (Monitor *monitor); void (*resume) (Monitor *monitor); - void (*delete) (Monitor *monitor); + void (*delete) (MooFileWatch *watch, + Monitor *monitor); } WatchFuncs; @@ -96,6 +100,9 @@ struct _MooFileWatchPrivate { #ifdef MOO_USE_FAM FAMConnection fam_connection; guint fam_connection_watch; +#endif +#ifdef __WIN32__ + guint id; #endif WatchFuncs funcs; MooFileWatchMethod method; @@ -121,7 +128,8 @@ static Monitor *monitor_fam_create (MooFileWatch *watch, GError **error); static void monitor_fam_suspend (Monitor *monitor); static void monitor_fam_resume (Monitor *monitor); -static void monitor_fam_delete (Monitor *monitor); +static void monitor_fam_delete (MooFileWatch *watch, + Monitor *monitor); #endif /* MOO_USE_FAM */ static gboolean watch_stat_start (MooFileWatch *watch, @@ -136,7 +144,8 @@ static Monitor *monitor_stat_create (MooFileWatch *watch, GError **error); static void monitor_stat_suspend (Monitor *monitor); static void monitor_stat_resume (Monitor *monitor); -static void monitor_stat_delete (Monitor *monitor); +static void monitor_stat_delete (MooFileWatch *watch, + Monitor *monitor); #ifdef __WIN32__ static gboolean watch_win32_start (MooFileWatch *watch, @@ -151,7 +160,8 @@ static Monitor *monitor_win32_create (MooFileWatch *watch, GError **error); static void monitor_win32_suspend (Monitor *monitor); static void monitor_win32_resume (Monitor *monitor); -static void monitor_win32_delete (Monitor *monitor); +static void monitor_win32_delete (MooFileWatch *watch, + Monitor *monitor); #endif /* __WIN32__ */ @@ -339,10 +349,11 @@ moo_file_watch_event_code_get_type (void) if (!type) { static const GEnumValue values[] = { - {MOO_FILE_WATCH_CHANGED, (char*) "MOO_FILE_WATCH_CHANGED", (char*) "changed"}, - {MOO_FILE_WATCH_DELETED, (char*) "MOO_FILE_WATCH_DELETED", (char*) "deleted"}, - {MOO_FILE_WATCH_CREATED, (char*) "MOO_FILE_WATCH_CREATED", (char*) "created"}, - {MOO_FILE_WATCH_MOVED, (char*) "MOO_FILE_WATCH_MOVED", (char*) "moved"}, + { MOO_FILE_WATCH_EVENT_CHANGED, (char*) "MOO_FILE_WATCH_EVENT_CHANGED", (char*) "changed"}, + { MOO_FILE_WATCH_EVENT_DELETED, (char*) "MOO_FILE_WATCH_EVENT_DELETED", (char*) "deleted"}, + { MOO_FILE_WATCH_EVENT_CREATED, (char*) "MOO_FILE_WATCH_EVENT_CREATED", (char*) "created"}, + { MOO_FILE_WATCH_EVENT_MOVED, (char*) "MOO_FILE_WATCH_EVENT_MOVED", (char*) "moved"}, + { MOO_FILE_WATCH_EVENT_ERROR, (char*) "MOO_FILE_WATCH_EVENT_ERROR", (char*) "error"}, { 0, NULL, NULL } }; @@ -450,7 +461,7 @@ moo_file_watch_cancel_monitor (MooFileWatch *watch, GINT_TO_POINTER (monitor_id)); g_return_if_fail (monitor != NULL); - watch->priv->funcs.delete (monitor); + watch->priv->funcs.delete (watch, monitor); watch->priv->monitors = g_slist_remove (watch->priv->monitors, monitor); g_hash_table_remove (watch->priv->requests, GINT_TO_POINTER (monitor_id)); @@ -464,7 +475,7 @@ emit_event (MooFileWatch *watch, DEBUG_PRINT ("watch %p, monitor %d: event %s for %s", watch, event->monitor_id, event_code_strings[event->code], - event->filename); + event->filename ? event->filename : ""); g_signal_emit (watch, signals[EVENT], 0, event); } @@ -498,10 +509,12 @@ moo_file_watch_close (MooFileWatch *watch, watch->priv->alive = FALSE; - g_slist_foreach (watch->priv->monitors, - (GFunc) watch->priv->funcs.delete, NULL); - g_slist_free (watch->priv->monitors); - watch->priv->monitors = NULL; + while (watch->priv->monitors) + { + watch->priv->funcs.delete (watch, watch->priv->monitors->data); + watch->priv->monitors = + g_slist_delete_link (watch->priv->monitors, watch->priv->monitors); + } return watch->priv->funcs.shutdown (watch, error); } @@ -697,7 +710,8 @@ monitor_fam_resume (Monitor *monitor) static void -monitor_fam_delete (Monitor *monitor) +monitor_fam_delete (G_GNUC_UNUSED MooFileWatch *watch, + Monitor *monitor) { FAMRequest fr; int result; @@ -793,20 +807,21 @@ read_fam_events (G_GNUC_UNUSED GIOChannel *source, event.monitor_id = fe.fr.reqnum; event.filename = fe.filename; event.data = fe.userdata; + event.error = NULL; switch (fe.code) { case FAMChanged: - event.code = MOO_FILE_WATCH_CHANGED; + event.code = MOO_FILE_WATCH_EVENT_CHANGED; break; case FAMDeleted: - event.code = MOO_FILE_WATCH_DELETED; + event.code = MOO_FILE_WATCH_EVENT_DELETED; break; case FAMCreated: - event.code = MOO_FILE_WATCH_CREATED; + event.code = MOO_FILE_WATCH_EVENT_CREATED; break; case FAMMoved: - event.code = MOO_FILE_WATCH_MOVED; + event.code = MOO_FILE_WATCH_EVENT_MOVED; break; case FAMStartExecuting: @@ -886,7 +901,9 @@ monitor_stat_create (MooFileWatch *watch, g_return_val_if_fail (MOO_IS_FILE_WATCH (watch), NULL); g_return_val_if_fail (filename != NULL, NULL); - if (stat (filename, &buf)) + errno = 0; + + if (g_stat (filename, &buf) != 0) { int saved_errno = errno; g_set_error (error, MOO_FILE_WATCH_ERROR, @@ -950,7 +967,8 @@ monitor_stat_resume (Monitor *monitor) static void -monitor_stat_delete (Monitor *monitor) +monitor_stat_delete (G_GNUC_UNUSED MooFileWatch *watch, + Monitor *monitor) { g_return_if_fail (monitor != NULL); DEBUG_PRINT ("removing monitor for '%s'", monitor->filename); @@ -991,9 +1009,11 @@ do_stat (MooFileWatch *watch) old = monitor->statbuf.st_mtime; - if (stat (monitor->filename, &monitor->statbuf)) + errno = 0; + + if (g_stat (monitor->filename, &monitor->statbuf) != 0) { - event.code = MOO_FILE_WATCH_DELETED; + event.code = MOO_FILE_WATCH_EVENT_DELETED; event.monitor_id = monitor->request; event.filename = monitor->filename; event.data = monitor->user_data; @@ -1007,7 +1027,7 @@ do_stat (MooFileWatch *watch) } else if (monitor->statbuf.st_mtime > old) { - event.code = MOO_FILE_WATCH_CHANGED; + event.code = MOO_FILE_WATCH_EVENT_CHANGED; event.monitor_id = monitor->request; event.filename = monitor->filename; event.data = monitor->user_data; @@ -1062,11 +1082,473 @@ errno_to_file_error (int code) */ #ifdef __WIN32__ +typedef struct { + guint watch_id; + guint request; + char *path; +} FAMThreadWatch; + +typedef struct { + GMutex *lock; + GAsyncQueue *incoming; + HANDLE events[MAXIMUM_WAIT_OBJECTS]; + FAMThreadWatch watches[MAXIMUM_WAIT_OBJECTS]; + guint n_events; +} FAMThread; + +typedef enum { + COMMAND_ADD_PATH, + COMMAND_REMOVE_PATH +} FAMThreadCommandType; + +typedef struct { + FAMThreadCommandType type; + char *path; + guint watch_id; + guint request; +} FAMThreadCommand; + +typedef struct { + FAMThread thread_data; + guint event_id; + gboolean running; + GSList *watches; +} FAMWin32; + +typedef struct { + gboolean error; + guint watch_id; + guint request; + guint code; + char *msg; +} FAMEvent; + +static FAMWin32 *fam; +static guint last_watch_id; + +/****************************************************************************/ +/* Watch thread + */ + +static void +fam_event_free (FAMEvent *event) +{ + if (event) + { + g_free (event->msg); + g_free (event); + } +} + +static void +fam_thread_event (guint code, + gboolean error, + const char *msg, + guint watch_id, + guint request) +{ + FAMEvent *event = g_new0 (FAMEvent, 1); + event->error = error; + event->code = code; + event->watch_id = watch_id; + event->request = request; + event->msg = g_strdup (msg); + _moo_event_queue_push (fam->event_id, event, (GDestroyNotify) fam_event_free); +} + +static void +fam_thread_add_path (FAMThread *thr, + const char *path, + guint watch_id, + guint request) +{ + gunichar2 *win_path; + + if (thr->n_events == MAXIMUM_WAIT_OBJECTS) + { + g_critical ("%s: too many folders watched", G_STRLOC); + return; + } + + win_path = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL); + + if (!win_path) + { + char *msg = g_strdup_printf ("Could not convert filename '%s' to UTF16", + path); + fam_thread_event (MOO_FILE_WATCH_ERROR_BAD_FILENAME, TRUE, msg, watch_id, request); + g_free (msg); + return; + } + + thr->events[thr->n_events] = + FindFirstChangeNotificationW (win_path, FALSE, + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME); + + if (thr->events[thr->n_events] == INVALID_HANDLE_VALUE) + { + char *err = g_win32_error_message (GetLastError ()); + char *msg = g_strdup_printf ("Could not convert start watch for '%s': %s", path, err); + fam_thread_event (MOO_FILE_WATCH_ERROR_FAILED, TRUE, msg, watch_id, request); + g_free (msg); + g_free (err); + return; + } + + thr->watches[thr->n_events].watch_id = watch_id; + thr->watches[thr->n_events].request = request; + thr->watches[thr->n_events].path = g_strdup (path); + + thr->n_events += 1; +} + +static void +fam_thread_remove_path (FAMThread *thr, + guint watch_id, + guint request) +{ + guint i; + + for (i = 1; i < thr->n_events; ++i) + { + if (thr->watches[i].watch_id == watch_id && + thr->watches[i].request == request) + { + FindCloseChangeNotification (thr->events[i]); + g_free (thr->watches[i].path); + + if (i < thr->n_events - 1) + { + memmove (&thr->watches[i], &thr->watches[i+1], + (thr->n_events - i - 1) * sizeof(thr->watches[i])); + memmove (&thr->events[i], &thr->events[i+1], + (thr->n_events - i - 1) * sizeof(thr->events[i])); + } + + thr->n_events -= 1; + + return; + } + } + + g_critical ("%s: can't remove watch %d, request %d", + G_STRLOC, watch_id, request); +} + +static void +fam_thread_check_dir (FAMThread *thr, + guint idx) +{ + struct stat buf; + + errno = 0; + + if (g_stat (thr->watches[idx].path, &buf) != 0 && + errno == ENOENT) + { + fam_thread_event (MOO_FILE_WATCH_EVENT_DELETED, + FALSE, NULL, + thr->watches[idx].watch_id, + thr->watches[idx].request); + fam_thread_remove_path (thr, + thr->watches[idx].watch_id, + thr->watches[idx].request); + } + else + { + fam_thread_event (MOO_FILE_WATCH_EVENT_CHANGED, + FALSE, NULL, + thr->watches[idx].watch_id, + thr->watches[idx].request); + + if (!FindNextChangeNotification (thr->events[idx])) + { + char *err = g_win32_error_message (GetLastError ()); + char *msg = g_strdup_printf ("Error in FindNextChangeNotification: %s", err); + + fam_thread_event (MOO_FILE_WATCH_ERROR_FAILED, TRUE, msg, + thr->watches[idx].watch_id, + thr->watches[idx].request); + fam_thread_remove_path (thr, + thr->watches[idx].watch_id, + thr->watches[idx].request); + + g_free (msg); + g_free (err); + } + } +} + +static void +fam_thread_do_command (FAMThread *thr) +{ + FAMThreadCommand *cmd; + GSList *list = NULL; + + DEBUG_PRINT ("fam_thread_do_command start"); + + g_mutex_lock (thr->lock); + while ((cmd = g_async_queue_try_pop (thr->incoming))) + list = g_slist_prepend (list, cmd); + ResetEvent (thr->events[0]); + g_mutex_unlock (thr->lock); + + list = g_slist_reverse (list); + + while (list) + { + cmd = list->data; + + switch (cmd->type) + { + case COMMAND_ADD_PATH: + fam_thread_add_path (thr, cmd->path, cmd->watch_id, cmd->request); + break; + + case COMMAND_REMOVE_PATH: + fam_thread_remove_path (thr, cmd->watch_id, cmd->request); + break; + } + + list = g_slist_delete_link (list, list); + g_free (cmd->path); + g_free (cmd); + } + + DEBUG_PRINT ("fam_thread_do_command end"); +} + + +static gpointer +fam_thread_main (FAMThread *thr) +{ + while (TRUE) + { + int ret; + + DEBUG_PRINT ("calling WaitForMultipleObjects for %d handles", thr->n_events); + ret = WaitForMultipleObjects (thr->n_events, thr->events, FALSE, INFINITE); + DEBUG_PRINT ("WaitForMultipleObjects returned %d", ret); + + if (ret == (int) WAIT_FAILED) + { + char *msg = g_win32_error_message (GetLastError ()); + g_critical ("%s: %s", G_STRLOC, msg); + g_free (msg); + break; + } + + if (ret == WAIT_OBJECT_0) + fam_thread_do_command (thr); + else if (WAIT_OBJECT_0 < ret && ret < (int) thr->n_events) + fam_thread_check_dir (thr, ret - WAIT_OBJECT_0); + else + { + g_critical ("%s: oops", G_STRLOC); + break; + } + } + + /* XXX cleanup */ + return NULL; +} + +static void +fam_thread_command (FAMThreadCommandType type, + const char *filename, + guint watch_id, + guint request) +{ + FAMThreadCommand *cmd; + + cmd = g_new0 (FAMThreadCommand, 1); + cmd->type = type; + cmd->path = g_strdup (filename); + cmd->watch_id = watch_id; + cmd->request = request; + + g_mutex_lock (fam->thread_data.lock); + g_async_queue_push (fam->thread_data.incoming, cmd); + SetEvent (fam->thread_data.events[0]); + g_mutex_unlock (fam->thread_data.lock); +} + +/****************************************************************************/ +/* Monitors + */ + +static MooFileWatch * +find_watch (guint id) +{ + GSList *l; + + for (l = fam->watches; l != NULL; l = l->next) + { + MooFileWatch *watch = l->data; + if (watch->priv->id == id) + return watch; + } + + return NULL; +} + +static void +do_event (FAMEvent *event) +{ + MooFileWatch *watch; + Monitor *monitor; + MooFileWatchEvent watch_event; + + watch = find_watch (event->watch_id); + + if (!watch) + { + DEBUG_PRINT ("got event for dead watch %d", event->watch_id); + return; + } + + monitor = g_hash_table_lookup (watch->priv->requests, + GUINT_TO_POINTER (event->request)); + + if (!monitor) + { + DEBUG_PRINT ("got event for dead monitor %d", event->request); + return; + } + + watch_event.monitor_id = event->request; + watch_event.filename = monitor->filename; + watch_event.data = monitor->user_data; + watch_event.error = NULL; + + if (event->error) + { + watch_event.code = MOO_FILE_WATCH_EVENT_ERROR; + g_set_error (&watch_event.error, MOO_FILE_WATCH_ERROR, + event->code, + event->msg ? event->msg : "FAILED"); + DEBUG_PRINT ("got error for watch %d: %s", event->watch_id, watch_event.error->message); + } + else + { + DEBUG_PRINT ("got event for filename %s", monitor->filename); + watch_event.code = event->code; + } + + emit_event (watch, &watch_event); + + if (watch_event.error) + g_error_free (watch_event.error); +} + +static void +event_callback (GList *events) +{ + GList *trimmed = NULL; + + while (events) + { + GList *l; + FAMEvent *event = events->data; + gboolean found = FALSE; + + event = events->data; + events = events->next; + + for (l = trimmed; l != NULL; l = l->next) + { + FAMEvent *old_event = l->data; + + if (old_event->watch_id == event->watch_id && + old_event->request == event->request) + { + found = TRUE; + + if (!old_event->error && event->error) + l->data = event; + + break; + } + } + + if (!found) + trimmed = g_list_prepend (trimmed, event); + } + + trimmed = g_list_reverse (trimmed); + + while (trimmed) + { + do_event (trimmed->data); + trimmed = g_list_delete_link (trimmed, trimmed); + } +} + +static gboolean +fam_win32_init (void) +{ + if (!fam) + { + GError *error = NULL; + + fam = g_new0 (FAMWin32, 1); + fam->running = FALSE; + + fam->thread_data.n_events = 1; + fam->thread_data.events[0] = CreateEvent (NULL, TRUE, FALSE, NULL);; + + if (!fam->thread_data.events[0]) + { + char *msg = g_win32_error_message (GetLastError ()); + g_critical ("%s: could not create incoming event", G_STRLOC); + g_critical ("%s: %s", G_STRLOC, msg); + g_free (msg); + return FALSE; + } + + fam->thread_data.incoming = g_async_queue_new (); + fam->thread_data.lock = g_mutex_new (); + fam->event_id = _moo_event_queue_connect ((MooEventQueueCallback) event_callback, + NULL, NULL); + + if (!g_thread_create ((GThreadFunc) fam_thread_main, &fam->thread_data, FALSE, &error)) + { + g_critical ("%s: could not start watch thread", G_STRLOC); + g_critical ("%s: %s", G_STRLOC, error->message); + g_error_free (error); + } + else + { + fam->running = TRUE; + DEBUG_PRINT ("initialized folder watch"); + } + } + + return fam->running; +} + static gboolean watch_win32_start (MooFileWatch *watch, GError **error) { - return watch_stat_start (watch, error); + if (!watch_stat_start (watch, error)) + return FALSE; + + if (!fam_win32_init ()) + { + g_set_error (error, MOO_FILE_WATCH_ERROR, + MOO_FILE_WATCH_ERROR_FAILED, + "Could not initialize folder watch thread"); + watch_stat_shutdown (watch, NULL); + return FALSE; + } + + watch->priv->id = ++last_watch_id; + fam->watches = g_slist_prepend (fam->watches, watch); + DEBUG_PRINT ("started watch %d", watch->priv->id); + + return TRUE; } @@ -1086,20 +1568,61 @@ monitor_win32_create (MooFileWatch *watch, int *request, GError **error) { + Monitor *monitor; + struct stat buf; + + g_return_val_if_fail (MOO_IS_FILE_WATCH (watch), NULL); g_return_val_if_fail (filename != NULL, FALSE); if (type == MONITOR_FILE) return monitor_stat_create (watch, type, filename, data, request, error); -#ifdef __GNUC__ -#warning "Implement monitor_win32_create()" -#endif + errno = 0; - g_set_error (error, MOO_FILE_WATCH_ERROR, - MOO_FILE_WATCH_ERROR_NOT_IMPLEMENTED, - "watching folders is not implemented on win32"); + if (g_stat (filename, &buf) != 0) + { + int saved_errno = errno; + g_set_error (error, MOO_FILE_WATCH_ERROR, + errno_to_file_error (saved_errno), + "stat: %s", g_strerror (saved_errno)); + return NULL; + } - return NULL; + if (type == MONITOR_DIR && !S_ISDIR (buf.st_mode)) + { + char *display_name = g_filename_display_name (filename); + g_set_error (error, MOO_FILE_WATCH_ERROR, + MOO_FILE_WATCH_ERROR_NOT_DIR, + "%s is not a directory", display_name); + g_free (display_name); + return NULL; + } + else if (type == MONITOR_FILE && S_ISDIR (buf.st_mode)) /* it's fatal on windows */ + { + char *display_name = g_filename_display_name (filename); + g_set_error (error, MOO_FILE_WATCH_ERROR, + MOO_FILE_WATCH_ERROR_IS_DIR, + "%s is a directory", display_name); + g_free (display_name); + return NULL; + } + + DEBUG_PRINT ("created monitor for '%s'", filename); + + monitor = g_new0 (Monitor, 1); + monitor->parent = watch; + monitor->type = type; + monitor->filename = g_strdup (filename); + monitor->user_data = data; + monitor->request = ++last_monitor_id; + monitor->suspended = FALSE; + + if (request) + *request = monitor->request; + + fam_thread_command (COMMAND_ADD_PATH, filename, watch->priv->id, monitor->request); + + return monitor; } @@ -1112,9 +1635,12 @@ monitor_win32_suspend (Monitor *monitor) return; if (monitor->type == MONITOR_FILE) - return monitor_stat_suspend (monitor); + { + monitor_stat_suspend (monitor); + return; + } - g_return_if_reached (); + g_critical ("%s: implement me", G_STRFUNC); } @@ -1127,20 +1653,30 @@ monitor_win32_resume (Monitor *monitor) return; if (monitor->type == MONITOR_FILE) - return monitor_stat_resume (monitor); + { + monitor_stat_resume (monitor); + return; + } - g_return_if_reached (); + g_critical ("%s: implement me", G_STRFUNC); } static void -monitor_win32_delete (Monitor *monitor) +monitor_win32_delete (MooFileWatch *watch, + Monitor *monitor) { g_return_if_fail (monitor != NULL); if (monitor->type == MONITOR_FILE) - return monitor_stat_delete (monitor); + { + monitor_stat_delete (watch, monitor); + return; + } - g_return_if_reached (); + DEBUG_PRINT ("removing monitor for '%s'", monitor->filename); + fam_thread_command (COMMAND_REMOVE_PATH, monitor->filename, watch->priv->id, monitor->request); + g_free (monitor->filename); + g_free (monitor); } #endif /* __WIN32__ */ diff --git a/moo/mooutils/moofilewatch.h b/moo/mooutils/moofilewatch.h index 3f59e967..58d45fef 100644 --- a/moo/mooutils/moofilewatch.h +++ b/moo/mooutils/moofilewatch.h @@ -59,10 +59,11 @@ typedef enum { /* Stripped FAMEventCode enumeration */ typedef enum { - MOO_FILE_WATCH_CHANGED = 1, - MOO_FILE_WATCH_DELETED = 2, - MOO_FILE_WATCH_CREATED = 3, - MOO_FILE_WATCH_MOVED = 4 + MOO_FILE_WATCH_EVENT_CHANGED = 1, + MOO_FILE_WATCH_EVENT_DELETED = 2, + MOO_FILE_WATCH_EVENT_CREATED = 3, + MOO_FILE_WATCH_EVENT_MOVED = 4, + MOO_FILE_WATCH_EVENT_ERROR = 5 } MooFileWatchEventCode; /* The structure has the same meaning as the FAMEvent @@ -76,6 +77,7 @@ struct _MooFileWatchEvent { MooFileWatchEventCode code; /* FAMEventCode */ int monitor_id; /* FAMRequest */ char *filename; + GError *error; gpointer data; }; diff --git a/moo/mooutils/mooutils-thread.c b/moo/mooutils/mooutils-thread.c new file mode 100644 index 00000000..3f080785 --- /dev/null +++ b/moo/mooutils/mooutils-thread.c @@ -0,0 +1,251 @@ +/* + * mooutils-thread.c + * + * Copyright (C) 2004-2006 by Yevgen Muntyan + * + * This program 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. + * + * See COPYING file that comes with this distribution. + */ + +#include "config.h" +#include "mooutils/mooutils-thread.h" + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +typedef struct { + MooEventQueueCallback callback; + gpointer callback_data; + GDestroyNotify notify; + guint id; +} QueueClient; + +typedef struct { + gpointer data; + GDestroyNotify destroy; + guint id; +} EventData; + +typedef struct { + GSList *clients; + guint last_id; + + int pipe_in; + int pipe_out; + GIOChannel *io; + + GHashTable *data; +} EventQueue; + + +static GStaticMutex queue_lock = G_STATIC_MUTEX_INIT; +static EventQueue *queue; + + +static QueueClient * +get_event_client (guint id) +{ + GSList *l; + + g_return_val_if_fail (queue != NULL, NULL); + + for (l = queue->clients; l != NULL; l = l->next) + { + QueueClient *s = l->data; + + if (s->id == id) + return s; + } + + return NULL; +} + + +static void +invoke_callback (gpointer id, + GQueue *events) +{ + GList *l; + QueueClient *client; + + g_message ("processing events for id %u", GPOINTER_TO_UINT (id)); + client = get_event_client (GPOINTER_TO_UINT (id)); + + if (client) + client->callback (events->head, client->callback_data); + + for (l = events->head; l != NULL; l = l->next) + { + EventData *data = l->data; + + if (data->destroy) + data->destroy (data->data); + + g_free (data); + } + + g_queue_free (events); +} + + +static gboolean +got_data (GIOChannel *io) +{ + GHashTable *data; + char buf[1]; + + g_static_mutex_lock (&queue_lock); + data = queue->data; + queue->data = NULL; + g_io_channel_read_chars (io, buf, 1, NULL, NULL); + g_static_mutex_unlock (&queue_lock); + + g_hash_table_foreach (data, (GHFunc) invoke_callback, NULL); + g_hash_table_destroy (data); + + return TRUE; +} + + +void +_moo_event_queue_do_events (guint event_id) +{ + GQueue *events = NULL; + + g_return_if_fail (queue != NULL); + + g_static_mutex_lock (&queue_lock); + + if (queue->data) + { + events = g_hash_table_lookup (queue->data, GUINT_TO_POINTER (event_id)); + + if (events) + g_hash_table_remove (queue->data, GUINT_TO_POINTER (event_id)); + } + + g_static_mutex_unlock (&queue_lock); + + if (events) + invoke_callback (GUINT_TO_POINTER (event_id), events); +} + + +static void +init_queue (void) +{ + int fds[2]; + + if (queue) + return; + + if (pipe (fds) != 0) + { + perror ("pipe"); + return; + } + + queue = g_new0 (EventQueue, 1); + + queue->clients = NULL; + + queue->pipe_in = fds[1]; + queue->pipe_out = fds[0]; + +#ifdef __WIN32__ + queue->io = g_io_channel_win32_new_fd (queue->pipe_out); +#else + queue->io = g_io_channel_unix_new (queue->pipe_out); +#endif + + g_io_add_watch (queue->io, G_IO_IN, (GIOFunc) got_data, NULL); + queue->data = NULL; +} + + +guint +_moo_event_queue_connect (MooEventQueueCallback callback, + gpointer data, + GDestroyNotify notify) +{ + QueueClient *client; + + g_return_val_if_fail (callback != NULL, 0); + + init_queue (); + + client = g_new0 (QueueClient, 1); + client->id = queue->last_id++; + client->callback = callback; + client->callback_data = data; + client->notify = notify; + + queue->clients = g_slist_prepend (queue->clients, client); + + return client->id; +} + + +void +_moo_event_queue_disconnect (guint event_id) +{ + QueueClient *client; + + g_return_if_fail (event_id != 0); + g_return_if_fail (queue != NULL); + + client = get_event_client (event_id); + g_return_if_fail (client != NULL); + + queue->clients = g_slist_remove (queue->clients, client); + + if (client->notify) + client->notify (client->callback_data); + + g_free (client); +} + + +/* called from a thread */ +void +_moo_event_queue_push (guint event_id, + gpointer data, + GDestroyNotify data_destroy) +{ + char c = 'd'; + EventData *event_data; + GQueue *events; + + event_data = g_new (EventData, 1); + event_data->data = data; + event_data->destroy = data_destroy; + event_data->id = event_id; + + g_static_mutex_lock (&queue_lock); + + if (!queue->data) + { + write (queue->pipe_in, &c, 1); + queue->data = g_hash_table_new (g_direct_hash, g_direct_equal); + } + + events = g_hash_table_lookup (queue->data, GUINT_TO_POINTER (event_id)); + + if (!events) + { + events = g_queue_new (); + g_hash_table_insert (queue->data, GUINT_TO_POINTER (event_id), events); + } + + g_queue_push_tail (events, event_data); + + g_static_mutex_unlock (&queue_lock); +} diff --git a/moo/mooutils/mooutils-thread.h b/moo/mooutils/mooutils-thread.h new file mode 100644 index 00000000..d0fb2767 --- /dev/null +++ b/moo/mooutils/mooutils-thread.h @@ -0,0 +1,41 @@ +/* + * mooutils-thread.h + * + * Copyright (C) 2004-2006 by Yevgen Muntyan + * + * This program 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. + * + * See COPYING file that comes with this distribution. + */ + +#ifndef __MOO_UTILS_WIN32_H__ +#define __MOO_UTILS_WIN32_H__ + +#include + +G_BEGIN_DECLS + + +typedef void (*MooEventQueueCallback) (GList *events, + gpointer data); + + +guint _moo_event_queue_connect (MooEventQueueCallback callback, + gpointer data, + GDestroyNotify notify); +void _moo_event_queue_disconnect (guint event_id); + +void _moo_event_queue_do_events (guint event_id); + +/* called from a thread */ +void _moo_event_queue_push (guint event_id, + gpointer data, + GDestroyNotify data_destroy); + + +G_END_DECLS + +#endif /* __MOO_UTILS_WIN32_H__ */