Make the obt_watch functionality work without inotify (via manual refreshes).
authorDana Jansens <danakj@orodu.net>
Sun, 24 Jul 2011 22:13:36 +0000 (18:13 -0400)
committerDana Jansens <danakj@orodu.net>
Sun, 16 Oct 2011 22:54:05 +0000 (18:54 -0400)
Adds obt/watch_manual.c which is a filler for when watch_inotify.c can't be
  used.  Other watch_foo.c may also exist in the future I hope.
Adds obt/watch_interface.h which the inotify (and others) subsystem can use
  to call back notification to the main watch system that events have
  occured.
Keep track of all files being watched within the main watch system, so that if
  a directory disappears, we can report the files inside it being removed.
  This change moved a lot of the recursive functionality out from
  watch_inotify.c into the main watch.c, making it much more simple and should
  help make it much easier to add other watch_foo.c subsystems.

Makefile.am
obt/linkbase.c
obt/tests/watchtest.c
obt/watch.c
obt/watch.h
obt/watch_inotify.c
obt/watch_interface.h [new file with mode: 0644]
obt/watch_manual.c [new file with mode: 0644]

index 33bcdf7ceac008e9632a3c5b89fe97ea30c33602..3967a727854abf34242f7dbbfb0f8e1c6e86b252 100644 (file)
@@ -158,8 +158,10 @@ obt_libobt_la_SOURCES = \
        obt/signal.c \
        obt/util.h \
        obt/watch.h \
+       obt/watch_interface.h \
        obt/watch.c \
        obt/watch_inotify.c \
+       obt/watch_manual.c \
        obt/xqueue.h \
        obt/xqueue.c
 
index dcea092ebea9072ab315f6ec4c0a839e3e9bd0a2..4c97ed654660b9bb6dc8b9c443dffe4ec0fce8e8 100644 (file)
@@ -173,7 +173,6 @@ static void category_free(ObtLinkBaseCategory *lc)
 /*! Called when a change happens in the filesystem. */
 static void update(ObtWatch *w, const gchar *base_path,
                    const gchar *sub_path,
-                   const gchar *full_path,
                    ObtWatchNotifyType type,
                    gpointer data)
 {
@@ -182,7 +181,7 @@ static void update(ObtWatch *w, const gchar *base_path,
     ObtLinkBaseEntry *remove = NULL;
     ObtLinkBaseEntry *show, *hide;
     ObtLink *link;
-    gchar *id;
+    gchar *id, *full_path;
     GList *list, *it;
     GList *remove_it = NULL;
     gint *priority;
@@ -192,6 +191,7 @@ static void update(ObtWatch *w, const gchar *base_path,
 
     id = obt_link_id_from_ddfile(sub_path);
     list = g_hash_table_lookup(self->base, id);
+    full_path = g_build_filename(base_path, sub_path, NULL);
 
     switch (type) {
     case OBT_WATCH_SELF_REMOVED:
@@ -289,6 +289,7 @@ static void update(ObtWatch *w, const gchar *base_path,
             g_hash_table_steal(self->base, id);
         }
     }
+    g_free(full_path);
     g_free(id);
 }
 
index 9ebdbaeef0d835029c6c876f34142e89f65fbdc0..5b4699e9cfb3edf25d2b5e0063adb130a4ade40d 100755 (executable)
@@ -28,23 +28,52 @@ exit
 #include "obt/watch.h"
 #include <glib.h>
 
-void func(ObtWatch *w, const gchar *base_path,
-          const gchar *subpath, ObtWatchNotifyType type,
-          gpointer data)
+static GMainLoop *loop;
+
+void func(ObtWatch *w, const gchar *base_path, const gchar *subpath,
+          ObtWatchNotifyType type, gpointer data)
 {
     g_print("base path: %s subpath: %s type=%d\n", base_path, subpath, type);
 }
 
+gboolean force_refresh(gpointer data)
+{
+    ObtWatch *w = data;
+    obt_watch_refresh(w);
+    return TRUE;
+}
+
+static void sighandler(gint signal)
+{
+    g_main_loop_quit(loop);
+}
+
 gint main()
 {
     ObtWatch *watch;
-    GMainLoop *loop;
+    struct sigaction action, oldaction;
+    sigset_t sigset;
+
+    loop = g_main_loop_new(NULL, FALSE);
+    g_return_val_if_fail(loop != NULL, 1);
 
     watch = obt_watch_new();
     obt_watch_add(watch, "/tmp/a", FALSE, func, NULL);
 
-    loop = g_main_loop_new(NULL, FALSE);
+
+    sigemptyset(&sigset);
+    action.sa_handler = sighandler;
+    action.sa_mask = sigset;
+    action.sa_flags = SA_NOCLDSTOP;
+    sigaction(SIGINT, &action, &oldaction);
+
+    g_timeout_add(5000, force_refresh, watch);
+
     g_main_loop_run(loop);
 
+    g_print("bye\n");
+
+    obt_watch_unref(watch);
+
     return 0;
 }
index 487655cd792c8ee4a64376052c999ace8be2a88d..6d7aaf04ad8e01a0de980fb5cf260a79f8a56034 100644 (file)
 */
 
 #include "obt/watch.h"
+#include "obt/watch_interface.h"
 
+#ifdef HAVE_STRING_H
+#  include <string.h>
+#endif
 #ifdef HAVE_UNISTD_H
 #  include <unistd.h>
 #endif
+#ifdef HAVE_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#  include <sys/stat.h>
+#endif
 
 #include <glib.h>
 
-typedef struct _ObtWatchTarget ObtWatchTarget;
-
-/*! Callback function for the system-specific GSource to alert us to changes.
-*/
-typedef void (*ObtWatchNotifyFunc)(const gchar *sub_path,
-                                   const gchar *full_path, gpointer target,
-                                   ObtWatchNotifyType type);
-
-
 /* Interface for system-specific stuff (e.g. inotify). the functions are
    defined in in watch_<system>.c
 */
 
-/*! Initializes the watch subsystem, and returns a GSource for it.
-  @param notify The GSource will call @notify when a watched file is changed.
-  @return Returns a GSource* on success, and a NULL if an error occurred.
-*/
-GSource* watch_sys_create_source(ObtWatchNotifyFunc notify);
-/*! Add a target to the watch subsystem.
-  @return Returns an integer key that is used to uniquely identify the target
-    within this subsystem.  A negative value indicates an error.
-*/
-gint watch_sys_add_target(GSource *source, const char *path,
-                          gboolean watch_hidden, gpointer target);
-/*! Remove a target from the watch system, by its key.
-  Use the key returned from watch_sys_add_target() to remove the target.
-*/
-void watch_sys_remove_target(GSource *source, gint key);
-
-
 /* General system which uses the watch_sys_* stuff
 */
 
@@ -68,12 +52,32 @@ struct _ObtWatchTarget {
     gchar *base_path;
     ObtWatchFunc func;
     gpointer data;
-    gint key;
+    gboolean watch_hidden;
+    ObtWatchFile *file;
+    gpointer watch_sys_data;
+};
+
+struct _ObtWatchFile {
+    ObtWatchFile *parent;
+    gchar *name;
+    time_t modified;
+    /* If this is a directory, then the hash table contains pointers to other
+       ObtWatchFile objects that are contained in this one. */
+    GHashTable *children_by_name;
+
+    gboolean seen;
+    gpointer watch_sys_data;
 };
 
 static void target_free(ObtWatchTarget *t);
-static void target_notify(const gchar *sub_path, const gchar *full_path,
-                          gpointer target, ObtWatchNotifyType type);
+
+static ObtWatchFile* tree_create(ObtWatchTarget *t,
+                                 const gchar *path, const gchar *last,
+                                 ObtWatchFile *parent);
+static void tree_destroy(ObtWatchFile *file);
+
+static void notify(ObtWatchTarget *target, ObtWatchFile *file,
+                   ObtWatchNotifyType type);
 
 ObtWatch* obt_watch_new()
 {
@@ -81,7 +85,7 @@ ObtWatch* obt_watch_new()
     GSource *source;
 
     w = NULL;
-    source = watch_sys_create_source(target_notify);
+    source = watch_sys_create_source();
     if (source) {
         w = g_slice_new(ObtWatch);
         w->ref = 1;
@@ -110,14 +114,91 @@ void obt_watch_unref(ObtWatch *w)
 
 static void target_free(ObtWatchTarget *t)
 {
-    if (t->key >= 0)
-        watch_sys_remove_target(t->w->source, t->key);
+    if (t->file) tree_destroy(t->file);
     g_free(t->base_path);
     g_slice_free(ObtWatchTarget, t);
 }
 
-gboolean obt_watch_add(ObtWatch *w, const gchar *path,
-                       gboolean watch_hidden,
+/*! Scans through the filesystem under the target and reports each file/dir
+  that it finds to the watch subsystem.
+  @path Absolute path to the file to scan for.
+  @last The last component (filename) of the file to scan for.
+  @parent The parent directory (if it exists) of the file we are scanning for.
+ */
+static ObtWatchFile* tree_create(ObtWatchTarget *target,
+                                 const gchar *path, const gchar *last,
+                                 ObtWatchFile *parent)
+{
+    ObtWatchFile *file;
+    struct stat buf;
+
+    if (stat(path, &buf) < 0)
+        return NULL;
+
+    file = g_slice_new(ObtWatchFile);
+    file->name = g_strdup(last);
+    file->modified = 0;
+    file->parent = parent;
+    file->children_by_name = NULL;
+
+    if (S_ISDIR(buf.st_mode))
+        file->children_by_name =
+            g_hash_table_new_full(g_str_hash, g_str_equal,
+                                  NULL, (GDestroyNotify)tree_destroy);
+
+    if (!parent) {
+        g_assert(target->file == NULL);
+        target->file = file;
+    }
+    else
+        g_hash_table_replace(parent->children_by_name,
+                             file->name,
+                             file);
+
+    if (!S_ISDIR(buf.st_mode))
+        notify(target, file, OBT_WATCH_ADDED);
+
+    file->watch_sys_data =
+        watch_sys_add_file(target->w->source,
+                           target, file, S_ISDIR(buf.st_mode));
+
+    /* recurse on the contents if it's a directory */
+    if (file && watch_main_file_is_dir(file)) {
+        GDir *dir;
+
+        dir = g_dir_open(path, 0, NULL);
+        if (dir) {
+            const gchar *name;
+
+            while ((name = g_dir_read_name(dir))) {
+                if (name[0] != '.' || target->watch_hidden) {
+                    gchar *subpath;
+                    ObtWatchFile *child;
+
+                    subpath = g_build_filename(path, name, NULL);
+                    child = tree_create(target, subpath, name, file);
+                    g_free(subpath);
+                }
+            }
+        }
+        g_dir_close(dir);
+    }
+
+    if (file)
+        file->modified = buf.st_mtime;
+
+    return file;
+}
+
+static void tree_destroy(ObtWatchFile *file)
+{
+    g_free(file->name);
+    if (file->children_by_name)
+        g_hash_table_unref(file->children_by_name);
+    g_slice_free(ObtWatchFile, file);
+}
+
+gboolean obt_watch_add(ObtWatch *w, const gchar *path, gboolean watch_hidden,
                        ObtWatchFunc func, gpointer data)
 {
     ObtWatchTarget *t;
@@ -127,18 +208,16 @@ gboolean obt_watch_add(ObtWatch *w, const gchar *path,
     g_return_val_if_fail(func != NULL, FALSE);
     g_return_val_if_fail(path[0] == G_DIR_SEPARATOR, FALSE);
 
-    t = g_slice_new0(ObtWatchTarget);
+    t = g_slice_new(ObtWatchTarget);
     t->w = w;
     t->base_path = g_strdup(path);
+    t->watch_hidden = watch_hidden;
     t->func = func;
     t->data = data;
-    g_hash_table_insert(w->targets_by_path, t->base_path, t);
+    t->file = NULL;
+    g_hash_table_replace(w->targets_by_path, t->base_path, t);
 
-    t->key = watch_sys_add_target(w->source, path, watch_hidden, t);
-    if (t->key < 0) {
-        g_hash_table_remove(w->targets_by_path, t->base_path);
-        return FALSE;
-    }
+    watch_main_notify_add(t, NULL, NULL);
 
     return TRUE;
 }
@@ -149,17 +228,229 @@ void obt_watch_remove(ObtWatch *w, const gchar *path)
     g_return_if_fail(path != NULL);
     g_return_if_fail(path[0] == G_DIR_SEPARATOR);
 
-    /* this also calls target_free */
+    /* this also calls target_free which does notifies */
     g_hash_table_remove(w->targets_by_path, path);
 }
 
-static void target_notify(const gchar *sub_path, const gchar *full_path,
-                          gpointer target, ObtWatchNotifyType type)
+static ObtWatchFile* refresh_file(ObtWatchTarget *target, ObtWatchFile *file,
+                                  ObtWatchFile *parent, const gchar *path)
+{
+    struct stat buf;
+
+    g_assert(file != NULL);
+
+    if (stat(path, &buf) < 0) {
+        watch_main_notify_remove(target, file);
+        file = NULL;
+    }
+
+    else if (S_ISDIR(buf.st_mode) != watch_main_file_is_dir(file)) {
+        gchar *name = g_strdup(file->name);
+
+        watch_main_notify_remove(target, file);
+        watch_main_notify_add(target, parent, name);
+        g_free(name);
+        file = NULL;
+    }
+
+    /* recurse on the contents if it's a directory */
+    else if (watch_main_file_is_dir(file)) {
+        GDir *dir;
+        GList *children, *it;
+
+        children = watch_main_file_children(file);
+        for (it = children; it; it = g_list_next(it))
+            ((ObtWatchFile*)it->data)->seen = FALSE;
+        g_list_free(children);
+
+        dir = g_dir_open(path, 0, NULL);
+        if (dir) {
+            const gchar *name;
+
+            while ((name = g_dir_read_name(dir))) {
+                if (name[0] != '.' || target->watch_hidden) {
+                    ObtWatchFile *child = watch_main_file_child(file, name);
+
+                    if (!child)
+                        watch_main_notify_add(target, file, name);
+                    else {
+                        gchar *subpath;
+                        ObtWatchFile *newchild;
+
+                        subpath = g_build_filename(path, name, NULL);
+                        newchild = refresh_file(target, child, file, subpath);
+                        g_free(subpath);
+
+                        if (newchild)
+                            child->seen = TRUE;
+                    }
+                }
+            }
+        }
+        g_dir_close(dir);
+
+        children = watch_main_file_children(file);
+        for (it = children; it; it = g_list_next(it))
+            if (((ObtWatchFile*)it->data)->seen == FALSE)
+                watch_main_notify_remove(target, it->data);
+        g_list_free(children);
+    }
+
+    /* check for modifications if it's a file */
+    else {
+        if (file->modified >= 0 && buf.st_mtime > file->modified)
+            watch_main_notify_modify(target, file);
+    }
+
+    if (file)
+        file->modified = buf.st_mtime;
+
+    return file;
+}
+
+static void foreach_refresh_target(gpointer k, gpointer v, gpointer u)
+{
+    ObtWatchTarget *target = v;
+
+    if (!target->file)
+        /* we don't have any files being watched, try look for
+           the target's root again */
+        watch_main_notify_add(target, NULL, NULL);
+    else
+        refresh_file(target, target->file, NULL, target->base_path);
+}
+
+void obt_watch_refresh(ObtWatch *w)
+{
+    g_return_if_fail(w != NULL);
+
+    g_hash_table_foreach(w->targets_by_path, foreach_refresh_target, NULL);
+}
+
+static void notify(ObtWatchTarget *target, ObtWatchFile *file,
+                   ObtWatchNotifyType type)
+{
+    gchar *sub_path = watch_main_file_sub_path(file);
+    target->func(target->w, target->base_path, sub_path, type,
+                 target->data);
+    g_free(sub_path);
+}
+
+void watch_main_notify_add(ObtWatchTarget *target,
+                           ObtWatchFile *parent, const gchar *name)
+{
+    gchar *path;
+
+    if (parent && name[0] == '.' && !target->watch_hidden)
+        return;
+
+    path = g_build_filename(target->base_path,
+                            watch_main_file_sub_path(parent),
+                            name,
+                            NULL);
+    tree_create(target, path, name, parent);
+    g_free(path);
+}
+
+static void foreach_child_notify_removed(gpointer k, gpointer v, gpointer u)
 {
-    ObtWatchTarget *t = target;
-    if (type == OBT_WATCH_SELF_REMOVED) {
-        /* this also calls target_free */
-        g_hash_table_remove(t->w->targets_by_path, t->base_path);
+    ObtWatchTarget *target = u;
+    ObtWatchFile *file = v;
+
+    if (watch_main_file_is_dir(file)) {
+        g_hash_table_foreach(file->children_by_name,
+                             foreach_child_notify_removed,
+                             target);
+        g_hash_table_remove_all(file->children_by_name);
     }
-    t->func(t->w, t->base_path, sub_path, full_path, type, t->data);
+    else if (!file->parent)
+        notify(target, file, OBT_WATCH_SELF_REMOVED);
+    else
+        notify(target, file, OBT_WATCH_REMOVED);
+
+    watch_sys_remove_file(target->w->source,
+                          target, file, file->watch_sys_data);
+}
+
+void watch_main_notify_remove(ObtWatchTarget *target, ObtWatchFile *file)
+{
+    foreach_child_notify_removed(NULL, file, target);
+
+    tree_destroy(file);
+    if (!file->parent)
+        target->file = NULL;
+}
+
+void watch_main_notify_modify(ObtWatchTarget *target, ObtWatchFile *file)
+{
+    if (!watch_main_file_is_dir(file)) {
+        notify(target, file, OBT_WATCH_MODIFIED);
+    }
+    file->modified = -1;
+}
+
+void build_sub_path(GString *str, ObtWatchFile *file)
+{
+    if (file->parent) {
+        build_sub_path(str, file->parent);
+        g_string_append(str, G_DIR_SEPARATOR_S);
+        g_string_append(str, file->name);
+    }
+}
+
+gboolean watch_main_target_watch_hidden(ObtWatchTarget *target)
+{
+    return target->watch_hidden;
+}
+
+ObtWatchFile* watch_main_target_root(ObtWatchTarget *target)
+{
+    return target->file;
+}
+
+gchar* watch_main_file_sub_path(ObtWatchFile *file)
+{
+    GString *str;
+    gchar *ret = NULL;
+
+    if (file) {
+        str = g_string_new("");
+        build_sub_path(str, file);
+        ret = str->str;
+        g_string_free(str, FALSE);
+    }
+
+    return ret;
+}
+
+gchar* watch_main_target_file_full_path(ObtWatchTarget *target,
+                                        ObtWatchFile *file)
+{
+    GString *str;
+    gchar *ret = NULL;
+
+    if (file) {
+        str = g_string_new(target->base_path);
+        build_sub_path(str, file);
+        ret = str->str;
+        g_string_free(str, FALSE);
+    }
+
+    return ret;
+}
+
+gboolean watch_main_file_is_dir(ObtWatchFile *file)
+{
+    return file->children_by_name != NULL;
+}
+
+ObtWatchFile* watch_main_file_child(ObtWatchFile *file,
+                                    const gchar *name)
+{
+    return g_hash_table_lookup(file->children_by_name, name);
+}
+
+GList* watch_main_file_children(ObtWatchFile *file)
+{
+    return g_hash_table_get_values(file->children_by_name);
 }
index 4df994dae26f1dec5816a9715b3f6559cb0e9240..b4d59372aeba697802b88fa22ad72c2e291a6ba7 100644 (file)
@@ -32,9 +32,11 @@ typedef enum _ObtWatchNotifyType ObtWatchNotifyType;
     notification is about the watch target itself, the subpath will be
     an empty string.
 */
-typedef void (*ObtWatchFunc)(ObtWatch *w, const gchar *base_path,
-                             const gchar *sub_path, const gchar *full_path,
-                             ObtWatchNotifyType type, gpointer data);
+typedef void (*ObtWatchFunc)(ObtWatch *w,
+                             const gchar *base_path,
+                             const gchar *sub_path,
+                             ObtWatchNotifyType type,
+                             gpointer data);
 
 enum _ObtWatchNotifyType {
     OBT_WATCH_ADDED, /*!< A file/dir was added in a watched dir */
@@ -63,6 +65,13 @@ gboolean obt_watch_add(ObtWatch *w, const gchar *path,
                        ObtWatchFunc func, gpointer data);
 void obt_watch_remove(ObtWatch *w, const gchar *path);
 
+/*! Force a refresh of the watcher.
+  This will report any changes since the last time the watcher refreshed its
+  view of the file system.  Note that any watchers that work off notifications
+  will have nothing to report for this function.
+*/
+void obt_watch_refresh(ObtWatch *w);
+
 G_END_DECLS
 
 #endif
index b55f5282a4fb3be38e4f000466d5ac914202fee7..492f8a44ca87513447191364be5c8a0ef8c11bd0 100644 (file)
@@ -19,6 +19,7 @@
 #ifdef HAVE_SYS_INOTIFY_H
 
 #include "watch.h"
+#include "watch_interface.h"
 
 #include <sys/inotify.h>
 #ifdef HAVE_UNISTD_H
 #include <glib.h>
 
 typedef struct _InoSource InoSource;
-typedef struct _InoTarget InoTarget;
-
-/*! Callback function in the watch general system.
-  Matches definition in watch.c
-*/
-typedef void (*ObtWatchNotifyFunc)(const gchar *sub_path,
-                                   const gchar *full_path, gpointer target,
-                                   ObtWatchNotifyType type);
+typedef struct _InoData InoData;
 
 struct _InoSource {
     GSource source;
 
     GPollFD pfd;
-    ObtWatchNotifyFunc notify;
     GHashTable *targets_by_key;
-    GHashTable *targets_by_path;
+    GHashTable *files_by_key;
 };
 
-struct _InoTarget {
+struct _InoData {
     gint key;
-    gchar *path;
-    guint base_len; /* the length of the prefix of path which is the
-                       target's path */
-    gpointer watch_target;
-    gboolean is_dir;
-    gboolean watch_hidden;
 };
 
 static gboolean source_check(GSource *source);
 static gboolean source_prepare(GSource *source, gint *timeout);
 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data);
 static void source_finalize(GSource *source);
-static gint add_target(GSource *source, InoTarget *parent,
-                       const gchar *path, gboolean watch_hidden,
-                       gpointer target);
-static void remove_target(GSource *source, InoTarget *target);
-static void target_free(InoTarget *target);
-static void notify_target(GSource *source, InoTarget *ino_target,
-                          const gchar *path, ObtWatchNotifyType type);
 
 static GSourceFuncs source_funcs = {
     source_prepare,
@@ -81,29 +61,12 @@ static GSourceFuncs source_funcs = {
     source_finalize
 };
 
-gint watch_sys_add_target(GSource *source, const char *path,
-                          gboolean watch_hidden, gpointer target)
-{
-    return add_target(source, NULL, path, watch_hidden, target);
-}
-
-void watch_sys_remove_target(GSource *source, gint key)
-{
-    InoSource *ino_source = (InoSource*)source;
-    InoTarget *t;
-
-    t = g_hash_table_lookup(ino_source->targets_by_key, &key);
-    remove_target(source, t);
-}
-
-GSource* watch_sys_create_source(ObtWatchNotifyFunc notify)
+GSource* watch_sys_create_source(void)
 {
     gint fd;
     GSource *source;
     InoSource *ino_source;
 
-    g_return_val_if_fail(notify != NULL, NULL);
-
     source = NULL;
     fd = inotify_init();
     if (fd < 0) {
@@ -111,15 +74,14 @@ GSource* watch_sys_create_source(ObtWatchNotifyFunc notify)
         g_warning("Failed to initialize inotify: %s", s);
     }
     else {
-        g_debug("initialized inotify on fd %d", fd);
+        g_debug("initialized inotify on fd %u", fd);
         source = g_source_new(&source_funcs, sizeof(InoSource));
         ino_source = (InoSource*)source;
-        ino_source->notify = notify;
         ino_source->targets_by_key = g_hash_table_new_full(
             g_int_hash, g_int_equal, NULL, NULL);
-        ino_source->targets_by_path = g_hash_table_new_full(
-            g_str_hash, g_str_equal, NULL, (GDestroyNotify)target_free);
-        ino_source->pfd = (GPollFD){ fd, G_IO_IN, G_IO_IN };
+        ino_source->files_by_key = g_hash_table_new_full(
+            g_int_hash, g_int_equal, NULL, NULL);
+        ino_source->pfd = (GPollFD){ fd, G_IO_IN, 0 };
         g_source_add_poll(source, &ino_source->pfd);
     }
     return source;
@@ -133,7 +95,9 @@ static gboolean source_prepare(GSource *source, gint *timeout)
 
 static gboolean source_check(GSource *source)
 {
-    return TRUE;
+    InoSource *ino_source = (InoSource*)source;
+
+    return ino_source->pfd.revents;
 }
 
 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
@@ -235,72 +199,65 @@ static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
             name_len == event.len)
         {
             /* done reading the file name ! */
-            InoTarget *t;
-            gboolean report;
-            ObtWatchNotifyType type;
-            gchar *full_path;
 
-            g_debug("read filename %s mask %x", name, event.mask);
+            if (event.len)
+                g_debug("read filename %s mask %x", name, event.mask);
+            else
+                g_debug("read no filename mask %x", event.mask);
 
             event.mask &= ~IN_IGNORED;  /* skip this one, we watch for things
                                            to get removed explicitly so this
                                            will just be double-reporting */
             if (event.mask) {
+                ObtWatchTarget *t;
+                ObtWatchFile *f;
+                gchar **name_split;
+                gchar **c;
 
+                f = g_hash_table_lookup(ino_source->files_by_key, &event.wd);
                 t = g_hash_table_lookup(ino_source->targets_by_key, &event.wd);
-                g_assert(t != NULL);
-
-                full_path = g_build_filename(t->path, name, NULL);
-                g_debug("full path to change: %s", full_path);
-
-                /* don't report hidden stuff inside a directory watch */
-                report = !t->is_dir || name[0] != '.' || t->watch_hidden;
-                if (event.mask & IN_MOVE_SELF) {
-                    g_warning("Watched target was moved away: %s", t->path);
-                    type = OBT_WATCH_SELF_REMOVED;
-                }
-                else if (event.mask & IN_ISDIR) {
-                    if (event.mask & IN_MOVED_TO ||
-                        event.mask & IN_CREATE)
-                    {
-                        add_target(source, t, full_path, t->watch_hidden,
-                                   t->watch_target);
-                        g_debug("added %s", full_path);
+                g_assert(f != NULL);
+
+                if (event.len)
+                    name_split = g_strsplit(name, G_DIR_SEPARATOR_S, 0);
+                else
+                    name_split = g_strsplit("", G_DIR_SEPARATOR_S, 0);
+
+                if (f) {
+                    if (event.mask & IN_MOVED_TO || event.mask & IN_CREATE) {
+                        ObtWatchFile *parent = f;
+                        for (c = name_split; *(c+1); ++c) {
+                            parent = watch_main_file_child(parent, *c);
+                            g_assert(parent);
+                        }
+                        watch_main_notify_add(t, parent, *c);
                     }
                     else if (event.mask & IN_MOVED_FROM ||
-                             event.mask & IN_DELETE)
+                             event.mask & IN_MOVE_SELF ||
+                             event.mask & IN_DELETE ||
+                             event.mask & IN_DELETE_SELF)
                     {
-                        InoTarget *subt;
-
-                        subt = g_hash_table_lookup(ino_source->targets_by_path,
-                                                   full_path);
-                        g_assert(subt);
-                        remove_target(source, subt);
-                        g_debug("removed %s", full_path);
+                        ObtWatchFile *file = f;
+                        for (c = name_split; *c; ++c)
+                            file = watch_main_file_child(file, *c);
+                        if (file) /* may not have been tracked */
+                            watch_main_notify_remove(t, file);
+                    }
+                    else if (event.mask & IN_MODIFY) {
+                        ObtWatchFile *file = f;
+                        for (c = name_split; *c; ++c)
+                            file = watch_main_file_child(file, *c);
+                        if (file) /* may not have been tracked */
+                            watch_main_notify_modify(t, file);
                     }
-                    report = FALSE;
-                }
-                else {
-                    if (event.mask & IN_MOVED_TO || event.mask & IN_CREATE)
-                        type = OBT_WATCH_ADDED;
-                    else if (event.mask & IN_MOVED_FROM ||
-                             event.mask & IN_DELETE)
-                        type = OBT_WATCH_REMOVED;
-                    else if (event.mask & IN_MODIFY)
-                        type = OBT_WATCH_MODIFIED;
                     else
                         g_assert_not_reached();
-                }
 
-                if (report) {
                     /* call the GSource callback if there is one */
                     if (cb) cb(data);
-
-                    /* call the WatchNotify callback */
-                    notify_target(source, t, full_path, type);
                 }
 
-                g_free(full_path);
+                g_strfreev(name_split);
 
                 /* only read one event at a time, so poll can tell us if there
                    is another one ready, and we don't block on the read()
@@ -313,6 +270,7 @@ static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
             state = READING_EVENT;
         }
     }
+    return TRUE;
 }
 
 static void source_finalize(GSource *source)
@@ -321,30 +279,34 @@ static void source_finalize(GSource *source)
     g_debug("source_finalize");
     close(ino_source->pfd.fd);
     g_hash_table_destroy(ino_source->targets_by_key);
+    g_hash_table_destroy(ino_source->files_by_key);
 }
 
-static gint add_target(GSource *source, InoTarget *parent,
-                       const gchar *path,
-                       gboolean watch_hidden, gpointer target)
+
+gpointer watch_sys_add_file(GSource *source,
+                            ObtWatchTarget *target,
+                            ObtWatchFile *file,
+                            gboolean is_dir)
 {
     InoSource *ino_source;
-    InoTarget *ino_target;
+    InoData *ino_data;
     guint32 mask;
     gint key;
-    gboolean is_dir;
+    gchar *path;
 
     ino_source = (InoSource*)source;
 
-    is_dir = g_file_test(path, G_FILE_TEST_IS_DIR);
     if (is_dir)
         mask = IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVE;
     else
         mask = IN_MODIFY;
     /* only watch IN_MOVE_SELF on the top-most target of the watch */
-    if (!parent)
-        mask |= IN_MOVE_SELF;
+    if (watch_main_target_root(target) == file)
+        mask |= IN_MOVE_SELF | IN_DELETE_SELF;
+
+    path = watch_main_target_file_full_path(target, file);
 
-    ino_target = NULL;
+    ino_data = NULL;
     key = inotify_add_watch(ino_source->pfd.fd, path, mask);
     g_debug("added watch descriptor %d for fd %d on path %s",
             key, ino_source->pfd.fd, path);
@@ -353,72 +315,41 @@ static gint add_target(GSource *source, InoTarget *parent,
         g_warning("Unable to watch path %s: %s", path, s);
     }
     else {
-        ino_target = g_slice_new(InoTarget);
-        ino_target->key = key;
-        ino_target->path = g_strdup(path);
-        ino_target->base_len = (parent ? parent->base_len : strlen(path));
-        ino_target->is_dir = is_dir;
-        ino_target->watch_hidden = watch_hidden;
-        ino_target->watch_target = target;
-        g_hash_table_insert(ino_source->targets_by_key, &ino_target->key,
-                            ino_target);
-        g_hash_table_insert(ino_source->targets_by_path, ino_target->path,
-                            ino_target);
+        ino_data = g_slice_new(InoData);
+        ino_data->key = key;
+        g_hash_table_insert(ino_source->targets_by_key,
+                            &ino_data->key,
+                            target);
+        g_hash_table_insert(ino_source->files_by_key,
+                            &ino_data->key,
+                            file);
     }
 
-    if (key >= 0 && is_dir) {
-        /* recurse */
-        GDir *dir;
-
-        dir = g_dir_open(path, 0, NULL);
-        if (dir) {
-            const gchar *name;
+    g_free(path);
 
-            while ((name = g_dir_read_name(dir))) {
-                if (name[0] != '.' || watch_hidden) {
-                    gchar *subpath;
-
-                    subpath = g_build_filename(path, name, NULL);
-                    if (g_file_test(subpath, G_FILE_TEST_IS_DIR))
-                        add_target(source, ino_target, subpath,
-                                   watch_hidden, target);
-                    else
-                        /* notify for each file in the directory on startup */
-                        notify_target(source, ino_target, subpath,
-                                      OBT_WATCH_ADDED);
-                    g_free(subpath);
-                }
-            }
-        }
-        g_dir_close(dir);
-    }
-
-    return key;
+    return ino_data;
 }
 
-static void remove_target(GSource *source, InoTarget *target)
+void watch_sys_remove_file(GSource *source,
+                           ObtWatchTarget *target,
+                           ObtWatchFile *file,
+                           gpointer data)
 {
-    InoSource *ino_source = (InoSource*)source;
-    g_debug("removing wd %d for fd %d", target->key, ino_source->pfd.fd);
-    inotify_rm_watch(ino_source->pfd.fd, (guint32)target->key);
-    g_hash_table_remove(ino_source->targets_by_key, &target->key);
-    g_hash_table_remove(ino_source->targets_by_path, target->path);
-}
+    InoSource *ino_source;
+    InoData *ino_data;
 
-static void target_free(InoTarget *target)
-{
-    g_free(target->path);
-    g_slice_free(InoTarget, target);
-}
+    if (data) {
+        ino_source = (InoSource*)source;
+        ino_data = data;
 
-static void notify_target(GSource *source, InoTarget *ino_target,
-                          const gchar *path, ObtWatchNotifyType type)
-{
-    InoSource *ino_source = (InoSource*)source;
-    ino_source->notify(path + ino_target->base_len,
-                       path,
-                       ino_target->watch_target,
-                       type);
+        g_debug("removing wd %d for fd %d", ino_data->key, ino_source->pfd.fd);
+        inotify_rm_watch(ino_source->pfd.fd, (guint32)ino_data->key);
+
+        g_hash_table_remove(ino_source->targets_by_key, &ino_data->key);
+        g_hash_table_remove(ino_source->files_by_key, &ino_data->key);
+        g_slice_free(InoData, ino_data);
+    }
 }
 
+
 #endif
diff --git a/obt/watch_interface.h b/obt/watch_interface.h
new file mode 100644 (file)
index 0000000..7866f67
--- /dev/null
@@ -0,0 +1,74 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   obt/watch_interface.h for the Openbox window manager
+   Copyright (c) 2010        Dana Jansens
+
+   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.
+
+   This program 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.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#ifndef __obt_watch_interface_h
+#define __obt_watch_interface_h
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ObtWatchTarget ObtWatchTarget;
+typedef struct _ObtWatchFile   ObtWatchFile;
+
+/*! Initializes the watch subsystem, and returns a GSource for it.
+  @param notify The GSource will call @notify when a watched file is changed.
+  @return Returns a GSource* on success, and a NULL if an error occurred.
+*/
+GSource* watch_sys_create_source(void);
+
+/*! Informs the watch system about a new file/dir.
+  It should return a structure that it wants to store inside the file.
+*/
+gpointer watch_sys_add_file(GSource *source,
+                            ObtWatchTarget *target,
+                            ObtWatchFile *file,
+                            gboolean is_dir);
+
+/*! Informs the watch system about the destruction of a file.
+  It can free the structure returned from watch_sys_add_file as this is given
+  in @data.
+*/
+void watch_sys_remove_file(GSource *source,
+                           ObtWatchTarget *target,
+                           ObtWatchFile *file,
+                           gpointer data);
+
+/* These are in watch.c, they are part of the main watch system */
+void watch_main_notify_add(ObtWatchTarget *target,
+                           ObtWatchFile *parent,
+                           const gchar *name);
+void watch_main_notify_remove(ObtWatchTarget *target,
+                              ObtWatchFile *file);
+void watch_main_notify_modify(ObtWatchTarget *target,
+                              ObtWatchFile *file);
+
+gboolean watch_main_target_watch_hidden(ObtWatchTarget *target);
+ObtWatchFile* watch_main_target_root(ObtWatchTarget *target);
+gchar* watch_main_target_file_full_path(ObtWatchTarget *target,
+                                        ObtWatchFile *file);
+
+gchar* watch_main_file_sub_path(ObtWatchFile *file);
+gboolean watch_main_file_is_dir(ObtWatchFile *file);
+ObtWatchFile* watch_main_file_child(ObtWatchFile *file,
+                                    const gchar *name);
+GList* watch_main_file_children(ObtWatchFile *file);
+
+G_END_DECLS
+
+#endif
diff --git a/obt/watch_manual.c b/obt/watch_manual.c
new file mode 100644 (file)
index 0000000..c9fa8c0
--- /dev/null
@@ -0,0 +1,94 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   obt/watch_manual.c for the Openbox window manager
+   Copyright (c) 2010        Dana Jansens
+
+   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.
+
+   This program 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.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+/*! This will just do nothing if a better mechanism isn't present, requiring
+  a manual refresh (via obt_watch_refresh()) to see updates in the filesystem.
+*/
+
+#ifndef HAVE_SYS_INOTIFY_H
+
+#include "watch.h"
+#include "watch_interface.h"
+
+#include <glib.h>
+
+typedef struct _ManualSource ManualSource;
+typedef struct _ManualFile ManualFile;
+
+static gboolean source_check(GSource *source);
+static gboolean source_prepare(GSource *source, gint *timeout);
+static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data);
+static void source_finalize(GSource *source);
+
+static GSourceFuncs source_funcs = {
+    source_prepare,
+    source_check,
+    source_read,
+    source_finalize
+};
+
+GSource* watch_sys_create_source()
+{
+    return g_source_new(&source_funcs, sizeof(GSource));
+}
+
+gpointer watch_sys_add_target(ObtWatchTarget *target)
+{
+    return NULL;
+}
+
+void watch_sys_remove_target(ObtWatchTarget *target, gpointer data)
+{
+}
+
+gpointer watch_sys_add_file(GSource *source,
+                            ObtWatchTarget *target,
+                            ObtWatchFile *file,
+                            gboolean is_dir)
+{
+    return NULL;
+}
+
+void watch_sys_remove_file(GSource *source,
+                           ObtWatchTarget *target,
+                           ObtWatchFile *file,
+                           gpointer data)
+{
+}
+
+static gboolean source_prepare(GSource *source, gint *timeout)
+{
+    *timeout = -1;
+    return FALSE;
+}
+
+static gboolean source_check(GSource *source)
+{
+    return FALSE;
+}
+
+static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
+{
+    return TRUE;
+}
+
+static void source_finalize(GSource *source)
+{
+}
+
+#endif