--- a/patches/gamin-01-all.diff Sun Mar 09 18:09:07 2008 +0000
+++ b/patches/gamin-01-all.diff Mon Mar 10 03:54:54 2008 +0000
@@ -270,15 +270,12 @@
dnl for the spec file
RELDATE=`date +'%a %b %e %Y'`
AC_SUBST(RELDATE)
-@@ -248,6 +259,46 @@
+@@ -248,6 +259,43 @@
backends="${backends}, kqueue"
fi
+case "$os" in
+ solaris*)
-+ PKG_CHECK_MODULES(GTHREAD, gthread-2.0 >= 2.14.0)
-+ AC_SUBST(GTHREAD_CFLAGS)
-+ AC_SUBST(GTHREAD_LIBS)
+ AM_CONDITIONAL(ON_SOLARIS, true)
+ AC_COMPILE_IFELSE([
+ #include <port.h>
@@ -317,7 +314,7 @@
dnl pthread support for reentrance of the client library.
AC_ARG_WITH(threads,
[ --with-threads add multithread support(on)])
-@@ -354,6 +405,14 @@
+@@ -354,6 +402,14 @@
AC_DEFINE(HAVE_CMSGCRED,1,[Have cmsgcred structure])
fi
@@ -332,7 +329,7 @@
#### Abstract sockets
AC_MSG_CHECKING(abstract socket namespace)
-@@ -501,49 +560,60 @@
+@@ -501,49 +557,60 @@
AC_SUBST(PYTHON_INCLUDES)
AC_SUBST(PYTHON_SITE_PACKAGES)
@@ -404,7 +401,7 @@
dnl ==========================================================================
-@@ -569,7 +639,7 @@
+@@ -569,7 +636,7 @@
prefix: ${prefix}
source code location: ${srcdir}
compiler: ${CC}
@@ -489,254 +486,882 @@
+
+#endif /* __GAM_FEN_H__ */
+
-Index: gamin/server/fen-thread-pool.c
+Index: gamin/server/fen-dump.c
===================================================================
---- gamin/server/fen-thread-pool.c (revision 0)
-+++ gamin/server/fen-thread-pool.c (revision 0)
-@@ -0,0 +1,216 @@
+--- gamin/server/fen-dump.c (revision 0)
++++ gamin/server/fen-dump.c (revision 0)
+@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 shiftwidth=4: */
+
-+#include <strings.h>
-+#include <sys/types.h>
-+#include <sys/stat.h>
-+#include <sys/time.h>
-+#include <errno.h>
-+#include <stdio.h>
-+#include <stdlib.h>
-+#include <unistd.h>
-+#include "fen-thread-pool.h"
++#include "config.h"
++#include <glib.h>
++#include <glib/gprintf.h>
++#include "fen-node.h"
++#include "fen-data.h"
++#include "fen-kernel.h"
++#include "fen-missing.h"
++#include "fen-dump.h"
++
++G_LOCK_EXTERN (fen_lock);
+
-+typedef struct thread_data th_data_t;
++/*-------------------- node ------------------*/
++static void
++dump_node (node_t* node, gpointer data)
++{
++ if (data && node->user_data) {
++ return;
++ }
++ g_printf ("[%s] < 0x%p : 0x%p > %s\n", __func__, node, node->user_data, NODE_NAME(node));
++}
++
++static gboolean
++dump_node_tree (node_t* node, gpointer user_data)
++{
++ node_op_t op = {dump_node, NULL, NULL, user_data};
++ GList* children;
++ GList* i;
++ if (G_TRYLOCK (fen_lock)) {
++ if (node) {
++ travel_nodes (node, &op);
++ }
++ G_UNLOCK (fen_lock);
++ }
++ return TRUE;
++}
+
-+struct thread_pool_data {
-+ pthread_t *threads;
-+ pthread_mutex_t q_mutex; /* protect the queue */
-+ pthread_cond_t q_cond;
-+ th_data_t *q_head; /* queue of data */
-+ th_data_t *q_tail;
-+ long q_len;
++/* ------------------ fdata port hash --------------------*/
++void
++dump_hash_cb (gpointer key,
++ gpointer value,
++ gpointer user_data)
++{
++ g_printf ("[%s] < 0x%p : 0x%p >\n", __func__, key, value);
++}
++
++gboolean
++dump_hash (GHashTable* hash, gpointer user_data)
++{
++ if (G_TRYLOCK (fen_lock)) {
++ if (g_hash_table_size (hash) > 0) {
++ g_hash_table_foreach (hash, dump_hash_cb, user_data);
++ }
++ G_UNLOCK (fen_lock);
++ }
++ return TRUE;
++}
+
-+ // pthread_mutex_t mutex; protect the following
-+ long max_t;
-+ long max_q;
-+};
++/* ------------------ event --------------------*/
++void
++dump_event (fnode_event_t* ev, gpointer user_data)
++{
++ fdata* data = ev->user_data;
++ g_printf ("[%s] < 0x%p : 0x%p > [ %10s ] %s\n", __func__, ev, ev->user_data, _event_string (ev->e), FN_NAME(data));
++}
+
-+struct thread_data {
-+ thp_run_cb run;
-+ thp_data_destroy_cb destroy;
-+ void *data;
-+ struct thread_data *prev;
-+ struct thread_data *next;
-+};
++void
++dump_event_queue (fdata* data, gpointer user_data)
++{
++ if (G_TRYLOCK (fen_lock)) {
++ if (data->eventq) {
++ g_queue_foreach (data->eventq, (GFunc)dump_event, user_data);
++ }
++ G_UNLOCK (fen_lock);
++ }
++}
++
+Index: gamin/server/fen-dump.h
+===================================================================
+--- gamin/server/fen-dump.h (revision 0)
++++ gamin/server/fen-dump.h (revision 0)
+@@ -0,0 +1,7 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++#ifndef _FEN_DUMP_H_
++#define _FEN_DUMP_H_
++
+
-+static void *thread_run (void * data);
-+#define MUTEX_LOCK(lock) \
-+do { \
-+ if (pthread_mutex_lock(&lock) != 0) { \
-+ abort (); \
-+ } \
-+} while (0)
-+#define MUTEX_UNLOCK(lock) \
-+do { \
-+ if (pthread_mutex_unlock(&lock) != 0) { \
-+ abort (); \
-+ } \
-+} while (0)
++#endif /* _FEN_DUMP_H_ */
+Index: gamin/server/fen-helper.c
+===================================================================
+--- gamin/server/fen-helper.c (revision 0)
++++ gamin/server/fen-helper.c (revision 0)
+@@ -0,0 +1,309 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
+
-+static void *
-+thread_run (void * data)
++#include "config.h"
++#include <glib.h>
++#include "fen-data.h"
++#include "fen-helper.h"
++#include "fen-kernel.h"
++#ifdef GIO_COMPILATION
++#include "gfilemonitor.h"
++#else
++#include "gam_event.h"
++#include "gam_server.h"
++#include "gam_protocol.h"
++#endif
++
++#define FH_W if (fh_debug_enabled) g_warning
++//static gboolean fh_debug_enabled = TRUE;
++static gboolean fh_debug_enabled = FALSE;
++
++G_LOCK_EXTERN (fen_lock);
++
++static void default_emit_event_cb (fdata *f, int events);
++static void default_emit_once_event_cb (fdata *f, int events, gpointer sub);
++static int default_event_converter (int event);
++
++static void
++scan_children_init (node_t *f, gpointer sub)
+{
-+ thp_t *tp = (thp_t *) data;
-+ int ret;
-+ th_data_t *td = NULL;
++ GDir *dir;
++ GError *err = NULL;
++ node_op_t op = {NULL, NULL, pre_del_cb, NULL};
++ fdata* pdata;
++
++ FH_W ("%s %s [0x%p]\n", __func__, NODE_NAME(f), f);
++ pdata = node_get_data (f);
+
-+ for (;;) {
-+ MUTEX_LOCK (tp->q_mutex);
-+ while (tp->q_len == 0) {
-+ if ((ret = pthread_cond_wait(&tp->q_cond, &tp->q_mutex)) != 0) {
-+ perror ("thread_run - pthread_cond_wait");
-+ exit (1);
-+ }
-+ }
++ dir = g_dir_open (NODE_NAME(f), 0, &err);
++ if (dir) {
++ const char *basename;
++
++ while ((basename = g_dir_read_name (dir)))
++ {
++ node_t *childf = NULL;
++ fdata* data;
++ GList *idx;
+
-+ td = tp->q_head;
-+ if (--tp->q_len == 0) {
-+ tp->q_head = tp->q_tail = NULL;
-+ } else {
-+ tp->q_head = td->next;
-+ tp->q_head->prev = NULL;
-+ }
-+
-+ MUTEX_UNLOCK (tp->q_mutex);
-+
-+ /* run task */
-+ td->run (td->data);
-+ if (td->destroy) {
-+ td->destroy (td->data);
-+ }
-+ free (td);
-+ td = NULL;
-+ }
++ childf = children_find (f, basename);
++ if (childf == NULL) {
++ gchar *filename;
++
++ filename = g_build_filename (NODE_NAME(f), basename, NULL);
++ childf = add_node (f, filename);
++ g_assert (childf);
++ g_free (filename);
++ }
++ if ((data = node_get_data (childf)) == NULL) {
++ data = fdata_new (childf, FALSE);
++ }
++
++ if (is_ported (data)) {
++ /* Ignored */
++ } else if (/* !is_ported (data) && */
++ port_add (&data->fobj, &data->len, data)) {
++ /* Emit created to all other subs */
++ fdata_emit_events (data, FN_EVENT_CREATED);
++ }
++ /* Emit created to the new sub */
++#ifdef GIO_COMPILATION
++ fdata_emit_events_once (data, FN_EVENT_CREATED, sub);
++#else
++ gam_server_emit_one_event (NODE_NAME(childf),
++ gam_subscription_is_dir (sub), GAMIN_EVENT_EXISTS, sub, 1);
++#endif
++ }
++ g_dir_close (dir);
++ } else {
++ //g_debug (err->message);
++ g_error_free (err);
++ }
+}
+
+/**
-+ * Create a thread pool, returns the pointer. Return NULL if failed.
++ * fen_add
++ *
++ * Won't hold a ref, we have a timout callback to clean unused fdata.
++ * If there is no value for a key, add it and return it; else return the old
++ * one.
+ */
-+
-+extern thp_t *
-+thread_pool_new (int max_thread_num, int max_queue_num)
++void
++fen_add (const gchar *filename, gpointer sub, gboolean is_mondir)
+{
-+ thp_t *tp;
-+ int i, ret;
++ node_op_t op = {NULL, add_missing_cb, pre_del_cb, (gpointer)filename};
++ node_t* f;
++ fdata* data;
++
++ g_assert (filename);
++ g_assert (sub);
++
++ G_LOCK (fen_lock);
++ f = find_node_full (filename, &op);
++ FH_W ("[ %s ] f[0x%p] sub[0x%p] %s\n", __func__, f, sub, filename);
++ g_assert (f);
++ data = node_get_data (f);
++ if (data == NULL) {
++ data = fdata_new (f, is_mondir);
++ }
+
-+ tp = (thp_t *) calloc (1, sizeof (thp_t));
-+ if (tp == NULL)
-+ return NULL;
++ if (is_mondir) {
++ data->mon_dir_num ++;
++ }
++
++ /* Change to active */
++#ifdef GIO_COMPILATION && 0
++ if (port_add (&data->fobj, &data->len, data) ||
++ g_file_test (FN_NAME(data), G_FILE_TEST_EXISTS)) {
++ if (is_mondir) {
++ scan_children_init (f, sub);
++ }
++ fdata_sub_add (data, sub);
++ } else {
++ fdata_sub_add (data, sub);
++ fdata_adjust_deleted (data);
++ }
++#else
++ if (port_add (&data->fobj, &data->len, data) ||
++ g_file_test (FN_NAME(data), G_FILE_TEST_EXISTS)) {
++ gam_server_emit_one_event (FN_NAME(data),
++ gam_subscription_is_dir (sub), GAMIN_EVENT_EXISTS, sub, 1);
++ if (is_mondir) {
++ scan_children_init (f, sub);
++ }
++ gam_server_emit_one_event (FN_NAME(data),
++ gam_subscription_is_dir (sub), GAMIN_EVENT_ENDEXISTS, sub, 1);
++ fdata_sub_add (data, sub);
++ } else {
++ fdata_sub_add (data, sub);
++ gam_server_emit_one_event (FN_NAME(data),
++ gam_subscription_is_dir (sub), GAMIN_EVENT_DELETED, sub, 1);
++ fdata_adjust_deleted (data);
++ gam_server_emit_one_event (FN_NAME(data),
++ gam_subscription_is_dir (sub), GAMIN_EVENT_ENDEXISTS, sub, 1);
++ }
++#endif
++ G_UNLOCK (fen_lock);
++}
++
++void
++fen_remove (const gchar *filename, gpointer sub, gboolean is_mondir)
++{
++ node_op_t op = {NULL, add_missing_cb, pre_del_cb, (gpointer)filename};
++ node_t* f;
++ fdata* data;
++
++ g_assert (filename);
++ g_assert (sub);
++
++ G_LOCK (fen_lock);
++ f = find_node (filename);
++ FH_W ("[ %s ] f[0x%p] sub[0x%p] %s\n", __func__, f, sub, filename);
+
-+ tp->max_t = max_thread_num;
-+ tp->max_q = max_queue_num;
-+ if ((ret = pthread_mutex_init (&tp->q_mutex, NULL)) != 0) {
-+ perror ("thread_pool_new - pthread_mutex_init");
-+ free (tp);
-+ return NULL;
-+ }
-+ if ((ret = pthread_cond_init(&tp->q_cond, NULL)) != 0) {
-+ perror ("thread_pool_new - pthread_cond_init");
-+ if (pthread_mutex_destroy (&tp->q_mutex) != 0) {
-+ perror ("thread_pool_new - pthread_mutex_destroy");
-+ }
-+ free (tp);
-+ return NULL;
-+ }
-+ tp->threads = (pthread_t *) calloc (max_thread_num, sizeof(pthread_t));
-+ if (tp->threads == NULL) {
-+ free (tp);
-+ return NULL;
-+ }
-+ for (i = 0; i < max_thread_num; i++) {
-+ if (pthread_create (&tp->threads[i], NULL,
-+ thread_run,
-+ (void *)tp) != 0) {
-+ perror ("thread_pool_new - pthread_create");
-+ exit (1);
-+ }
-+ }
-+ return tp;
++ g_assert (f);
++ data = node_get_data (f);
++ g_assert (data);
++
++ if (is_mondir) {
++ data->mon_dir_num --;
++ }
++ fdata_sub_remove (data, sub);
++ if (FN_IS_PASSIVE(data)) {
++#ifdef GIO_COMPILATION
++ pending_remove_node (f, &op);
++#else
++ remove_node (f, &op);
++#endif
++ }
++ G_UNLOCK (fen_lock);
++}
++
++static gboolean
++fen_init_once_func (gpointer data)
++{
++ g_debug ("%s\n", __func__);
++ if (!node_class_init ()) {
++ g_debug ("node_class_init failed.");
++ return FALSE;
++ }
++ if (!fdata_class_init (default_emit_event_cb,
++ default_emit_once_event_cb,
++ default_event_converter)) {
++ g_debug ("fdata_class_init failed.");
++ return FALSE;
++ }
++ return TRUE;
++}
++
++gboolean
++fen_init ()
++{
++#ifdef GIO_COMPILATION
++ static GOnce fen_init_once = G_ONCE_INIT;
++ g_once (&fen_init_once, (GThreadFunc)fen_init_once_func, NULL);
++ return (gboolean)fen_init_once.retval;
++#else
++ return fen_init_once_func (NULL);
++#endif
++}
++
++static void
++default_emit_once_event_cb (fdata *f, int events, gpointer sub)
++{
++#ifdef GIO_COMPILATION
++ GFile* child;
++ fen_sub* _sub = (fen_sub*)sub;
++ child = g_file_new_for_path (FN_NAME(f));
++ g_file_monitor_emit_event (G_FILE_MONITOR (_sub->user_data),
++ child, NULL, events);
++ g_object_unref (child);
++#else
++ gam_server_emit_one_event (FN_NAME(f),
++ gam_subscription_is_dir (sub), events, sub, 1);
++#endif
++}
++
++static void
++default_emit_event_cb (fdata *f, int events)
++{
++ GList* i;
++ fdata* pdata;
++
++#ifdef GIO_COMPILATION
++ GFile* child;
++ child = g_file_new_for_path (FN_NAME(f));
++ for (i = f->subs; i; i = i->next) {
++ fen_sub* sub = (fen_sub*)i->data;
++ gboolean file_is_dir = sub->is_mondir;
++ if (events != G_FILE_MONITOR_EVENT_CHANGED || !file_is_dir) {
++ g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
++ child, NULL, events);
++ }
++ }
++ if ((pdata = get_parent_data (f)) != NULL) {
++ for (i = pdata->subs; i; i = i->next) {
++ fen_sub* sub = (fen_sub*)i->data;
++ gboolean file_is_dir = sub->is_mondir;
++ g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
++ child, NULL, events);
++ }
++ }
++ g_object_unref (child);
++#else
++ for (i = f->subs; i; i = i->next) {
++ gboolean file_is_dir = gam_subscription_is_dir (i->data);
++ if (events != GAMIN_EVENT_CHANGED || !file_is_dir) {
++ gam_server_emit_one_event (FN_NAME(f), file_is_dir, events, i->data, 1);
++ }
++ }
++ if ((pdata = get_parent_data (f)) != NULL) {
++ for (i = pdata->subs; i; i = i->next) {
++ gboolean file_is_dir = gam_subscription_is_dir (i->data);
++ gam_server_emit_one_event (FN_NAME(f), file_is_dir, events, i->data, 1);
++ }
++ }
++#endif
+}
+
-+/**
-+ * Dostroy a thread pool.
-+ *
-+ * (Not finished yet, shouldn't be invoked.)
-+ */
++static int
++default_event_converter (int event)
++{
++#ifdef GIO_COMPILATION
++ switch (event) {
++ case FN_EVENT_CREATED:
++ return G_FILE_MONITOR_EVENT_CREATED;
++ case FILE_DELETE:
++ case FILE_RENAME_FROM:
++ return G_FILE_MONITOR_EVENT_DELETED;
++ case UNMOUNTED:
++ return G_FILE_MONITOR_EVENT_UNMOUNTED;
++ case FILE_ATTRIB:
++ return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
++ case MOUNTEDOVER:
++ case FILE_MODIFIED:
++ case FILE_RENAME_TO:
++ return G_FILE_MONITOR_EVENT_CHANGED;
++ default:
++ /* case FILE_ACCESS: */
++ g_assert_not_reached ();
++ return -1;
++ }
++#else
++ switch (event) {
++ case FN_EVENT_CREATED:
++ return GAMIN_EVENT_CREATED;
++ case FILE_DELETE:
++ case FILE_RENAME_FROM:
++ return GAMIN_EVENT_DELETED;
++ case FILE_ATTRIB:
++ case MOUNTEDOVER:
++ case UNMOUNTED:
++ case FILE_MODIFIED:
++ case FILE_RENAME_TO:
++ return GAMIN_EVENT_CHANGED;
++ default:
++ /* case FILE_ACCESS: */
++ g_assert_not_reached ();
++ return -1;
++ }
++#endif
++}
+Index: gamin/server/fen-helper.h
+===================================================================
+--- gamin/server/fen-helper.h (revision 0)
++++ gamin/server/fen-helper.h (revision 0)
+@@ -0,0 +1,15 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#include "fen-sub.h"
++
++#ifndef _FEN_HELPER_H_
++#define _FEN_HELPER_H_
++
++void fen_add (const gchar *filename, gpointer sub, gboolean is_mondir);
++void fen_remove (const gchar *filename, gpointer sub, gboolean is_mondir);
++
++/* FEN subsystem initializing */
++gboolean fen_init ();
++
++#endif /* _FEN_HELPER_H_ */
+Index: gamin/server/fen-node.c
+===================================================================
+--- gamin/server/fen-node.c (revision 0)
++++ gamin/server/fen-node.c (revision 0)
+@@ -0,0 +1,441 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
+
-+extern void
-+thread_pool_destroy (thp_t *tp, int wait)
++#include "config.h"
++#include <errno.h>
++#include <strings.h>
++#include <glib.h>
++#include "fen-node.h"
++#include "fen-dump.h"
++
++#define NODE_STAT(n) (((node_t*)(n))->stat)
++
++struct _dnode {
++ gchar* filename;
++ node_op_t* op;
++ GTimeVal tv;
++};
++
++#define FN_W if (fn_debug_enabled) g_warning
++static gboolean fn_debug_enabled = TRUE;
++
++G_LOCK_EXTERN (fen_lock);
++#define PROCESS_DELETING_INTERVAL 900 /* in second */
++
++static node_t* _head = NULL;
++static GList *deleting_nodes = NULL;
++static guint deleting_nodes_id = 0;
++
++static node_t* node_new (node_t* parent, const gchar* basename);
++static void node_delete (node_t* parent);
++static gboolean remove_node_internal (node_t* node, node_op_t* op);
++static void children_add (node_t *p, node_t *f);
++static void children_remove (node_t *p, node_t *f);
++static guint children_foreach_remove (node_t *f, GHRFunc func, gpointer user_data);
++static void children_foreach (node_t *f, GHFunc func, gpointer user_data);
++static gboolean children_remove_cb (gpointer key,
++ gpointer value,
++ gpointer user_data);
++
++static struct _dnode*
++_dnode_new (const gchar* filename, node_op_t* op)
++{
++ struct _dnode* d;
++
++ g_assert (op);
++ if ((d = g_new (struct _dnode, 1)) != NULL) {
++ d->filename = g_strdup (filename);
++ d->op = g_memdup (op, sizeof (node_op_t));
++ g_assert (d->op);
++ g_get_current_time (&d->tv);
++ g_time_val_add (&d->tv, PROCESS_DELETING_INTERVAL);
++ }
++ return d;
++}
++
++static void
++_dnode_free (struct _dnode* d)
++{
++ g_assert (d);
++ g_free (d->filename);
++ g_free (d->op);
++ g_free (d);
++}
++
++static gboolean
++g_timeval_lt (GTimeVal *val1, GTimeVal *val2)
++{
++ if (val1->tv_sec < val2->tv_sec)
++ return TRUE;
++
++ if (val1->tv_sec > val2->tv_sec)
++ return FALSE;
++
++ /* val1->tv_sec == val2->tv_sec */
++ if (val1->tv_usec < val2->tv_usec)
++ return TRUE;
++
++ return FALSE;
++}
++
++static gboolean
++scan_deleting_nodes (gpointer data)
+{
-+ int i;
-+ /* clean the queue first */
-+ /* clean up the resources */
-+ if (pthread_mutex_destroy (&tp->q_mutex) != 0) {
-+ perror ("pthread_mutex_destroy");
-+ }
-+ if (pthread_cond_destroy (&tp->q_cond) != 0) {
-+ perror ("pthread_cond_destroy");
-+ }
-+ for (i = 0; i < tp->max_t; i++) {
-+ pthread_join (tp->threads[i], NULL);
-+ }
-+ free (tp);
++ struct _dnode* d;
++ GTimeVal tv_now;
++ GList* i;
++ GList* deleted_list = NULL;
++ gboolean ret = TRUE;
++ node_t* node;
++
++ g_get_current_time (&tv_now);
++
++ if (G_TRYLOCK (fen_lock)) {
++ for (i = deleting_nodes; i; i = i->next) {
++ d = (struct _dnode*)i->data;
++ /* Time to free, try only once */
++ if (g_timeval_lt (&d->tv, &tv_now)) {
++ if ((node = find_node (d->filename)) != NULL) {
++ remove_node_internal (node, d->op);
++ }
++ _dnode_free (d);
++ deleted_list = g_list_prepend (deleted_list, i);
++ }
++ }
++
++ for (i = deleted_list; i; i = i->next) {
++ deleting_nodes = g_list_remove_link (deleting_nodes,
++ (GList *)i->data);
++ g_list_free_1 ((GList *)i->data);
++ }
++ g_list_free (deleted_list);
++
++ if (deleting_nodes == NULL) {
++ deleting_nodes_id = 0;
++ ret = FALSE;
++ }
++ G_UNLOCK (fen_lock);
++ }
++ return ret;
++}
++
++gpointer
++node_get_data (node_t* node)
++{
++ g_assert (node);
++ return node->user_data;
++}
++
++gpointer
++node_set_data (node_t* node, gpointer user_data)
++{
++ gpointer data = node->user_data;
++ g_assert (node);
++ node->user_data = user_data;
++ return data;
++}
++
++void
++travel_nodes (node_t* node, node_op_t* op)
++{
++ GList* children;
++ GList* i;
++
++ if (node) {
++ if (op && op->hit) {
++ op->hit (node, op->user_data);
++ }
++ }
++ children = g_hash_table_get_values (node->children);
++ if (children) {
++ for (i = children; i; i = i->next) {
++ travel_nodes (i->data, op);
++ }
++ g_list_free (children);
++ }
++}
++
++static node_t*
++find_node_internal (node_t* node, const gchar* filename, node_op_t* op)
++{
++ gchar* str;
++ gchar* token;
++ gchar* lasts;
++ node_t* parent;
++ node_t* child;
++
++ g_assert (filename && filename[0] == '/');
++ g_assert (node);
++
++ parent = node;
++ str = g_strdup (filename + strlen (NODE_NAME(parent)));
++
++ if ((token = strtok_r (str, G_DIR_SEPARATOR_S, &lasts)) != NULL) {
++ do {
++ //FN_W ("%s %s + %s\n", __func__, NODE_NAME(parent), token);
++ child = children_find (parent, token);
++ if (child) {
++ parent = child;
++ } else {
++ if (op && op->add_missing) {
++ child = op->add_missing (parent, op->user_data);
++ goto L_hit;
++ }
++ break;
++ }
++ } while ((token = strtok_r (NULL, G_DIR_SEPARATOR_S, &lasts)) != NULL);
++ } else {
++ /* It's the head */
++ g_assert (parent == _head);
++ child = _head;
++ }
++
++ if (token == NULL && child) {
++ L_hit:
++ if (op && op->hit) {
++ op->hit (child, op->user_data);
++ }
++ }
++ g_free (str);
++ return child;
++}
++
++node_t*
++find_node (const gchar *filename)
++{
++ return find_node_internal (_head, filename, NULL);
++}
++
++node_t*
++find_node_full (const gchar* filename, node_op_t* op)
++{
++ return find_node_internal (_head, filename, op);
++}
++
++node_t*
++add_node (node_t* parent, const gchar* filename)
++{
++ gchar* str;
++ gchar* token;
++ gchar* lasts;
++ node_t* child = NULL;
++
++ g_assert (_head);
++ g_assert (filename && filename[0] == '/');
++
++ if (parent == NULL) {
++ parent = _head;
++ }
++
++ str = g_strdup (filename + strlen (NODE_NAME(parent)));
++
++ if ((token = strtok_r (str, G_DIR_SEPARATOR_S, &lasts)) != NULL) {
++ do {
++ //FN_W ("%s %s + %s\n", __func__, NODE_NAME(parent), token);
++ child = node_new (parent, token);
++ if (child) {
++ children_add (parent, child);
++ parent = child;
++ } else {
++ break;
++ }
++ } while ((token = strtok_r (NULL, G_DIR_SEPARATOR_S, &lasts)) != NULL);
++ }
++ g_free (str);
++ if (token == NULL) {
++ return child;
++ } else {
++ return NULL;
++ }
+}
+
+/**
-+ * Run a task via run_cb.
-+ * Returns 0 if successful.
++ * delete recursively
+ */
++static gboolean
++remove_children (node_t* node, node_op_t* op)
++{
++ //FN_W ("%s 0x%p [id:%5d] [c:%5d] %s\n", __func__, f, f->eventq_id, g_hash_table_size(f->children), NODE_NAME(f));
++ /* Change to passive */
++ if (children_num (node) > 0) {
++ children_foreach_remove (node, children_remove_cb,
++ (gpointer)op);
++ }
++ if (children_num (node) == 0) {
++ return TRUE;
++ }
++ return FALSE;
++}
++
++static gboolean
++remove_node_internal (node_t* node, node_op_t* op)
++{
++ node_t* parent = NULL;
++ /*
++ * If the parent is passive and doesn't have children, delete it.
++ * NOTE node_delete_deep is a depth first delete recursively.
++ * Top node is deleted in node_cancel_sub
++ */
++ g_assert (node);
++ g_assert (op && op->pre_del);
++ if (node != _head) {
++ if (remove_children (node, op)) {
++ if (node->user_data) {
++ if (!op->pre_del (node, op->user_data)) {
++ return FALSE;
++ }
++ }
++ parent = node->parent;
++ children_remove (parent, node);
++ node_delete (node);
++ if (children_num (parent) == 0) {
++ remove_node_internal (parent, op);
++ }
++ return TRUE;
++ }
++ return FALSE;
++ }
++ return TRUE;
++}
+
-+extern int
-+thread_pool_run (thp_t *tp, thp_run_cb run_cb, void *data)
++void
++pending_remove_node (node_t* node, node_op_t* op)
++{
++ struct _dnode* d;
++ GList* l;
++
++ for (l = deleting_nodes; l; l=l->next) {
++ d = (struct _dnode*) l->data;
++ if (g_ascii_strcasecmp (d->filename, NODE_NAME(node)) == 0) {
++ return;
++ }
++ }
++
++ d = _dnode_new (NODE_NAME(node), op);
++ g_assert (d);
++ deleting_nodes = g_list_prepend (deleting_nodes, d);
++ if (deleting_nodes_id == 0) {
++ deleting_nodes_id = g_timeout_add_seconds (PROCESS_DELETING_INTERVAL,
++ scan_deleting_nodes,
++ NULL);
++ g_assert (deleting_nodes_id > 0);
++ }
++}
++
++void
++remove_node (node_t* node, node_op_t* op)
++{
++ remove_node_internal (node, op);
++}
++
++static node_t*
++node_new (node_t* parent, const gchar* basename)
+{
-+ return thread_pool_run_full (tp, run_cb, data, NULL);
++ node_t *f = NULL;
++
++ g_assert (basename && basename[0]);
++ if ((f = g_new0 (node_t, 1)) != NULL) {
++ if (parent) {
++ f->basename = g_strdup (basename);
++ f->filename = g_build_filename (G_DIR_SEPARATOR_S,
++ NODE_NAME(parent), basename, NULL);
++ } else {
++ f->basename = g_strdup (basename);
++ f->filename = g_strdup (basename);
++ }
++ f->children = g_hash_table_new_full (g_str_hash, g_str_equal,
++ NULL, (GDestroyNotify)node_delete);
++ FN_W ("[ %s ] 0x%p %s\n", __func__, f, NODE_NAME(f));
++ }
++ return f;
++}
++
++static void
++node_delete (node_t *f)
++{
++ FN_W ("[ %s ] 0x%p %s\n", __func__, f, NODE_NAME(f));
++ g_assert (g_hash_table_size (f->children) == 0);
++ g_assert (f->user_data == NULL);
++
++ g_hash_table_unref (f->children);
++ g_free (f->basename);
++ g_free (f->filename);
++ g_free (f);
++}
++
++static void
++children_add (node_t *p, node_t *f)
++{
++ //FN_W ("%s [p] %8s [c] %8s\n", __func__, p->basename, f->basename);
++ g_hash_table_insert (p->children, f->basename, f);
++ f->parent = p;
++}
++
++static void
++children_remove (node_t *p, node_t *f)
++{
++ //FN_W ("%s [p] %8s [c] %8s\n", __func__, p->basename, f->basename);
++ g_hash_table_steal (p->children, f->basename);
++ f->parent = NULL;
++}
++
++guint
++children_num (node_t *f)
++{
++ return g_hash_table_size (f->children);
++}
++
++node_t *
++children_find (node_t *f, const gchar *basename)
++{
++ return (node_t *) g_hash_table_lookup (f->children, (gpointer)basename);
+}
+
+/**
-+ * Run a task via run_cb. After run_cb, the data will destroyed by destroy_cb.
-+ * Returns 0 if successful.
++ * depth first delete recursively
+ */
-+
-+extern int
-+thread_pool_run_full (thp_t *tp,
-+ thp_run_cb run_cb,
-+ void *data,
-+ thp_data_destroy_cb destroy_cb)
++static gboolean
++children_remove_cb (gpointer key,
++ gpointer value,
++ gpointer user_data)
+{
-+ th_data_t *td = NULL;
-+ int ret;
++ node_t* f = (node_t*)value;
++ node_op_t* op = (node_op_t*) user_data;
++
++ g_assert (f->parent);
+
-+ if ((td = (th_data_t *) calloc (1, sizeof (th_data_t))) == NULL) {
-+ return errno;
-+ }
-+
-+ td->run = run_cb;
-+ td->data = data;
-+ td->destroy = destroy_cb;
-+
-+ /* ready for adding */
-+ MUTEX_LOCK (tp->q_mutex);
-+
-+ if (tp->q_len >= tp->max_q) {
-+ /* is full */
-+ MUTEX_UNLOCK (tp->q_mutex);
-+ free (td);
-+ return tp->max_q;
-+ }
++ //FN_W ("%s [p] %8s [c] %8s\n", __func__, p->basename, f->basename);
++ if (remove_children (f, op)) {
++ if (f->user_data != NULL) {
++ return op->pre_del (f, op->user_data);
++ }
++ return TRUE;
++ }
++ return FALSE;
++}
+
-+ if (tp->q_len == 0) {
-+ tp->q_tail = tp->q_head = td;
-+ } else {
-+ td->prev = tp->q_tail;
-+ tp->q_tail->next = td;
-+ tp->q_tail = td;
-+ }
-+ tp->q_len ++;
-+
-+ if ((ret = pthread_cond_broadcast(&tp->q_cond)) != 0) {
-+ perror ("thread_pool_run_full - pthread_cond_wait");
-+ abort ();
-+ }
-+
-+ MUTEX_UNLOCK (tp->q_mutex);
-+
-+ return 0;
++static guint
++children_foreach_remove (node_t *f, GHRFunc func, gpointer user_data)
++{
++ g_assert (f);
++
++ return g_hash_table_foreach_remove (f->children, func, user_data);
+}
-Index: gamin/server/fen-thread-pool.h
-===================================================================
---- gamin/server/fen-thread-pool.h (revision 0)
-+++ gamin/server/fen-thread-pool.h (revision 0)
-@@ -0,0 +1,22 @@
-+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-+/* vim:set expandtab ts=4 shiftwidth=4: */
+
-+#include <pthread.h>
-+
-+#ifndef _FEN_THREAD_POOL_H_
-+#define _FEN_THREAD_POOL_H_
++static void
++children_foreach (node_t *f, GHFunc func, gpointer user_data)
++{
++ g_assert (f);
++
++ g_hash_table_foreach (f->children, func, user_data);
++}
+
-+typedef struct thread_pool_data thp_t;
-+
-+typedef void (*thp_run_cb) (void *data);
-+typedef void (*thp_data_destroy_cb) (void *data);
-+
-+extern thp_t *thread_pool_new (int max_thread_num, int max_queue_num);
-+extern void thread_pool_destroy (thp_t *tp, int wait);
-+extern int thread_pool_run (thp_t *tp, thp_run_cb run_cb, void *data);
-+extern int thread_pool_run_full (thp_t *tp,
-+ thp_run_cb run_cb,
-+ void *data,
-+ thp_data_destroy_cb destroy_cb);
-+
-+#endif /* _FEN_THREAD_POOL_H_ */
++gboolean
++node_class_init ()
++{
++ g_debug ("%s\n", __func__);
++ if (_head == NULL) {
++ _head = node_new (NULL, G_DIR_SEPARATOR_S);
++ }
++ return _head != NULL;
++}
Index: gamin/server/gam_channel.c
===================================================================
--- gamin/server/gam_channel.c (revision 328)
@@ -792,6 +1417,198 @@
GAM_DEBUG(DEBUG_INFO,
"Socket credentials not supported on this OS\n");
goto failed;
+Index: gamin/server/fen-node.h
+===================================================================
+--- gamin/server/fen-node.h (revision 0)
++++ gamin/server/fen-node.h (revision 0)
+@@ -0,0 +1,53 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++#ifndef _FEN_NODE_H_
++#define _FEN_NODE_H_
++
++typedef struct node node_t;
++
++struct node
++{
++ gchar *filename;
++ gchar *basename;
++ gint stat;
++
++ /* the parent and children of node */
++ node_t *parent;
++ GHashTable *children; /* children in basename */
++
++ gpointer user_data;
++};
++
++#define IS_TOPNODE(fp) (((node_t *)(fp))->parent == NULL)
++#define NODE_NAME(fp) (((node_t *)(fp))->filename)
++
++typedef struct node_op
++{
++ /* find */
++ void (*hit) (node_t* node, gpointer user_data);
++ node_t* (*add_missing) (node_t* parent, gpointer user_data);
++ /* create */
++ //void (*create) (node_t* node, gpointer user_data);
++ /* delete */
++ gboolean (*pre_del) (node_t* node, gpointer user_data);
++ /* data */
++ gpointer user_data;
++} node_op_t;
++
++node_t* add_node (node_t* parent, const gchar* filename);
++void remove_node (node_t* node, node_op_t* op);
++void pending_remove_node (node_t* node, node_op_t* op);
++
++void travel_nodes (node_t* node, node_op_t* op);
++node_t* find_node_full (const gchar* filename, node_op_t* op);
++node_t* find_node (const gchar *filename);
++
++node_t* children_find (node_t *f, const gchar *basename);
++guint children_num (node_t *f);
++
++gpointer node_get_data (node_t* node);
++gpointer node_set_data (node_t* node, gpointer user_data);
++
++gboolean node_class_init ();
++
++#endif /* _FEN_NODE_H_ */
+Index: gamin/server/fen-missing.c
+===================================================================
+--- gamin/server/fen-missing.c (revision 0)
++++ gamin/server/fen-missing.c (revision 0)
+@@ -0,0 +1,107 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/*
++ * Design:
++ * A Solaris port has a resource limit of events (port_max_events) which
++ * limits the number of objects (fds) that can be actively associated objects
++ * whith the port. The default is (65536), but can be changed.
++ *
++ * project.max-port-ids identify the max number of ports
++ * process.max-port-events identify the max objs of a port
++ * process.max-file-descriptor identify the max fds of a process
++ *
++ * For a user server process, process.max-file-descriptor seems a bottleneck.
++ * I will use a port list for monitor fds to avoid process.max-file-descriptor
++ * is greater than process.max-port-events.
++ */
++#include "config.h"
++#include <glib.h>
++#include "fen-data.h"
++#include "fen-missing.h"
++
++G_LOCK_EXTERN (fen_lock);
++#define SCAN_MISSING_INTERVAL 4000 /* in milliseconds */
++#define FM_W if (fm_debug_enabled) g_warning
++
++/* global data structure for scan missing files */
++gboolean fm_debug_enabled = TRUE;
++static GList *missing_list = NULL;
++static guint scan_missing_source_id = 0;
++
++static gboolean scan_missing_list (gpointer data);
++
++static gboolean
++scan_missing_list (gpointer data)
++{
++ GList *existing_list = NULL;
++ GList *idx = NULL;
++ fdata *f;
++ gboolean ret = TRUE;
++
++ G_LOCK (fen_lock);
++
++ for (idx = missing_list; idx; idx = idx->next) {
++ f = (fdata*)idx->data;
++
++ if (port_add (&f->fobj, &f->len, f)) {
++ /* TODO - emit CREATE event */
++ fdata_emit_events (f, FN_EVENT_CREATED);
++ existing_list = g_list_prepend (existing_list, idx);
++ }
++ }
++
++ for (idx = existing_list; idx; idx = idx->next) {
++ missing_list = g_list_remove_link (missing_list, (GList *)idx->data);
++ g_list_free_1 ((GList *)idx->data);
++ }
++ g_list_free (existing_list);
++
++ if (missing_list == NULL) {
++ scan_missing_source_id = 0;
++ ret = FALSE;
++ }
++
++ G_UNLOCK (fen_lock);
++ return ret;
++}
++
++/**
++ * missing_add
++ *
++ * Unsafe, need lock fen_lock.
++ */
++void
++missing_add (fdata *f)
++{
++ GList *idx;
++
++ g_assert (!is_ported (f));
++
++ if (g_list_find (missing_list, f) != NULL) {
++ FM_W ("%s is ALREADY added %s\n", __func__, FN_NAME(f));
++ return;
++ }
++ FM_W ("%s is added %s\n", __func__, FN_NAME(f));
++
++ missing_list = g_list_prepend (missing_list, f);
++
++ /* if doesn't scan, then start */
++ if (scan_missing_source_id == 0) {
++ scan_missing_source_id = g_timeout_add (SCAN_MISSING_INTERVAL,
++ scan_missing_list,
++ NULL);
++ g_assert (scan_missing_source_id > 0);
++ }
++}
++
++/**
++ * missing_remove
++ *
++ * Unsafe, need lock fen_lock.
++ */
++void
++missing_remove (fdata *f)
++{
++ //FM_W ("%s %s\n", __func__, FN_NAME(f));
++ missing_list = g_list_remove (missing_list, f);
++}
+Index: gamin/server/fen-missing.h
+===================================================================
+--- gamin/server/fen-missing.h (revision 0)
++++ gamin/server/fen-missing.h (revision 0)
+@@ -0,0 +1,17 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#ifndef __GAM_FEN_H__
++#define __GAM_FEN_H__
++
++#include "fen-data.h"
++
++G_BEGIN_DECLS
++
++extern void missing_add (fdata *f);
++extern void missing_remove (fdata *f);
++
++G_END_DECLS
++
++#endif /* __GAM_FEN_H__ */
++
Index: gamin/server/gam_fs.c
===================================================================
--- gamin/server/gam_fs.c (revision 328)
@@ -880,544 +1697,1055 @@
===================================================================
--- gamin/server/fen-data.c (revision 0)
+++ gamin/server/fen-data.c (revision 0)
-@@ -0,0 +1,322 @@
+@@ -0,0 +1,684 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 shiftwidth=4: */
+
+#include "config.h"
++#include <port.h>
+#include <sys/types.h>
-+#include <strings.h>
-+#include <fcntl.h>
-+#include <unistd.h>
-+#include <pthread.h>
-+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <errno.h>
-+#include <stdio.h>
-+#include <stdlib.h>
-+#include "gam_error.h"
++#include <glib.h>
+#include "fen-data.h"
+#include "fen-kernel.h"
-+
-+//#define FEN_DEBUG_ALL
-+static GHashTable *fnode_hash = NULL;
-+static pthread_mutex_t fnode_hash_mutex = PTHREAD_MUTEX_INITIALIZER;
-+
-+#define MUTEX_LOCK(lock) \
-+do { \
-+ if (pthread_mutex_lock(&lock) != 0) { \
-+ g_assert_not_reached (); \
-+ } \
-+} while (0)
++#include "fen-missing.h"
++#include "fen-dump.h"
+
-+#define MUTEX_UNLOCK(lock) \
-+do { \
-+ if (pthread_mutex_unlock(&lock) != 0) { \
-+ g_assert_not_reached (); \
-+ } \
-+} while (0)
++#define PROCESS_EVENTQ_TIME 10 /* in milliseconds */
++#define PAIR_EVENTS_TIMEVAL 00000 /* in microseconds */
++#define PAIR_EVENTS_INC_TIMEVAL 0000 /* in microseconds */
++#define SCAN_CHANGINGS_TIME 50 /* in milliseconds */
++#define SCAN_CHANGINGS_MAX_TIME (4*100) /* in milliseconds */
++#define SCAN_CHANGINGS_MIN_TIME (4*100) /* in milliseconds */
++#define INIT_CHANGES_NUM 2
++#define BASE_NUM 2
+
-+int
-+fnode_trylock (pthread_mutex_t *mutex)
-+{
-+ int ret;
-+ if ((ret = pthread_mutex_trylock (mutex)) == 0) {
-+ return 0;
-+ } else if (ret != EBUSY) {
-+ perror ("fnode_trylock");
-+ abort ();
-+ }
-+ return 1;
-+}
++#define FD_W if (fd_debug_enabled) g_warning
++static gboolean fd_debug_enabled = TRUE;
+
-+fnode_t *
-+fnode_new (const gchar *path, port_emit_events_cb emit_events)
-+{
-+ fnode_t *fn = NULL;
++G_LOCK_EXTERN (fen_lock);
++static GList *deleting_data = NULL;
++static guint deleting_data_id = 0;
++
++static void (*emit_once_cb) (fdata *f, int events, gpointer sub);
++static void (*emit_cb) (fdata *f, int events);
++static int (*_event_converter) (int event);
+
-+ g_assert (path && path[0]);
-+ if ((fn = calloc (1, sizeof (fnode_t))) != NULL) {
-+ if (pthread_mutex_init (&fn->ref_lock, NULL) != 0) {
-+ perror ("fnode_new - pthread_mutex_init");
-+ free (fn);
-+ return NULL;
-+ }
-+ if (pthread_mutex_init (&fn->fn_lock, NULL) != 0) {
-+ perror ("fnode_new - pthread_mutex_init");
-+ free (fn);
-+ return NULL;
-+ }
-+ if (pthread_mutex_init (&fn->prop_lock, NULL) != 0) {
-+ perror ("fnode_new - pthread_mutex_init");
-+ free (fn);
-+ return NULL;
-+ }
-+ FN_PATH(fn) = strdup (path);
-+ fn->emit_events = emit_events;
-+ fn->ref = 1;
-+ }
-+ return fn;
-+}
++static gboolean fdata_delete (fdata* f);
++static gint fdata_sub_find (gpointer a, gpointer b);
++static void scan_children (node_t *f);
++static void scan_known_children (node_t* f);
+
-+void
-+fnode_delete (fnode_t *fn)
++node_t*
++add_missing_cb (node_t* parent, gpointer user_data)
+{
-+ g_assert (fn->ref == 0);
-+ g_assert (fn->subs == NULL);
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : FNODE_DELETE 0x%p %s\n", fn, FN_PATH(fn));
-+ if (pthread_mutex_destroy (&fn->prop_lock) != 0) {
-+ g_assert_not_reached ();
-+ }
-+ if (pthread_mutex_destroy (&fn->fn_lock) != 0) {
-+ g_assert_not_reached ();
-+ }
-+ if (pthread_mutex_destroy (&fn->ref_lock) != 0) {
-+ g_assert_not_reached ();
-+ }
-+ free (FN_PATH(fn));
-+ free (fn);
-+}
-+
-+void
-+fnode_set_stat (fnode_t *f, struct stat *sbuf)
-+{
-+ file_obj_t *fobj = &f->fobj;
-+
-+ MUTEX_LOCK (f->prop_lock);
-+ fobj->fo_atime = sbuf->st_atim;
-+ fobj->fo_mtime = sbuf->st_mtim;
-+ fobj->fo_ctime = sbuf->st_ctim;
-+ f->is_dir = S_ISDIR (sbuf->st_mode) ? TRUE : FALSE;
-+ MUTEX_UNLOCK (f->prop_lock);
-+}
-+
-+int
-+fnode_mdcount (fnode_t *f)
-+{
-+ int count;
-+ MUTEX_LOCK (f->prop_lock);
-+ g_assert (f->md_ref >= 0);
-+ count = f->md_ref;
-+ MUTEX_UNLOCK (f->prop_lock);
-+ return count;
-+}
-+
-+void
-+fnode_mdref (fnode_t *f)
-+{
-+ MUTEX_LOCK (f->prop_lock);
-+ f->md_ref ++;
-+ g_assert (f->md_ref > 0);
-+ MUTEX_UNLOCK (f->prop_lock);
-+}
-+
-+void
-+fnode_mdunref (fnode_t *f)
-+{
-+ MUTEX_LOCK (f->prop_lock);
-+ g_assert (f->md_ref > 0);
-+ f->md_ref --;
-+ MUTEX_UNLOCK (f->prop_lock);
-+}
-+
-+void
-+fnode_ref (fnode_t *f)
-+{
-+ MUTEX_LOCK (f->ref_lock);
-+ f->ref ++;
-+ MUTEX_UNLOCK (f->ref_lock);
-+}
-+
-+void
-+fnode_unref (fnode_t *f)
-+{
-+ MUTEX_LOCK (f->ref_lock);
-+ f->ref --;
-+ MUTEX_UNLOCK (f->ref_lock);
++ g_assert (parent);
++ //FD_W ("%s p:0x%p %s\n", __func__, parent, (gchar*)user_data);
++ return add_node (parent, (gchar*)user_data);
+}
+
+gboolean
-+fnode_is_dir (fnode_t *f)
++pre_del_cb (node_t* node, gpointer user_data)
++{
++ fdata* data;
++
++ g_assert (node);
++ data = node_get_data (node);
++ //FD_W ("%s node:0x%p %s\n", __func__, node, NODE_NAME(node));
++ if (data != NULL) {
++ if (!FN_IS_PASSIVE(data)) {
++ return FALSE;
++ }
++ fdata_delete (data);
++ }
++ return TRUE;
++}
++
++static guint
++_pow (guint x, guint y)
++{
++ guint z = 1;
++ g_assert (x >= 0 && y >= 0);
++ for (; y > 0; y--) {
++ z *= x;
++ }
++ return z;
++}
++
++static guint
++get_scalable_scan_time (fdata* data)
++{
++ guint sleep_time;
++ /* Caculate from num = 0 */
++ sleep_time = _pow (BASE_NUM, data->changed_event_num) * SCAN_CHANGINGS_TIME;
++ if (sleep_time < SCAN_CHANGINGS_MIN_TIME) {
++ sleep_time = SCAN_CHANGINGS_MIN_TIME;
++ } else if (sleep_time > SCAN_CHANGINGS_MAX_TIME) {
++ sleep_time = SCAN_CHANGINGS_MAX_TIME;
++ data->change_update_id = INIT_CHANGES_NUM;
++ }
++ //FD_W ("SCALABE SCAN num:time [ %4u : %4u ] %s\n", data->changed_event_num, sleep_time, FN_NAME(data));
++ return sleep_time;
++}
++
++static gboolean
++g_timeval_lt (GTimeVal *val1, GTimeVal *val2)
+{
-+ gboolean retval;
++ if (val1->tv_sec < val2->tv_sec)
++ return TRUE;
++
++ if (val1->tv_sec > val2->tv_sec)
++ return FALSE;
++
++ /* val1->tv_sec == val2->tv_sec */
++ if (val1->tv_usec < val2->tv_usec)
++ return TRUE;
++
++ return FALSE;
++}
++
++/**
++ * If all active children nodes are ported, then cancel monitor the parent node
++ *
++ * Unsafe, need lock.
++ */
++static void
++scan_known_children (node_t* f)
++{
++ GDir *dir;
++ GError *err = NULL;
++ fdata* pdata;
++
++ FD_W ("%s %s [0x%p]\n", __func__, NODE_NAME(f), f);
++ pdata = node_get_data (f);
++ /*
++ * Currect fdata must is directly monitored. Be sure it is 1 level monitor.
++ */
++ dir = g_dir_open (NODE_NAME(f), 0, &err);
++ if (dir) {
++ const char *basename;
++
++ while ((basename = g_dir_read_name (dir)))
++ {
++ node_t* childf = NULL;
++ fdata* data;
++ GList *idx;
++ /*
++ * If the node is existed, and isn't ported, then emit created
++ * event. Ignore others.
++ */
++ childf = children_find (f, basename);
++ if (childf &&
++ (data = node_get_data (childf)) != NULL &&
++ !FN_IS_PASSIVE (data)) {
++ if (!is_ported (data) &&
++ port_add (&data->fobj, &data->len, data)) {
++ fdata_emit_events (data, FN_EVENT_CREATED);
++ }
++ }
++ }
++ g_dir_close (dir);
++ } else {
++ //FD_W (err->message);
++ g_error_free (err);
++ }
++}
++
++static void
++scan_children (node_t *f)
++{
++ GDir *dir;
++ GError *err = NULL;
++ fdata* pdata;
++
++ FD_W ("%s %s [0x%p]\n", __func__, NODE_NAME(f), f);
++ pdata = node_get_data (f);
++ /*
++ * Currect fdata must is directly monitored. Be sure it is 1 level monitor.
++ */
++ dir = g_dir_open (NODE_NAME(f), 0, &err);
++ if (dir) {
++ const char *basename;
++
++ while ((basename = g_dir_read_name (dir)))
++ {
++ node_t* childf = NULL;
++ fdata* data;
++ GList *idx;
+
-+ MUTEX_LOCK (f->prop_lock);
-+ retval = f->is_dir;
-+ MUTEX_UNLOCK (f->prop_lock);
-+ return retval;
++ childf = children_find (f, basename);
++ if (childf == NULL) {
++ gchar *filename;
++
++ filename = g_build_filename (NODE_NAME(f), basename, NULL);
++ childf = add_node (f, filename);
++ g_assert (childf);
++ data = fdata_new (childf, FALSE);
++ g_free (filename);
++ }
++ if ((data = node_get_data (childf)) == NULL) {
++ data = fdata_new (childf, FALSE);
++ }
++ /* Be sure data isn't ported and add to port successfully */
++ /* Don't need delete it, it will be deleted by the parent */
++ if (is_ported (data)) {
++ /* Ignored */
++ } else if (/* !is_ported (data) && */
++ port_add (&data->fobj, &data->len, data)) {
++ fdata_emit_events (data, FN_EVENT_CREATED);
++ }
++ }
++ g_dir_close (dir);
++ } else {
++ //FD_W (err->message);
++ g_error_free (err);
++ }
++}
++
++static gboolean
++scan_deleting_data (gpointer data)
++{
++ fdata *f;
++ GList* i;
++ GList* deleted_list = NULL;
++ gboolean ret = TRUE;
++
++ if (G_TRYLOCK (fen_lock)) {
++ for (i = deleting_data; i; i = i->next) {
++ f = (fdata*)i->data;
++ if (fdata_delete (f)) {
++ deleted_list = g_list_prepend (deleted_list, i);
++ }
++ }
++
++ for (i = deleted_list; i; i = i->next) {
++ deleting_data = g_list_remove_link (deleting_data,
++ (GList *)i->data);
++ g_list_free_1 ((GList *)i->data);
++ }
++ g_list_free (deleted_list);
++
++ if (deleting_data == NULL) {
++ deleting_data_id = 0;
++ ret = FALSE;
++ }
++ G_UNLOCK (fen_lock);
++ }
++ return ret;
++}
++
++fdata*
++get_parent_data (fdata* data)
++{
++ if (FN_NODE(data) && !IS_TOPNODE(FN_NODE(data))) {
++ return node_get_data (FN_NODE(data)->parent);
++ }
++ return NULL;
++}
++
++node_t*
++get_parent_node (fdata* data)
++{
++ if (FN_NODE(data)) {
++ return (FN_NODE(data)->parent);
++ }
++ return NULL;
++}
++
++fdata *
++fdata_new (node_t* node, gboolean is_mondir)
++{
++ fdata *f = NULL;
++
++ g_assert (node);
++ if ((f = g_new0 (fdata, 1)) != NULL) {
++ FN_NODE(f) = node;
++ FN_NAME(f) = g_strdup (NODE_NAME(node));
++ f->is_dir = is_mondir;
++ f->eventq = g_queue_new ();
++ FD_W ("[ %s ] 0x%p %s\n", __func__, f, FN_NAME(f));
++ node_set_data (node, f);
++ }
++ return f;
++}
++
++static gboolean
++fdata_delete (fdata *f)
++{
++ fnode_event_t *ev;
++
++ //FD_W ("[ TRY %s ] 0x%p id[%4d:%4d] %s\n", __func__, f, f->eventq_id, f->change_update_id, FN_NAME(f));
++ g_assert (FN_IS_PASSIVE(f));
++
++ port_remove (f);
++ //missing_remove (f);
++
++ if (f->node != NULL) {
++ node_set_data (f->node, NULL);
++ f->node = NULL;
++ }
++
++ if (f->change_update_id > 0 || f->eventq_id > 0) {
++ if (FN_IS_LIVING(f)) {
++ f->is_cancelled = TRUE;
++ deleting_data = g_list_prepend (deleting_data, f);
++ if (deleting_data_id == 0) {
++ deleting_data_id = g_idle_add (scan_deleting_data, NULL);
++ g_assert (deleting_data_id > 0);
++ }
++ }
++ return FALSE;
++ }
++ FD_W ("[ %s ] 0x%p %s\n", __func__, f, FN_NAME(f));
++
++ while ((ev = g_queue_pop_head (f->eventq)) != NULL) {
++ fnode_event_delete (ev);
++ }
++
++ g_queue_free (f->eventq);
++ g_free (FN_NAME(f));
++ g_free (f);
++ return TRUE;
+}
+
+void
-+fnode_emit_events (fnode_t *f, int events)
++fdata_reset (fdata* data)
+{
-+ g_assert (f->emit_events);
-+ f->emit_events (f, events);
-+}
++ fnode_event_t *ev;
++
++ g_assert (data);
+
-+gboolean
-+fn_hash_init ()
-+{
-+ fnode_hash = g_hash_table_new(g_str_hash, g_str_equal);
++ while ((ev = g_queue_pop_head (data->eventq)) != NULL) {
++ fnode_event_delete (ev);
++ }
+}
+
-+fnode_t *
-+fn_hash_safe_lookup (const gchar *file_name)
-+{
-+ fnode_t *fn;
-+
-+ MUTEX_LOCK (fnode_hash_mutex);
-+ fn = (fnode_t *) g_hash_table_lookup (fnode_hash, file_name);
-+ MUTEX_UNLOCK (fnode_hash_mutex);
-+ return fn;
-+}
-+
-+fnode_t *
-+fn_hash_safe_node_new (const gchar *file_name,
-+ port_emit_events_cb emit_events)
++static gint
++fdata_sub_find (gpointer a, gpointer b)
+{
-+ fnode_t *fn;
-+
-+ MUTEX_LOCK (fnode_hash_mutex);
-+ fn = (fnode_t *) g_hash_table_lookup (fnode_hash, file_name);
-+ if (fn == NULL) {
-+ /* there is no lock now */
-+ fn = fnode_new (file_name, emit_events);
-+ if (fn != NULL) {
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : FNODE_NEW 0x%p %s\n", fn, FN_PATH(fn));
-+ g_hash_table_insert (fnode_hash, (gpointer) FN_PATH(fn), fn);
-+ }
-+ } else {
-+ fnode_ref (fn);
-+ }
-+ MUTEX_UNLOCK (fnode_hash_mutex);
-+ return fn;
++ if (a != b) {
++ return 1;
++ } else {
++ return 0;
++ }
+}
+
+void
-+fn_hash_safe_foreach (GHFunc func, gpointer user_data)
-+{
-+ MUTEX_LOCK (fnode_hash_mutex);
-+ g_hash_table_foreach (fnode_hash, func, user_data);
-+ MUTEX_UNLOCK (fnode_hash_mutex);
-+}
-+
-+void
-+fn_hash_safe_foreach_remove (GHRFunc func, gpointer user_data)
++fdata_sub_add (fdata *f, gpointer sub)
+{
-+ MUTEX_LOCK (fnode_hash_mutex);
-+ g_hash_table_foreach_remove (fnode_hash, func, user_data);
-+ MUTEX_UNLOCK (fnode_hash_mutex);
-+}
-+
-+void
-+fn_hash_foreach_remove (GHRFunc func, gpointer user_data)
-+{
-+ g_hash_table_foreach_remove (fnode_hash, func, user_data);
++ //FD_W ("[%s] [data: 0x%p ] [s: 0x%p ] %s\n", __func__, f, sub, FN_NAME(f));
++ g_assert (g_list_find_custom (f->subs, sub, (GCompareFunc)fdata_sub_find) == NULL);
++ f->subs = g_list_prepend (f->subs, sub);
+}
+
+void
-+fn_hash_lock ()
++fdata_sub_remove (fdata *f, gpointer sub)
++{
++ GList *l;
++ //FD_W ("[%s] [data: 0x%p ] [s: 0x%p ] %s\n", __func__, f, sub, FN_NAME(f));
++ g_assert (g_list_find_custom (f->subs, sub, (GCompareFunc)fdata_sub_find) != NULL);
++ l = g_list_find_custom (f->subs, sub, (GCompareFunc)fdata_sub_find);
++ g_assert (l);
++ g_assert (sub == l->data);
++ f->subs = g_list_delete_link (f->subs, l);
++}
++
++/**
++ * Adjust self on failing to Port
++ */
++void
++fdata_adjust_deleted (fdata* f)
+{
-+ MUTEX_LOCK (fnode_hash_mutex);
++ node_t* parent;
++ fdata* pdata;
++ node_op_t op = {NULL, NULL, pre_del_cb, NULL};
++
++ /*
++ * It's a top node. We move it to missing list.
++ */
++ parent = get_parent_node (f);
++ pdata = get_parent_data (f);
++ if (!FN_IS_PASSIVE(f) ||
++ children_num (FN_NODE(f)) > 0 ||
++ (pdata && !FN_IS_PASSIVE(pdata))) {
++ if (parent) {
++ if (pdata == NULL) {
++ pdata = fdata_new (parent, FALSE);
++ }
++ g_assert (pdata);
++ if (!port_add (&pdata->fobj, &pdata->len, pdata)) {
++ fdata_adjust_deleted (pdata);
++ }
++ } else {
++ /* f is root */
++ g_assert (IS_TOPNODE(FN_NODE(f)));
++ missing_add (f);
++ }
++ } else {
++#ifdef GIO_COMPILATION
++ pending_remove_node (FN_NODE(f), &op);
++#else
++ remove_node (FN_NODE(f), &op);
++#endif
++ }
+}
+
-+int
-+fn_hash_trylock ()
++static gboolean
++fdata_adjust_changed (fdata *f)
+{
-+ int ret;
-+ if ((ret = pthread_mutex_trylock (&fnode_hash_mutex)) == 0) {
-+ return 0;
-+ } else if (ret != EBUSY) {
-+ perror ("fn_hash_trylock");
-+ abort ();
++ fnode_event_t *ev;
++ struct stat buf;
++ node_t* parent;
++ fdata* pdata;
++
++ G_LOCK (fen_lock);
++ parent = get_parent_node (f);
++ pdata = get_parent_data (f);
++
++ if (!FN_IS_LIVING(f) ||
++ (children_num (FN_NODE(f)) == 0 &&
++ FN_IS_PASSIVE(f) &&
++ pdata && FN_IS_PASSIVE(pdata))) {
++ f->change_update_id = 0;
++ G_UNLOCK (fen_lock);
++ return FALSE;
++ }
++
++ //FD_W ("[ %s ] %s\n", __func__, FN_NAME(f));
++ if (FN_STAT (FN_NAME(f), &buf) != 0) {
++ FD_W ("LSTAT [%-20s] %s\n", FN_NAME(f), g_strerror (errno));
++ goto L_delete;
+ }
-+ return 1;
++ f->is_dir = S_ISDIR (buf.st_mode) ? TRUE : FALSE;
++ if (f->len != buf.st_size) {
++ //FD_W ("LEN [%lld:%lld] %s\n", f->len, buf.st_size, FN_NAME(f));
++ f->len = buf.st_size;
++ ev = fnode_event_new (FILE_MODIFIED, TRUE, f);
++ if (ev != NULL) {
++ ev->is_pending = TRUE;
++ fdata_add_event (f, ev);
++ }
++ /* Fdata is still changing, so scalable scan */
++ f->change_update_id = g_timeout_add (get_scalable_scan_time (f),
++ (GSourceFunc)fdata_adjust_changed,
++ (gpointer)f);
++ G_UNLOCK (fen_lock);
++ return FALSE;
++ } else {
++ f->changed_event_num = 0;
++ f->fobj.fo_atime = buf.st_atim;
++ f->fobj.fo_mtime = buf.st_mtim;
++ f->fobj.fo_ctime = buf.st_ctim;
++ if (FN_IS_DIR(f)) {
++ if (FN_IS_MONDIR(f)) {
++ scan_children (FN_NODE(f));
++ } else {
++ scan_known_children (FN_NODE(f));
++ if ((children_num (FN_NODE(f)) == 0 &&
++ FN_IS_PASSIVE(f) &&
++ pdata && FN_IS_PASSIVE(pdata))) {
++ port_remove (f);
++ goto L_exit;
++ }
++ }
++ }
++ if (!port_add_simple (&f->fobj, f)) {
++ L_delete:
++ ev = fnode_event_new (FILE_DELETE, FALSE, f);
++ if (ev != NULL) {
++ fdata_add_event (f, ev);
++ }
++ }
++ }
++L_exit:
++ f->change_update_id = 0;
++ G_UNLOCK (fen_lock);
++ return FALSE;
++}
++
++void
++fdata_emit_events_once (fdata *f, int event, gpointer sub)
++{
++ emit_once_cb (f, _event_converter (event), sub);
+}
+
+void
-+fn_hash_unlock ()
++fdata_emit_events (fdata *f, int event)
++{
++ emit_cb (f, _event_converter (event));
++}
++
++static gboolean
++process_events (gpointer udata)
+{
-+ MUTEX_UNLOCK (fnode_hash_mutex);
++ node_op_t op = {NULL, NULL, pre_del_cb, NULL};
++ gboolean is_pending = FALSE;
++ fdata* f;
++ fnode_event_t* ev;
++ int e;
++
++ //FD_W ("IN <======== %s\n", __func__);
++
++ f = (fdata*)udata;
++ //FD_W ("%s 0x%p id:%-4d %s\n", __func__, f, f->eventq_id, FN_NAME(f));
++
++ G_LOCK (fen_lock);
++
++ if (!FN_IS_LIVING(f)) {
++ f->eventq_id = 0;
++ G_UNLOCK (fen_lock);
++ return FALSE;
++ }
++
++ if ((ev = (fnode_event_t*)g_queue_pop_head (f->eventq)) != NULL) {
++ /* Send events to clients. */
++ e = ev->e;
++ is_pending = ev->is_pending;
++ if (!is_pending) {
++ fdata_emit_events (f, ev->e);
++#ifdef GIO_COMPILATION
++ if (ev->has_twin) {
++ fdata_emit_events (f, FILE_ATTRIB);
++ }
++#endif
++ }
++
++ fnode_event_delete (ev);
++ ev = NULL;
++
++ /* Adjust node state. */
++ /*
++ * Node the node has been created, so we can delete create event in
++ * optimizing. To reduce the statings, we add it to Port on discoving
++ * it then emit CREATED event. So we don't need to do anything here.
++ */
++ switch (e) {
++ case FILE_MODIFIED:
++ case MOUNTEDOVER:
++ case UNMOUNTED:
++ /* If the event is a changed event, then pending process it */
++ if (f->change_update_id == 0) {
++ f->change_update_id = g_timeout_add (get_scalable_scan_time(f),
++ (GSourceFunc)fdata_adjust_changed,
++ (gpointer)f);
++ g_assert (f->change_update_id > 0);
++ }
++ break;
++ case FILE_ATTRIB: /* Ignored */
++ case FILE_DELETE:
++ break;
++ default:
++ g_assert_not_reached ();
++ break;
++ }
++ /* Process one event a time */
++ G_UNLOCK (fen_lock);
++ return TRUE;
++ }
++ f->eventq_id = 0;
++ G_UNLOCK (fen_lock);
++ //FD_W ("OUT ========> %s\n", __func__);
++ return FALSE;
+}
+
-+static void
-+fn_hash_foreach_report_fnode (gpointer key,
-+ gpointer value,
-+ gpointer user_data)
++/**
++ * fdata_add_event:
++ *
++ */
++void
++fdata_add_event (fdata *f, fnode_event_t *ev)
+{
-+ fnode_t *fn = (fnode_t *) value;
-+ GList *idx;
-+
-+ g_assert (key == FN_PATH (fn));
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : fnode 0x%p\n", fn);
++ node_op_t op = {NULL, NULL, pre_del_cb, NULL};
++ fnode_event_t *tail;
+
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : {\n");
-+ MUTEX_LOCK (fn->fn_lock);
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-14s = %s\n", "path", (gchar*)key);
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-14s = %d\n", "ref", fn->ref);
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA %-14s = 0x%p\n", "parent", fn->parent);
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-14s = 0x%p\n", "subs", fn->subs);
-+
-+ for (idx = fn->subs; idx; idx = idx->next) {
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : {\n");
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-4s = 0x%p\n", "p", idx->data);
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-4s = %s\n", "s", gam_subscription_get_path(idx->data));
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : }\n");
++ if (!FN_IS_LIVING(f)) {
++ fnode_event_delete (ev);
++ return;
++ }
++
++ //FD_W ("%s %d\n", __func__, e);
++ g_get_current_time (&ev->t);
++ /*
++ * If created/deleted events of child node happened, then we use parent
++ * event queue to handle.
++ * If child node emits deleted event, it seems no changes for the parent
++ * node, but the attr is changed. So we may try to cancel processing the
++ * coming changed events of the parent node.
++ */
++ tail = (fnode_event_t*)g_queue_peek_tail (f->eventq);
++ switch (ev->e) {
++ case FILE_RENAME_FROM:
++ case FILE_RENAME_TO:
++ case FILE_ACCESS:
++ fnode_event_delete (ev);
++ g_assert_not_reached ();
++ return;
++ case FILE_DELETE:
++ /* clear changed event number */
++ f->changed_event_num = 0;
++ /*
++ * We will cancel all previous events.
++ */
++ if (tail) {
++ g_queue_pop_tail (f->eventq);
++ do {
++ fnode_event_delete (tail);
++ } while ((tail = (fnode_event_t*)g_queue_pop_tail (f->eventq)) != NULL);
++ }
++ /*
++ * Given a node "f" is deleted, process it ASAP.
++ */
++ fdata_emit_events (f, ev->e);
++ fnode_event_delete (ev);
++ fdata_adjust_deleted (f);
++ return;
++ case FILE_MODIFIED:
++ case UNMOUNTED:
++ case MOUNTEDOVER:
++ /* clear changed event number */
++ f->changed_event_num ++;
++ case FILE_ATTRIB:
++ default:
++ /*
++ * If in the time range, we will try optimizing
++ * (changed+) to (changed)
++ * (attrchanged changed) to ([changed, attrchanged])
++ * (event attrchanged) to ([event, attrchanged])
++ */
++ if (tail) {
++ do {
++ if (tail->e == ev->e) {
++ if (g_timeval_lt (&ev->t, &tail->t)) {
++ g_queue_peek_tail (f->eventq);
++ /* Add the increment */
++ g_time_val_add (&ev->t, PAIR_EVENTS_INC_TIMEVAL);
++ /* skip the previous event */
++ FD_W ("SKIPPED -- %s\n", _event_string (tail->e));
++ fnode_event_delete (tail);
++ } else {
++ break;
++ }
++ } else if (tail->e == FILE_ATTRIB && ev->e == FILE_MODIFIED) {
++ ev->has_twin = TRUE;
++ fnode_event_delete (tail);
++ } else if (ev->e == FILE_ATTRIB) {
++ tail->has_twin = TRUE;
++ /* skip the current event */
++ fnode_event_delete (ev);
++ return;
++ } else {
++ break;
++ }
++ } while ((tail = (fnode_event_t*)g_queue_peek_tail (f->eventq)) != NULL);
++ }
+ }
+
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA %-14s = 0x%p\n", "pnode", fn->pn);
-+ MUTEX_UNLOCK (fn->fn_lock);
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : }\n");
++ /* must add the threshold time */
++ g_time_val_add (&ev->t, PAIR_EVENTS_TIMEVAL);
++
++ g_queue_push_tail (f->eventq, ev);
++
++ /* starting process_events */
++ if (f->eventq_id == 0) {
++ f->eventq_id = g_timeout_add (PROCESS_EVENTQ_TIME,
++ process_events,
++ (gpointer)f);
++ g_assert (f->eventq_id > 0);
++ }
++ //FD_W ("%s 0x%p id:%-4d %s\n", __func__, f, f->eventq_id, FN_NAME(f));
+}
+
-+void
-+fn_hash_safe_debug ()
++gboolean
++fdata_class_init (void (*user_emit_cb) (fdata*, int),
++ void (*user_emit_once_cb) (fdata*, int, gpointer),
++ int (*user_event_converter) (int event))
+{
-+ while (1) {
-+ guint size;
-+ GList *keys, *idx;
-+
-+ if (fn_hash_trylock () == 0) {
-+ size = g_hash_table_size (fnode_hash);
-+#ifdef FEN_DEBUG_ALL
-+ if (size > 0) {
-+ g_hash_table_foreach (fnode_hash,
-+ fn_hash_foreach_report_fnode,
-+ NULL);
-+ }
-+#else
-+ if (size > 0 && size <= 2) {
-+ g_hash_table_foreach (fnode_hash,
-+ fn_hash_foreach_report_fnode,
-+ NULL);
-+ }
-+#endif
-+ MUTEX_UNLOCK (fnode_hash_mutex);
-+ }
-+#ifdef FEN_DEBUG_ALL
-+ if (size > 0) {
-+ GAM_DEBUG(DEBUG_INFO, "FENDATA : HASH HAS [ %6d ] NODES\n", size);
-+ }
-+#endif
-+ sleep (10);
++ g_debug ("%s\n", __func__);
++ if (user_emit_cb == NULL) {
++ return FALSE;
++ }
++ if (user_emit_once_cb == NULL) {
++ return FALSE;
++ }
++ if (user_event_converter == NULL) {
++ return FALSE;
+ }
++ emit_cb = user_emit_cb;
++ emit_once_cb = user_emit_once_cb;
++ _event_converter = user_event_converter;
++
++ if (!port_class_init (fdata_add_event)) {
++ g_debug ("port_class_init failed.");
++ return FALSE;
++ }
++ return TRUE;
+}
Index: gamin/server/fen-data.h
===================================================================
--- gamin/server/fen-data.h (revision 0)
+++ gamin/server/fen-data.h (revision 0)
-@@ -0,0 +1,73 @@
+@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 shiftwidth=4: */
+
-+#include <port.h>
-+#include <glib.h>
++#include <fcntl.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include "fen-node.h"
++#include "fen-kernel.h"
+
+#ifndef _FEN_DATA_H_
+#define _FEN_DATA_H_
+
-+#define FN_PATH(fp) (((fnode_t *)(fp))->fobj.fo_name)
-+
-+typedef struct fnode fnode_t;
-+typedef void (*port_emit_events_cb) (fnode_t *f, int events);
++#define FN_EVENT_CREATED 0
++#define FN_NAME(fp) (((fdata*)(fp))->fobj.fo_name)
++#define FN_NODE(fp) (((fdata*)(fp))->node)
++#define FN_IS_DIR(fp) (((fdata*)(fp))->is_dir)
++#define FN_IS_PASSIVE(fp) (((fdata*)(fp))->subs == NULL)
++#define FN_IS_MONDIR(fp) (((fdata*)(fp))->mon_dir_num > 0)
++#define FN_IS_LIVING(fp) (!((fdata*)(fp))->is_cancelled)
+
-+struct fnode
++typedef struct
+{
-+ /* must be the first member */
+ file_obj_t fobj;
-+
++ off_t len;
++ gboolean is_cancelled;
++
++ node_t* node;
+ /* to identify if the path is dir */
+ gboolean is_dir;
-+
-+ /* the number of mondir subs this fnode has */
-+ int md_ref;
-+
-+ /* protect above all */
-+ pthread_mutex_t prop_lock;
++ guint mon_dir_num;
+
-+ /* state */
-+ pthread_mutex_t ref_lock;
-+ int ref;
-+
-+ /* lock/unlock by fn_hash, protect the following members */
-+ pthread_mutex_t fn_lock;
-+
-+ /* List of subscriptions monitoring this fnode/path */
++ /* List of subscriptions monitoring this fdata/path */
+ GList *subs;
+
-+ /* the parent fnode monitoring the directory of this fnode/path */
-+ fnode_t *parent;
-+
-+/* private data */
-+
-+ /* call backs */
-+ port_emit_events_cb emit_events;
-+
-+ /* fen-kernel private */
-+ void *pn;
-+ gboolean is_poll;
-+};
++ /* prcessed changed events num */
++ guint changed_event_num;
++
++ /* process events source id */
++ GQueue* eventq;
++ guint eventq_id;
++ guint change_update_id;
++} fdata;
+
-+/* fnode functions */
-+fnode_t *fnode_new (const gchar *path, port_emit_events_cb emit_events);
-+void fnode_delete (fnode_t *fn);
-+void fnode_set_stat (fnode_t *f, struct stat *sbuf);
-+gboolean fnode_is_dir (fnode_t *f);
-+void fnode_emit_events (fnode_t *f, int events);
++/* fdata functions */
++fdata* fdata_new (node_t* node, gboolean is_mondir);
++void fdata_reset (fdata* data);
++void fdata_emit_events_once (fdata *f, int event, gpointer sub);
++void fdata_emit_events (fdata *f, int event);
++void fdata_add_event (fdata *f, fnode_event_t *ev);
++void fdata_adjust_deleted (fdata *f);
++fdata* get_parent_data (fdata* data);
++node_t* get_parent_node (fdata* data);
+
-+/* path <-> fnode_t hash */
-+gboolean fn_hash_init ();
-+fnode_t *fn_hash_safe_node_new (const gchar *file_name,
-+ port_emit_events_cb emit_events);
-+fnode_t *fn_hash_safe_node_ref (fnode_t *f);
-+fnode_t *fn_hash_safe_node_unref (fnode_t *f);
-+fnode_t *fn_hash_safe_lookup (const gchar *file_name);
-+gint fn_hash_safe_node_remove (fnode_t *f);
-+void fn_hash_safe_foreach (GHFunc func, gpointer user_data);
-+void fn_hash_safe_foreach_remove (GHRFunc func, gpointer user_data);
-+void fn_hash_safe_debug ();
++/* sub */
++void fdata_sub_add (fdata *f, gpointer sub);
++void fdata_sub_remove (fdata *f, gpointer sub);
+
-+/* fnode_t */
++/* misc */
++node_t* add_missing_cb (node_t* parent, gpointer user_data);
++gboolean pre_del_cb (node_t* node, gpointer user_data);
++
++/* init */
++gboolean fdata_class_init (void (*user_emit_cb) (fdata*, int),
++ void (*user_emit_once_cb) (fdata*, int, gpointer),
++ int (*user_event_converter) (int event));
+
+#endif /* _FEN_DATA_H_ */
+Index: gamin/server/fen-sub.c
+===================================================================
+--- gamin/server/fen-sub.c (revision 0)
++++ gamin/server/fen-sub.c (revision 0)
+@@ -0,0 +1,22 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#include "config.h"
++#include "fen-sub.h"
++
++fen_sub*
++fen_sub_new (gpointer udata, gboolean is_mondir)
++{
++ fen_sub *sub;
++ //g_printf ("%s [ 0x%p ]\n", __func__, udata);
++ sub = g_new (fen_sub, 1);
++ sub->user_data = udata;
++ sub->is_mondir = is_mondir;
++ return sub;
++}
++
++void
++fen_sub_delete (fen_sub *sub)
++{
++ g_free (sub);
++}
+Index: gamin/server/fen-sub.h
+===================================================================
+--- gamin/server/fen-sub.h (revision 0)
++++ gamin/server/fen-sub.h (revision 0)
+@@ -0,0 +1,18 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#include <glib.h>
++
++#ifndef _FEN_SUB_H_
++#define _FEN_SUB_H_
++
++typedef struct _fen_sub
++{
++ gpointer user_data;
++ gboolean is_mondir;
++} fen_sub;
++
++fen_sub* fen_sub_new (gpointer udata, gboolean is_mondir);
++void fen_sub_delete (fen_sub *sub);
++
++#endif _FEN_SUB_H_
Index: gamin/server/fen-kernel.c
===================================================================
--- gamin/server/fen-kernel.c (revision 0)
+++ gamin/server/fen-kernel.c (revision 0)
-@@ -0,0 +1,364 @@
+@@ -0,0 +1,504 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 shiftwidth=4: */
+
-+#include <pthread.h>
++#include "config.h"
+#include <rctl.h>
+#include <strings.h>
-+#include <sys/types.h>
-+#include <sys/stat.h>
-+#include <sys/time.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <glib.h>
+#include "fen-kernel.h"
-+#include "gam_error.h"
++#include "fen-dump.h"
++
++#define FK_W if (fk_debug_enabled) g_warning
++//static gboolean fk_debug_enabled = TRUE;
++static gboolean fk_debug_enabled = FALSE;
+
-+#define PE_ALLOC 128
-+#define PNP_FD(pp) (((pnode_t *)(pp))->port)
-+#define MON_EVENTS (/* FILE_ACCESS | */ FILE_MODIFIED | FILE_ATTRIB | FILE_NOFOLLOW)
++G_GNUC_INTERNAL G_LOCK_DEFINE (fen_lock);
++#define PE_ALLOC 64
++#define F_PORT(pp) (((_f *)(fo))->port->port)
++#define F_NAME(pp) (((_f *)(fo))->fobj->fo_name)
++#define FEN_ALL_EVENTS (FILE_MODIFIED | FILE_ATTRIB | FILE_NOFOLLOW)
++#define FEN_IGNORE_EVENTS (FILE_ACCESS)
++#define PROCESS_PORT_EVENTS_TIME 400 /* in milliseconds */
+
-+static ulong max_port_evnets = 512;
-+static ulong max_port_events = 256;
++static GHashTable *_obj_fen_hash = NULL; /* <user_data, port> */
++static ulong max_port_events = 512;
+static GList *pn_vq; /* the queue of ports which don't have the max objs */
+static GList *pn_fq; /* the queue of ports which have the max objs */
-+static pthread_mutex_t pn_queues_lock = PTHREAD_MUTEX_INITIALIZER;
++static GQueue *g_eventq = NULL;
++static void (*add_event_cb) (gpointer, fnode_event_t*);
++
++typedef struct pnode
++{
++ long ref; /* how many fds are associated to this port */
++ int port;
++ guint port_source_id;
++} pnode_t;
+
-+#define MUTEX_LOCK(lock) \
-+do { \
-+ if (pthread_mutex_lock(&lock) != 0) { \
-+ abort (); \
-+ } \
-+} while (0)
-+#define MUTEX_UNLOCK(lock) \
-+do { \
-+ if (pthread_mutex_unlock(&lock) != 0) { \
-+ abort (); \
-+ } \
-+} while (0)
++typedef struct {
++ pnode_t* port;
++ file_obj_t* fobj;
+
-+static void pnode_list_walker_cb (gpointer data, gpointer udata);
-+static void *port_fetch_event_cb (void *arg);
++ gboolean is_active;
++ gpointer user_data;
++} _f;
++
++static gboolean port_fetch_event_cb (void *arg);
+static pnode_t *pnode_new ();
+static void pnode_delete (pnode_t *pn);
+
-+struct pnode
++gboolean
++is_ported (gpointer f)
+{
-+ ulong ref; /* how many fds are associated to this port */
-+ pthread_mutex_t m_ref; /* protect ref */
-+ pthread_t tid;
-+ int port;
-+};
++ _f* fo = g_hash_table_lookup (_obj_fen_hash, f);
++
++ if (fo) {
++ return fo->is_active;
++ }
++ return FALSE;
++}
+
-+enum {
-+ CTL_CLOSE_PORT = 0,
-+ CTL_JOIN_THREAD,
-+ CTL_FREE_PNODE
-+};
-+
-+void
-+printevent(int event, char *pname)
++static void
++printevent (const char *pname, int event, const char *tag)
+{
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL - %s :",pname);
-+ if (event & FILE_ACCESS) {
-+ GAM_DEBUG(DEBUG_INFO, " FILE_ACCESS");
-+ }
-+ if (event & FILE_MODIFIED) {
-+ GAM_DEBUG(DEBUG_INFO, " FILE_MODIFIED");
-+ }
-+ if (event & FILE_ATTRIB) {
-+ GAM_DEBUG(DEBUG_INFO, " FILE_ATTRIB");
-+ }
-+ if (event & FILE_DELETE) {
-+ GAM_DEBUG(DEBUG_INFO, " FILE_DELETE");
-+ }
-+ if (event & FILE_RENAME_TO) {
-+ GAM_DEBUG(DEBUG_INFO, " FILE_RENAME_TO");
-+ }
-+ if (event & FILE_RENAME_FROM) {
-+ GAM_DEBUG(DEBUG_INFO, " FILE_RENAME_FROM");
-+ }
-+ if (event & UNMOUNTED) {
-+ GAM_DEBUG(DEBUG_INFO, " UNMOUNTED");
-+ }
-+ if (event & MOUNTEDOVER) {
-+ GAM_DEBUG(DEBUG_INFO, " MOUNTEDOVER");
-+ }
-+ GAM_DEBUG(DEBUG_INFO, "\n");
++ GString* str;
++ str = g_string_new ("");
++
++ g_string_printf (str, "[%s] [%-20s]", tag, pname);
++ if (event & FILE_ACCESS) {
++ str = g_string_append (str, " ACCESS");
++ }
++ if (event & FILE_MODIFIED) {
++ str = g_string_append (str, " MODIFIED");
++ }
++ if (event & FILE_ATTRIB) {
++ str = g_string_append (str, " ATTRIB");
++ }
++ if (event & FILE_DELETE) {
++ str = g_string_append (str, " DELETE");
++ }
++ if (event & FILE_RENAME_TO) {
++ str = g_string_append (str, " RENAME_TO");
++ }
++ if (event & FILE_RENAME_FROM) {
++ str = g_string_append (str, " RENAME_FROM");
++ }
++ if (event & UNMOUNTED) {
++ str = g_string_append (str, " UNMOUNTED");
++ }
++ if (event & MOUNTEDOVER) {
++ str = g_string_append (str, " MOUNTEDOVER");
++ }
++
++ FK_W ("%s\n", str->str);
++ g_string_free (str, TRUE);
+}
+
-+static void *
++static void
++port_add_kevent (int e, gpointer f)
++{
++ fnode_event_t *ev, *tail;
++ GTimeVal t;
++ gboolean has_twin = FALSE;
++
++ //printevent (F_NAME(f), e, "org");
++ /*
++ * Child FILE_DELETE | FILE_RENAME_FROM will trigger parent FILE_MODIFIED.
++ * FILE_MODIFIED will trigger FILE_ATTRIB.
++ */
++
++ if ((e & FILE_ATTRIB) && e != FILE_ATTRIB) {
++ e ^= FILE_ATTRIB;
++ has_twin = TRUE;
++ }
++ if (e == FILE_RENAME_FROM) {
++ e = FILE_DELETE;
++ }
++ if (e == FILE_RENAME_TO) {
++ e = FILE_MODIFIED;
++ }
++
++ switch (e) {
++ case FILE_DELETE:
++ case FILE_RENAME_FROM:
++ case FILE_MODIFIED:
++ case FILE_ATTRIB:
++ case UNMOUNTED:
++ case MOUNTEDOVER:
++ break;
++ case FILE_RENAME_TO:
++ case FILE_ACCESS:
++ default:
++ g_assert_not_reached ();
++ return;
++ }
++
++ tail = (fnode_event_t*) g_queue_peek_tail (g_eventq);
++ if (tail) {
++ if (tail->user_data == f) {
++ if (tail->e == e) {
++ tail->has_twin = (has_twin | (tail->has_twin ^ has_twin));
++ /* skip the current */
++ return;
++ } else if (e == FILE_MODIFIED && !has_twin
++ && tail->e == FILE_ATTRIB) {
++ tail->e = FILE_MODIFIED;
++ tail->has_twin = TRUE;
++ return;
++ } else if (e == FILE_ATTRIB
++ && tail->e == FILE_MODIFIED && !tail->has_twin) {
++ tail->has_twin = TRUE;
++ return;
++ }
++ }
++ }
++
++ if ((ev = fnode_event_new (e, has_twin, f)) != NULL) {
++ g_queue_push_tail (g_eventq, ev);
++ }
++}
++
++static void
++port_process_kevents ()
++{
++ fnode_event_t *ev;
++
++ while ((ev = (fnode_event_t*)g_queue_pop_head (g_eventq)) != NULL) {
++ FK_W ("[%s] 0x%p %s\n", __func__, ev, _event_string (ev->e));
++ add_event_cb (ev->user_data, ev);
++ }
++}
++
++static gboolean
+port_fetch_event_cb (void *arg)
+{
+ pnode_t *pn = (pnode_t *)arg;
-+ uint_t nget;
++ _f* fo;
++ uint_t nget = 0;
+ port_event_t pe[PE_ALLOC];
-+ int ret;
-+
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : [THREAD] started [pn] 0x%p [tid] %d\n", pn, pn->tid);
++ timespec_t timeout;
++ gpointer f;
++ gboolean ret = TRUE;
++
++ //FK_W ("IN <======== %s\n", __func__);
++ G_LOCK (fen_lock);
++
++ memset (&timeout, 0, sizeof (timespec_t));
++ do {
++ nget = 1;
++ if (port_getn (pn->port, pe, PE_ALLOC, &nget, &timeout) == 0) {
++ int i;
++ for (i = 0; i < nget; i++) {
++ fo = (_f*)pe[i].portev_user;
++ /* handle event */
++ switch (pe[i].portev_source) {
++ case PORT_SOURCE_FILE:
++ /* If got FILE_EXCEPTION or add to port failed,
++ delete the pnode */
++ fo->is_active = FALSE;
++ if (fo->user_data) {
++ printevent (F_NAME(fo), pe[i].portev_events, "RAW");
++ port_add_kevent (pe[i].portev_events, fo->user_data);
++ } else {
++ /* fnode is deleted */
++ goto L_delete;
++ }
++ if (pe[i].portev_events & FILE_EXCEPTION) {
++ g_hash_table_remove (_obj_fen_hash, fo->user_data);
++ L_delete:
++ //FK_W ("[ FREE_FO ] [0x%p]\n", fo);
++ pnode_delete (fo->port);
++ g_free (fo);
++ }
++ break;
++ default:
++ /* case PORT_SOURCE_TIMER: */
++ FK_W ("[kernel] unknown portev_source %d\n", pe[i].portev_source);
++ }
++ }
++ } else {
++ FK_W ("[kernel] port_getn %s\n", g_strerror (errno));
++ nget = 0;
++ }
++ } while (nget == PE_ALLOC);
+
-+ while (1) {
-+#if 0
-+ /* first get the number of available events */
-+ if (port_getn (PNP_FD(pn), pe, 0, &nget, NULL) == 0) {
-+ if (nget > PE_ALLOC)
-+ nget = PE_ALLOC;
-+ } else {
-+ break;
-+ }
-+#endif
-+ nget = 1;
-+ if (port_getn (PNP_FD(pn), pe, PE_ALLOC, &nget, NULL) == 0) {
-+ int i;
-+ for (i = 0; i < nget; i++) {
-+ fnode_t *f;
-+ f = (fnode_t *)pe[i].portev_user;
-+ /* handle event */
-+ printevent(pe[i].portev_events, FN_PATH(f));
-+ MUTEX_LOCK (f->ref_lock);
-+ g_assert (f->ref > 0);
-+ pnode_delete (f->pn);
-+ f->pn = NULL;
-+ f->ref --;
-+ MUTEX_UNLOCK (f->ref_lock);
-+ fnode_emit_events (f, pe[i].portev_events);
-+ }
-+ } else {
-+ break;
-+ }
-+ }
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : [THREAD] stopped [pn] 0x%p [tid] %d\n", pn, pn->tid);
-+ return (void *)0;
++ /* Processing g_eventq */
++ port_process_kevents ();
++
++ if (pn->ref == 0) {
++ pn->port_source_id = 0;
++ ret = FALSE;
++ }
++ G_UNLOCK (fen_lock);
++ //FK_W ("OUT ========> %s\n", __func__);
++ return ret;
+}
+
+/*
@@ -1426,21 +2754,17 @@
+static void
+pnode_delete (pnode_t *pn)
+{
-+ MUTEX_LOCK (pn->m_ref);
-+ MUTEX_LOCK (pn_queues_lock);
-+ if (pn->ref == max_port_evnets) {
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : move to visible queue - [pn] 0x%p [ref] %d\n", pn, pn->ref);
++ g_assert (pn->ref <= max_port_events);
++
++ if (pn->ref == max_port_events) {
++ //FK_W ("PORT : move to visible queue - [pn] 0x%p [ref] %d\n", pn, pn->ref);
+ pn_fq = g_list_remove (pn_fq, pn);
+ pn_vq = g_list_prepend (pn_vq, pn);
+ }
+ if ((-- pn->ref) == 0) {
-+ // corrently we can't stop a thread except we close the port fd
-+ // pthread_join (pn->tid, NULL);
-+ // pn->tid = 0;
++ /* Should dispatch the source */
+ }
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : pnode_delete: [pn] 0x%p [ref] %d\n", pn, pn->ref);
-+ MUTEX_UNLOCK (pn_queues_lock);
-+ MUTEX_UNLOCK (pn->m_ref);
++ //FK_W ("%s [pn] 0x%p [ref] %d\n", __func__, pn, pn->ref);
+}
+
+/*
@@ -1455,174 +2779,179 @@
+{
+ pnode_t *pn = NULL;
+
-+ MUTEX_LOCK (pn_queues_lock);
+ if (pn_vq) {
+ pn = (pnode_t*)pn_vq->data;
-+ g_assert (pn->ref < max_port_evnets);
++ g_assert (pn->ref < max_port_events);
+ } else {
-+ pn = malloc (sizeof (pnode_t));
++ pn = g_new0 (pnode_t, 1);
+ if (pn != NULL) {
-+ bzero ((void *)pn, sizeof (pnode_t));
-+ if (pthread_mutex_init (&pn->m_ref, NULL) != 0) {
-+ perror ("pthread_mutex_init");
-+ free (pn);
-+ pn = NULL;
-+ } else if ((PNP_FD(pn) = port_create ()) == -1) {
-+ perror ("port_create");
-+ free (pn);
-+ pn = NULL;
-+ } else if (pthread_create (&pn->tid, NULL,
-+ port_fetch_event_cb,
-+ (void *)pn) != 0) {
-+ perror ("pthread_create");
-+ free (pn);
-+ pn = NULL;
-+ } else {
++ if ((pn->port = port_create ()) >= 0) {
+ g_assert (g_list_find (pn_vq, pn) == NULL);
+ pn_vq = g_list_prepend (pn_vq, pn);
++ } else {
++ FK_W ("PORT_CREATE %s\n", g_strerror (errno));
++ g_free (pn);
++ pn = NULL;
+ }
+ }
+ }
+ if (pn) {
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : pnode_new: [pn] 0x%p [ref] %d\n", pn, pn->ref);
++ //FK_W ("%s [pn] 0x%p [ref] %d\n", __func__, pn, pn->ref);
+ pn->ref++;
-+ if (pn->ref == max_port_evnets) {
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : move to full queue - [pn] 0x%p [ref] %d\n", pn, pn->ref);
++ if (pn->ref == max_port_events) {
++ //FK_W ("PORT : move to full queue - [pn] 0x%p [ref] %d\n", pn, pn->ref);
+ pn_vq = g_list_remove (pn_vq, pn);
+ pn_fq = g_list_prepend (pn_fq, pn);
+ g_assert (g_list_find (pn_vq, pn) == NULL);
+ }
++ /* attach the source */
++ if (pn->port_source_id == 0) {
++ pn->port_source_id = g_timeout_add (PROCESS_PORT_EVENTS_TIME,
++ port_fetch_event_cb,
++ (void *)pn);
++ g_assert (pn->port_source_id > 0);
++ }
+ }
-+ MUTEX_UNLOCK (pn_queues_lock);
++
+ return pn;
+}
+
+/**
-+ * There is no guarantee that one fobj must be associated to one port fd.
-+ * And no guarantee that the same file will be added only once.
-+ * So it's up level duty to make sure only add a fobj one time.
++ * port_add_internal
+ *
-+ * Should call fnode_ref before call this function to protect the data.
-+ * And call fnode_unref if receive FILE_EXCEPTION events.
-+ *
-+ * Returns 0 if succeeded, -1 already added.
++ * < private >
++ * Unsafe, need lock fen_lock.
+ */
-+extern int
-+port_monitor_add (fnode_t *f, struct stat * st)
++static gboolean
++port_add_internal (file_obj_t* fobj, off_t* len,
++ gpointer f, gboolean need_stat)
+{
+ int ret;
+ struct stat buf;
++ _f* fo = NULL;
+
-+ MUTEX_LOCK (f->ref_lock);
-+ if (f->pn == NULL) {
-+ if (st == NULL) {
-+ st = &buf;
-+ if ((ret = lstat (FN_PATH(f), st)) != 0) {
-+ ret = errno;
-+ perror ("FENKERNEL - lstat");
-+ MUTEX_UNLOCK (f->ref_lock);
-+ return ret;
-+ }
-+ }
-+ fnode_set_stat (f, st);
++ g_assert (f && fobj);
++ FK_W ("%s [0x%p] %s\n", __func__, f, fobj->fo_name);
+
-+ if ((f->pn = pnode_new ()) != NULL) {
++ if ((fo = g_hash_table_lookup (_obj_fen_hash, f)) == NULL) {
++ fo = g_new0 (_f, 1);
++ fo->fobj = fobj;
++ fo->user_data = f;
++ g_assert (fo);
++ //FK_W ("[ NEW_FO ] [0x%p] %s\n", fo, F_NAME(fo));
++ g_hash_table_insert (_obj_fen_hash, f, fo);
++ }
++
++ if (fo->is_active) {
++ return TRUE;
++ }
+
-+ if ((ret = port_associate (PNP_FD(f->pn),
-+ PORT_SOURCE_FILE,
-+ (uintptr_t)&f->fobj,
-+ MON_EVENTS,
-+ (void *)f)) == 0) {
-+ f->ref ++;
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : associated file %s\n",
-+ FN_PATH(f));
-+ } else {
-+ perror ("FENKERNEL - port_associate");
-+ pnode_delete (f->pn);
-+ f->pn = NULL;
-+ ret = errno;
-+ }
-+ } else {
-+ ret = ENOMEM;
++ if (fo->port == NULL) {
++ fo->port = pnode_new ();
++ }
++
++ if (need_stat) {
++ if (FN_STAT (F_NAME(fo), &buf) != 0) {
++ FK_W ("LSTAT [%-20s] %s\n", F_NAME(fo), g_strerror (errno));
++ goto L_exit;
+ }
-+ } else {
-+ ret = -1;
++ g_assert (len);
++ fo->fobj->fo_atime = buf.st_atim;
++ fo->fobj->fo_mtime = buf.st_mtim;
++ fo->fobj->fo_ctime = buf.st_ctim;
++ *len = buf.st_size;
+ }
-+ MUTEX_UNLOCK (f->ref_lock);
-+ return ret;
++
++ if (port_associate (F_PORT(fo),
++ PORT_SOURCE_FILE,
++ (uintptr_t)fo->fobj,
++ FEN_ALL_EVENTS,
++ (void *)fo) == 0) {
++ fo->is_active = TRUE;
++ FK_W ("%s %s\n", "PORT_ASSOCIATE", F_NAME(fo));
++ return TRUE;
++ } else {
++ FK_W ("PORT_ASSOCIATE [%-20s] %s\n", F_NAME(fo), g_strerror (errno));
++ L_exit:
++ //FK_W ("[ FREE_FO ] [0x%p]\n", fo);
++ g_hash_table_remove (_obj_fen_hash, f);
++ pnode_delete (fo->port);
++ g_free (fo);
++ }
++ return FALSE;
++}
++
++gboolean
++port_add (file_obj_t* fobj, off_t* len, gpointer f)
++{
++ return port_add_internal (fobj, len, f, TRUE);
++}
++
++gboolean
++port_add_simple (file_obj_t* fobj, gpointer f)
++{
++ return port_add_internal (fobj, NULL, f, FALSE);
+}
+
+/**
-+ * Should call fnode_unref if call this function successfully
++ * port_remove
+ *
-+ * Return 0 if succeeded, -1 already removed.
++ * < private >
++ * Unsafe, need lock fen_lock.
+ */
-+extern int
-+port_monitor_remove (fnode_t *f)
++void
++port_remove (gpointer f)
+{
-+ int ret;
++ _f* fo = NULL;
+
-+ MUTEX_LOCK (f->ref_lock);
-+ if (f->pn) {
-+ if ((ret = port_dissociate (PNP_FD(f->pn), PORT_SOURCE_FILE,
-+ (uintptr_t)&f->fobj)) == 0) {
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : dissociated file %s\n",
-+ FN_PATH(f));
-+ pnode_delete (f->pn);
-+ f->pn = NULL;
-+ f->ref --;
++ FK_W ("%s\n", __func__);
++ if ((fo = g_hash_table_lookup (_obj_fen_hash, f)) != NULL) {
++ /* Marked */
++ fo->user_data = NULL;
++ g_hash_table_remove (_obj_fen_hash, f);
++
++ if (port_dissociate (F_PORT(fo),
++ PORT_SOURCE_FILE,
++ (uintptr_t)fo->fobj) == 0) {
++ /*
++ * Note, we can run foode_delete if dissociating is failed,
++ * because there may be some pending events (mostly like
++ * FILE_DELETE) in the port_get. If we delete the foode
++ * the fnode may be deleted, then port_get will run on an invalid
++ * address.
++ */
++ //FK_W ("[ FREE_FO ] [0x%p]\n", fo);
++ pnode_delete (fo->port);
++ g_free (fo);
+ } else {
-+ ret = errno;
-+ perror ("FENKERNEL - port_dissociate");
++ FK_W ("PORT_DISSOCIATE [%-20s] %s\n", F_NAME(fo), g_strerror (errno));
+ }
-+ } else {
-+ ret = -1;
+ }
-+ MUTEX_UNLOCK (f->ref_lock);
-+ return ret;
+}
+
-+static void
-+port_list_walker_cb (gpointer data, gpointer udata)
++const gchar *
++_event_string (int event)
+{
-+ pnode_t *pn = (pnode_t *)data;
-+ switch (GPOINTER_TO_INT (udata)) {
-+ case CTL_CLOSE_PORT:
-+ close (PNP_FD(pn));
-+ break;
-+ case CTL_JOIN_THREAD:
-+ pthread_join (pn->tid, NULL);
-+ break;
-+ case CTL_FREE_PNODE:
-+ {
-+ if (pthread_mutex_destroy (&pn->m_ref) != 0) {
-+ perror ("pthread_mutex_destroy");
-+ }
-+ /* free anyway */
-+ free (pn);
-+ break;
-+ }
-+ default:
-+ break;
-+ }
-+}
-+
-+static void
-+pnode_monitor_remove_all ()
-+{
-+ pnode_t *pn;
-+
-+ /* close ports */
-+ MUTEX_LOCK (pn_queues_lock);
-+ g_list_foreach (pn_vq, port_list_walker_cb, (gpointer)CTL_CLOSE_PORT);
-+ g_list_foreach (pn_fq, port_list_walker_cb, (gpointer)CTL_CLOSE_PORT);
-+ /* wait for threads */
-+ g_list_foreach (pn_vq, port_list_walker_cb, (gpointer)CTL_JOIN_THREAD);
-+ g_list_foreach (pn_fq, port_list_walker_cb, (gpointer)CTL_JOIN_THREAD);
-+ /* free pnodes */
-+ g_list_foreach (pn_vq, port_list_walker_cb, (gpointer)CTL_FREE_PNODE);
-+ g_list_foreach (pn_fq, port_list_walker_cb, (gpointer)CTL_FREE_PNODE);
-+ MUTEX_UNLOCK (pn_queues_lock);
++ switch (event) {
++ case FILE_DELETE:
++ return "FILE_DELETE";
++ case FILE_RENAME_FROM:
++ return "FILE_RENAME_FROM";
++ case FILE_MODIFIED:
++ return "FILE_MODIFIED";
++ case FILE_RENAME_TO:
++ return "FILE_RENAME_TO";
++ case MOUNTEDOVER:
++ return "MOUNTEDOVER";
++ case FILE_ATTRIB:
++ return "FILE_ATTRIB";
++ case UNMOUNTED:
++ return "UNMOUNTED";
++ case FILE_ACCESS:
++ return "FILE_ACCESS";
++ default:
++ return "EVENT_UNKNOWN";
++ }
+}
+
+/**
@@ -1630,25 +2959,59 @@
+ *
+ */
+
-+extern int
-+fen_kernel_init ()
++extern gboolean
++port_class_init (void (*user_add_event) (gpointer, fnode_event_t*))
+{
+ rctlblk_t *rblk;
-+
++ g_debug ("%s\n", __func__);
+ if ((rblk = malloc (rctlblk_size ())) == NULL) {
-+ perror ("rblk malloc");
-+ exit (1);
++ g_debug ("[kernel] rblk malloc %s\n", g_strerror (errno));
++ return FALSE;
+ }
+ if (getrctl ("process.max-port-events", NULL, rblk, RCTL_FIRST) == -1) {
-+ perror ("getrctl");
-+ exit (1);
++ g_debug ("[kernel] getrctl %s\n", g_strerror (errno));
++ free (rblk);
++ return FALSE;
+ } else {
-+ if (max_port_evnets > rctlblk_get_value(rblk))
-+ max_port_evnets = rctlblk_get_value(rblk);
-+ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : max event of a port: %u\n", max_port_evnets);
++ max_port_events = rctlblk_get_value(rblk);
++ g_debug ("[kernel] max_port_events = %u\n", max_port_events);
++ free (rblk);
+ }
-+ free (rblk);
-+ return 0;
++ if ((_obj_fen_hash = g_hash_table_new(g_direct_hash,
++ g_direct_equal)) == NULL) {
++ g_debug ("[kernel] fobj hash initializing faild\n");
++ return FALSE;
++ }
++ if ((g_eventq = g_queue_new ()) == NULL) {
++ g_debug ("[kernel] FEN global event queue initializing faild\n");
++ }
++ if (user_add_event == NULL) {
++ return FALSE;
++ }
++ add_event_cb = user_add_event;
++ return TRUE;
++}
++
++fnode_event_t*
++fnode_event_new (int event, gboolean has_twin, gpointer user_data)
++{
++ fnode_event_t *ev;
++
++ if ((ev = g_new (fnode_event_t, 1)) != NULL) {
++ g_assert (ev);
++ ev->e = event;
++ ev->user_data = user_data;
++ ev->has_twin = has_twin;
++ /* Default isn't a pending event. */
++ ev->is_pending = FALSE;
++ }
++ return ev;
++}
++
++void
++fnode_event_delete (fnode_event_t* ev)
++{
++ g_free (ev);
+}
Index: gamin/server/Makefile.am
===================================================================
@@ -1663,37 +3026,31 @@
endif
-@@ -69,6 +69,13 @@
+@@ -69,6 +69,24 @@
gam_server_SOURCES += gam_kqueue.c gam_kqueue.h
endif
+if ENABLE_FEN
+gam_server_SOURCES += gam_fen.c gam_fen.h \
-+ fen-kernel.c fen-kernel.h \
-+ fen-thread-pool.c fen-thread-pool.h \
-+ fen-data.c fen-data.h
++ fen-dump.c \
++ fen-dump.h \
++ fen-kernel.c \
++ fen-kernel.h \
++ fen-missing.c \
++ fen-missing.h \
++ fen-helper.c \
++ fen-helper.h \
++ fen-data.c \
++ fen-data.h \
++ fen-node.c \
++ fen-node.h \
++ fen-sub.c \
++ fen-sub.h
+endif
+
if ENABLE_HURD_MACH_NOTIFY
gam_server_SOURCES += gam_hurd_mach_notify.c gam_hurd_mach_notify.h
-@@ -79,6 +86,7 @@
- @MIG@ -s -server $(top_builddir)/server/fs_notify.c $(includedir)/hurd/fs_notify.defs
- endif
-
-+gam_server_CFLAGS =
- gam_server_LDFLAGS =
- gam_server_DEPENDENCIES = $(DEPS)
- gam_server_LDADD= $(top_builddir)/lib/libgamin_shared.a $(LDADDS) $(LIBGAMIN_LIBS)
-@@ -86,3 +94,8 @@
- if ENABLE_HURD_MACH_NOTIFY
- gam_server_LDADD += -lports -lthreads
- endif
-+
-+if ENABLE_FEN
-+gam_server_CFLAGS += $(GTHREAD_CFLAGS)
-+gam_server_LDADD += $(GTHREAD_LIBS)
-+endif
Index: gamin/server/gam_server.c
===================================================================
--- gamin/server/gam_server.c (revision 328)
@@ -1736,30 +3093,45 @@
===================================================================
--- gamin/server/fen-kernel.h (revision 0)
+++ gamin/server/fen-kernel.h (revision 0)
-@@ -0,0 +1,18 @@
+@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 shiftwidth=4: */
+
+#include <port.h>
-+#include <fen-data.h>
++#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef _FEN_KERNEL_H_
+#define _FEN_KERNEL_H_
+
-+typedef struct pnode pnode_t;
++#define FN_STAT lstat
++
++typedef struct fnode_event
++{
++ int e;
++ gboolean has_twin;
++ gboolean is_pending;
++ gpointer user_data;
++ GTimeVal t;
++} fnode_event_t;
+
-+extern int fen_kernel_init ();
-+extern int port_monitor_add (fnode_t *f, struct stat * st);
-+extern int port_monitor_remove (fnode_t *f);
-+extern void port_monitor_remove_all ();
++gboolean port_add (file_obj_t* fobj, off_t* len, gpointer f);
++gboolean port_add_simple (file_obj_t* fobj, gpointer f);
++void port_remove (gpointer f);
++gboolean is_ported (gpointer f);
++
++fnode_event_t* fnode_event_new (int event, gboolean has_twin, gpointer user_data);
++void fnode_event_delete (fnode_event_t* ev);
++const gchar * _event_string (int event);
++
++extern gboolean port_class_init ();
+
+#endif /* _FEN_KERNEL_H_ */
Index: gamin/server/gam_fen.c
===================================================================
--- gamin/server/gam_fen.c (revision 0)
+++ gamin/server/gam_fen.c (revision 0)
-@@ -0,0 +1,801 @@
+@@ -0,0 +1,119 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 shiftwidth=4: */
+/*
@@ -1777,604 +3149,13 @@
+ * is greater than process.max-port-events.
+ */
+#include "config.h"
-+#include <strings.h>
-+#include <fcntl.h>
-+#include <unistd.h>
-+#include <sys/param.h>
-+#include <sys/time.h>
-+#include <sys/types.h>
-+#include <stdlib.h>
-+#include <errno.h>
+#include "gam_error.h"
+#include "gam_fen.h"
+#include "gam_event.h"
+#include "gam_server.h"
+#include "gam_protocol.h"
-+#include "fen-kernel.h"
-+#include "fen-thread-pool.h"
-+#include "fen-data.h"
+#include <glib.h>
-+
-+//#define FEN_DEBUG
-+#define GC_INTERVAL 15000 /* once every 15 second */
-+#define POLL_INTERVAL 1500 /* poll once every 1.5 second */
-+#define MAX_EVENT_THREADS 25
-+#define MAX_EVENT_QUEUE_LEN G_MAXINT
-+
-+#define MSLEEP(ms) (usleep ((ms)*1000))
-+#define MUTEX_LOCK(lock) \
-+do { \
-+ if (pthread_mutex_lock(&lock) != 0) { \
-+ g_assert_not_reached (); \
-+ } \
-+} while (0)
-+
-+#define MUTEX_UNLOCK(lock) \
-+do { \
-+ if (pthread_mutex_unlock(&lock) != 0) { \
-+ g_assert_not_reached (); \
-+ } \
-+} while (0)
-+
-+typedef struct event_pair
-+{
-+ int events;
-+ fnode_t *f;
-+} event_pair_t;
-+
-+static void
-+ep_free (event_pair_t *ep)
-+{
-+ g_free (ep);
-+}
-+
-+static GList *delete_pending_list = NULL;
-+static thp_t *thread_pool = NULL;
-+
-+/* run by thread pool */
-+static void events_process_task (event_pair_t *ep);
-+static void poll_file_task (fnode_t *f);
-+static void events_unsafe_emit (fnode_t *f,
-+ GaminEventType event,
-+ const gchar *path);
-+static void find_new_file_in_dir (fnode_t *parent);
-+
-+/* others */
-+static GaminEventType fen_to_gamin_event (int events);
-+static void create_children_initially (const char *fullname,
-+ fnode_t *parent);
-+static gboolean fen_monitor_add (fnode_t *f);
-+static gboolean poll_monitor_add (fnode_t *f);
-+
-+static int
-+get_lstat (const char *path, struct stat *sbuf)
-+{
-+ int retval;
-+ if ((retval = lstat (path, sbuf)) != 0) {
-+ perror ("get_lstat");
-+ retval = errno;
-+ }
-+ return retval;
-+}
-+
-+static void
-+publish_gam_events (fnode_t *f, int events)
-+{
-+ event_pair_t *ep;
-+
-+ fnode_ref (f);
-+ ep = g_new (event_pair_t, 1);
-+ ep->f = f;
-+ ep->events = events;
-+
-+ if (thread_pool_run_full (thread_pool,
-+ (thp_run_cb) events_process_task,
-+ (gpointer) ep,
-+ (GDestroyNotify) ep_free) != 0) {
-+ GAM_DEBUG(DEBUG_INFO, "FEN : LOSING EVENTS ON %s\n", FN_PATH (f));
-+ fnode_unref (f);
-+ ep_free (ep);
-+ }
-+}
-+
-+static void
-+publish_fen_events (fnode_t *f, int events)
-+{
-+ publish_gam_events (f, fen_to_gamin_event (events));
-+}
-+
-+static gboolean
-+fn_hash_foreach_free_fnode (gpointer key,
-+ gpointer value,
-+ gpointer user_data)
-+{
-+ fnode_t *f = (fnode_t *) value;
-+ if (fnode_trylock (&f->fn_lock) == 0) {
-+ if (fnode_trylock (&f->ref_lock) != 0) {
-+ MUTEX_UNLOCK (f->fn_lock);
-+ return FALSE;
-+ }
-+ if (f->ref != 0 ) {
-+ MUTEX_UNLOCK (f->ref_lock);
-+ MUTEX_UNLOCK (f->fn_lock);
-+ return FALSE;
-+ }
-+ /* ref_lock and fn_lock are locked now */
-+ if (f->parent == NULL && f->subs == NULL) {
-+ gboolean ret;
-+ if (f->pn || f->is_poll) {
-+ MUTEX_UNLOCK (f->ref_lock);
-+ port_monitor_remove (f->pn);
-+ ret = FALSE;
-+ MUTEX_UNLOCK (f->fn_lock);
-+ } else {
-+ /* we can free this f now */
-+ delete_pending_list = g_list_prepend (delete_pending_list, f);
-+ ret = TRUE;
-+ MUTEX_UNLOCK (f->ref_lock);
-+ MUTEX_UNLOCK (f->fn_lock);
-+ }
-+ return ret;
-+ }
-+ MUTEX_UNLOCK (f->ref_lock);
-+ MUTEX_UNLOCK (f->fn_lock);
-+ }
-+ return FALSE;
-+}
-+
-+/* occupy a thread */
-+void
-+gc_thread ()
-+{
-+ while (1) {
-+ guint size;
-+ GList *keys, *idx;
-+
-+ if (fn_hash_trylock () == 0) {
-+ fn_hash_foreach_remove (fn_hash_foreach_free_fnode,
-+ NULL);
-+ fn_hash_unlock ();
-+ }
-+ if (delete_pending_list) {
-+ g_list_foreach (delete_pending_list,
-+ (GFunc) fnode_delete,
-+ NULL);
-+ g_list_free (delete_pending_list);
-+ delete_pending_list = NULL;
-+ }
-+ MSLEEP (GC_INTERVAL);
-+ }
-+}
-+
-+static GaminEventType
-+fen_to_gamin_event (int events)
-+{
-+ GaminEventType et = 0;
-+
-+ if (events & FILE_MODIFIED ||
-+ events & FILE_ATTRIB ||
-+ events & FILE_ACCESS ||
-+ events & FILE_RENAME_TO ||
-+ events & MOUNTEDOVER ||
-+ events & UNMOUNTED) {
-+ et |= GAMIN_EVENT_CHANGED;
-+ }
-+ if (events & FILE_DELETE ||
-+ events & FILE_RENAME_FROM) {
-+ et |= GAMIN_EVENT_DELETED;
-+ }
-+ return et;
-+}
-+
-+static void
-+events_unsafe_emit (fnode_t *f, GaminEventType events, const gchar *path)
-+{
-+ gboolean file_is_dir;
-+ if (path == NULL) {
-+ path = FN_PATH(f);
-+ file_is_dir = fnode_is_dir (f);
-+ } else {
-+ struct stat fsb;
-+ memset (&fsb, 0, sizeof (struct stat));
-+ lstat (path, &fsb);
-+ file_is_dir = S_ISDIR (fsb.st_mode) ? TRUE : FALSE;
-+ }
-+
-+ if (events & GAMIN_EVENT_CREATED) {
-+ gam_server_emit_event(path, file_is_dir,
-+ GAMIN_EVENT_CREATED, f->subs, 1);
-+ }
-+ if (events & GAMIN_EVENT_CHANGED) {
-+ GList *idx;
-+ for (idx = f->subs; idx; idx = idx->next) {
-+ GamSubscription *sub = (GamSubscription *)idx->data;
-+ if (!gam_subscription_is_dir (sub)) {
-+ gam_server_emit_one_event (path, file_is_dir, GAMIN_EVENT_CHANGED, sub, 1);
-+ }
-+ }
-+ if (f->parent) {
-+ MUTEX_LOCK (f->parent->fn_lock);
-+ gam_server_emit_event(path, file_is_dir,
-+ GAMIN_EVENT_CHANGED, f->parent->subs, 1);
-+ MUTEX_UNLOCK (f->parent->fn_lock);
-+ }
-+ }
-+ if (events & GAMIN_EVENT_DELETED) {
-+ gam_server_emit_event(path, file_is_dir,
-+ GAMIN_EVENT_DELETED, f->subs, 1);
-+ if (f->parent) {
-+ MUTEX_LOCK (f->parent->fn_lock);
-+ gam_server_emit_event(path, file_is_dir,
-+ GAMIN_EVENT_DELETED, f->parent->subs, 1);
-+ MUTEX_UNLOCK (f->parent->fn_lock);
-+ }
-+ }
-+ if (events & GAMIN_EVENT_MOVED) {
-+ gam_server_emit_event(path, file_is_dir,
-+ GAMIN_EVENT_MOVED, f->subs, 1);
-+ if (f->parent) {
-+ MUTEX_LOCK (f->parent->fn_lock);
-+ gam_server_emit_event(path, file_is_dir,
-+ GAMIN_EVENT_MOVED, f->parent->subs, 1);
-+ MUTEX_UNLOCK (f->parent->fn_lock);
-+ }
-+ }
-+}
-+
-+/**
-+ * When got events from PORT, process them here.
-+ * fnode of event_pair is refed before it's called, so should unref before
-+ * it returns.
-+ */
-+
-+static void
-+events_process_task (event_pair_t *ep)
-+{
-+ int ret;
-+ fnode_t *f = ep->f;
-+ GaminEventType events = ep->events;
-+
-+ g_assert (f);
-+ MUTEX_LOCK (f->ref_lock);
-+ g_assert (f->ref > 0);
-+ MUTEX_UNLOCK (f->ref_lock);
-+
-+ GAM_DEBUG(DEBUG_INFO, "FEN : events_process_task 0x%p %s\n", f, FN_PATH(f));
-+
-+ MUTEX_LOCK (f->fn_lock);
-+ events_unsafe_emit (f, events, NULL);
-+ if (f->parent == NULL && f->subs == NULL) {
-+ fnode_unref (f);
-+ MUTEX_UNLOCK (f->fn_lock);
-+ return;
-+ }
-+ MUTEX_UNLOCK (f->fn_lock);
-+
-+ if (events & GAMIN_EVENT_CHANGED || events & GAMIN_EVENT_CREATED) {
-+ switch (port_monitor_add (f, NULL)) {
-+ case 0:
-+ case -1:
-+ /* scan for new created files if somebody mondir */
-+ if (fnode_is_dir (f) && fnode_mdcount (f) > 0) {
-+ find_new_file_in_dir (f);
-+ }
-+ break;
-+ case ENOENT:
-+ publish_gam_events (f, GAMIN_EVENT_DELETED);
-+ break;
-+ default:
-+ if (!poll_monitor_add(f)) {
-+ g_assert_not_reached ();
-+ }
-+ }
-+ } else if (events & GAMIN_EVENT_DELETED || events == -1) {
-+ /* if the parent dir is mondir,
-+ unsub the parent's subs from mine. */
-+ MUTEX_LOCK (f->fn_lock);
-+ f->parent = NULL;
-+ if (f->subs != NULL) {
-+ /* turn to polling */
-+ if (!poll_monitor_add(f)) {
-+ g_assert_not_reached ();
-+ }
-+ }
-+ MUTEX_UNLOCK (f->fn_lock);
-+ }
-+ fnode_unref (f);
-+ GAM_DEBUG(DEBUG_INFO, "FEN : events_process_task 0x%p\n", f);
-+}
-+
-+/**
-+ * When a path can be monitored by PORT, we should thread to run stat(2) on
-+ * and detect if the file node is created. After that we finish this thread
-+ * and pass the node to PORT.
-+ */
-+
-+static void
-+poll_file_task (fnode_t *f)
-+{
-+ int ret;
-+ struct stat buf;
-+
-+ //GAM_DEBUG(DEBUG_INFO, "FEN : poll_file_task %s\n", FN_PATH(f));
-+
-+ MUTEX_LOCK (f->fn_lock);
-+ MUTEX_LOCK (f->ref_lock);
-+ if (f->parent == NULL && f->subs == NULL) {
-+ f->is_poll = FALSE;
-+ f->ref --;
-+ MUTEX_UNLOCK (f->ref_lock);
-+ MUTEX_UNLOCK (f->fn_lock);
-+ return;
-+ }
-+ MUTEX_UNLOCK (f->ref_lock);
-+ MUTEX_UNLOCK (f->fn_lock);
-+
-+ if ((ret = get_lstat (FN_PATH(f), &buf)) == 0) {
-+ switch (port_monitor_add (f, &buf)) {
-+ case 0:
-+ case -1:
-+ MUTEX_LOCK (f->ref_lock);
-+ f->is_poll = FALSE;
-+ MUTEX_UNLOCK (f->ref_lock);
-+ publish_gam_events (f, GAMIN_EVENT_CREATED);
-+ /* scan for new created files if somebody mondir */
-+ if (fnode_is_dir (f) && fnode_mdcount (f) > 0) {
-+ find_new_file_in_dir (f);
-+ }
-+ break;
-+ case ENOENT:
-+ publish_gam_events (f, GAMIN_EVENT_DELETED);
-+ default:
-+ goto l_poll;
-+ }
-+ } else {
-+ l_poll:
-+ MSLEEP (POLL_INTERVAL);
-+ MUTEX_LOCK (f->ref_lock);
-+ f->is_poll = FALSE;
-+ MUTEX_UNLOCK (f->ref_lock);
-+ if (!poll_monitor_add(f)) {
-+ g_assert_not_reached ();
-+ }
-+ }
-+ fnode_unref (f);
-+ return;
-+}
-+
-+static gboolean
-+poll_monitor_add (fnode_t *f)
-+{
-+ MUTEX_LOCK (f->ref_lock);
-+ if (!f->is_poll) {
-+ if (thread_pool_run (thread_pool,
-+ (thp_run_cb) poll_file_task, f) == 0) {
-+ //GAM_DEBUG(DEBUG_INFO, "FEN : [ polling ] %s\n", FN_PATH(f));
-+ f->ref ++;
-+ f->is_poll = TRUE;
-+ } else {
-+ MUTEX_UNLOCK (f->ref_lock);
-+ return FALSE;
-+ }
-+ }
-+ MUTEX_UNLOCK (f->ref_lock);
-+ return TRUE;
-+}
-+
-+/**
-+ * If the fnode_t is a directory and GAMIN_EVENT_CHANGED happenned on it,
-+ * Use it to detect what happenned in the first level of the directory.
-+ *
-+ * In thread
-+ */
-+
-+static void
-+find_new_file_in_dir (fnode_t *parent)
-+{
-+ GDir *dir;
-+ GError *err = NULL;
-+
-+ GAM_DEBUG(DEBUG_INFO, "FEN : find_new_file_in_dir 0x%p %s\n",
-+ parent, FN_PATH(parent));
-+ dir = g_dir_open (FN_PATH(parent), 0, &err);
-+ if (dir) {
-+ const char *filename;
-+
-+ while ((filename = g_dir_read_name (dir)))
-+ {
-+ fnode_t *tmpf;
-+ GList *idx;
-+ gchar *fullname = g_build_filename (FN_PATH(parent),
-+ filename,
-+ NULL);
-+ /* If can't found the fullname from the global hash,
-+ emit the event on all mondir subs.
-+ Else if foreach mondir subs, if can't find in fn subs of
-+ fullname, emit event on that sub.
-+ */
-+ //GAM_DEBUG(DEBUG_INFO, "FEN : found %s\n", fullname);
-+ if ((tmpf = fn_hash_safe_node_new (fullname,
-+ publish_fen_events)) == NULL) {
-+ GAM_DEBUG(DEBUG_INFO, "FEN : create fn failed: %s\n", fullname);
-+ goto l_continue;
-+ }
-+
-+ switch (port_monitor_add (tmpf, NULL)) {
-+ case 0:
-+ case -1:
-+ break;
-+ case ENOENT:
-+ default:
-+ goto l_skip;
-+ }
-+
-+ /* set parent iff the parent has subscriptions or is mondired */
-+ MUTEX_LOCK (tmpf->fn_lock);
-+ MUTEX_LOCK (parent->fn_lock);
-+ if ((parent->parent || parent->subs) && tmpf->parent == NULL) {
-+ tmpf->parent = parent;
-+ gam_server_emit_event (fullname, fnode_is_dir(tmpf), GAMIN_EVENT_CREATED, parent->subs, 1);
-+ }
-+ MUTEX_UNLOCK (parent->fn_lock);
-+ MUTEX_UNLOCK (tmpf->fn_lock);
-+
-+ l_skip:
-+ fnode_unref (tmpf);
-+ l_continue:
-+ g_free (fullname);
-+ }
-+ g_dir_close (dir);
-+ } else {
-+ GAM_DEBUG (DEBUG_INFO, "unable to open directory %s: %s\n", FN_PATH(parent), err->message);
-+ g_error_free (err);
-+ }
-+}
-+
-+static void
-+foreach_hash_remove_parent (gpointer key,
-+ gpointer value,
-+ gpointer user_data)
-+{
-+ /* hash is locked outside, note
-+ do not lock hash here or the invorked functions */
-+ fnode_t *parent = (fnode_t *) user_data;
-+ const gchar *path = FN_PATH(parent);
-+ gchar *dirname = g_path_get_dirname (key);
-+
-+ /* skip root "/" */
-+ if (parent != value &&
-+ strcmp (path, dirname) == 0) {
-+ fnode_t *f = (fnode_t *) value;
-+
-+ MUTEX_LOCK (f->fn_lock);
-+ f->parent = NULL;
-+ if (f->subs == NULL) {
-+ port_monitor_remove (f);
-+ }
-+ MUTEX_UNLOCK (f->fn_lock);
-+ }
-+ g_free (dirname);
-+}
-+
-+/*
-+ * We do it because we know the parent has some mondir subscriptions.
-+ * Parent is refed.
-+ * Fullname is known to be existing, but skip it if it's removed when
-+ * add it to port.
-+ */
-+
-+static void
-+create_children_initially (const char *fullname,
-+ fnode_t *parent)
-+
-+{
-+ fnode_t *f;
-+
-+ if ((f = fn_hash_safe_node_new (fullname, publish_fen_events)) == NULL) {
-+ GAM_DEBUG(DEBUG_INFO, "FEN : create_children_initially - failed to create fn\n");
-+ return;
-+ }
-+
-+ MUTEX_LOCK (f->fn_lock);
-+ MUTEX_LOCK (parent->fn_lock);
-+ switch (port_monitor_add (f, NULL)) {
-+ case 0:
-+ case -1:
-+ case ENOENT:
-+ break;
-+ default:
-+ /* skip any other unsopport error */
-+ goto l_skip;
-+ }
-+ /* append the mondir subs */
-+ f->parent = parent;
-+
-+ l_skip:
-+ fnode_unref (f);
-+ MUTEX_UNLOCK (parent->fn_lock);
-+ MUTEX_UNLOCK (f->fn_lock);
-+}
-+
-+/**
-+ * First time scan the subscription file or directory. Only invoked in
-+ * gam_fen_add_subscription. Here f is refed.
-+ * Returns TRUE, if pathname exists.
-+ */
-+
-+static gboolean
-+gam_fen_send_initial_events (const char *pathname,
-+ fnode_t *f,
-+ GamSubscription *sub)
-+{
-+ int retval;
-+ gboolean is_dir;
-+ GaminEventType gevent;
-+ int req = 1;
-+ struct stat buf;
-+
-+ GAM_DEBUG (DEBUG_INFO, "gam_fen_send_initial_events on %s\n", pathname);
-+ is_dir = gam_subscription_is_dir (sub);
-+
-+ switch (get_lstat (FN_PATH(f), &buf)) {
-+ case 0:
-+ fnode_set_stat (f, &buf);
-+ gevent = GAMIN_EVENT_EXISTS;
-+ retval = TRUE;
-+ break;
-+ case ENOENT:
-+ gevent = GAMIN_EVENT_DELETED;
-+ retval = FALSE;
-+ is_dir = FALSE;
-+ break;
-+ default:
-+ perror ("gam_fen_send_initial_events");
-+ }
-+
-+ if (req != 0 || gevent == GAMIN_EVENT_DELETED)
-+ gam_server_emit_one_event (pathname, is_dir, gevent, sub, 1);
-+
-+ if (req == 0)
-+ return retval;
-+
-+ /* NOTE: If the sub is mondiring, we need create the new fnode
-+ for each child files/dirs and mark the f->is_md_child. */
-+ if (is_dir && fnode_is_dir (f)) {
-+ GDir *dir;
-+ GError *err = NULL;
-+
-+ dir = g_dir_open (FN_PATH(f), 0, &err);
-+ if (dir) {
-+ const char *filename;
-+ while ((filename = g_dir_read_name (dir)))
-+ {
-+ gboolean file_is_dir = FALSE;
-+ gchar *fullname = g_build_filename (FN_PATH(f),
-+ filename,
-+ NULL);
-+ if (lstat (fullname, &buf) == 0) {
-+ file_is_dir = S_ISDIR (buf.st_mode) ? TRUE : FALSE;
-+ }
-+ MUTEX_LOCK (f->fn_lock);
-+ gam_server_emit_one_event (fullname, file_is_dir, GAMIN_EVENT_EXISTS, sub, 1);
-+ MUTEX_UNLOCK (f->fn_lock);
-+
-+ /* if I'm a mondir */
-+ if (fnode_mdcount (f) > 0) {
-+ create_children_initially (fullname, f);
-+ }
-+
-+ g_free (fullname);
-+ }
-+ g_dir_close (dir);
-+ } else {
-+ GAM_DEBUG (DEBUG_INFO, "unable to open directory %s: %s\n", FN_PATH(f), err->message);
-+ g_error_free (err);
-+ }
-+ }
-+
-+ gam_server_emit_one_event (pathname, is_dir, GAMIN_EVENT_ENDEXISTS, sub, 1);
-+ return retval;
-+}
++#include "fen-helper.h"
+
+/**
+ * Initializes the FEN system. This must be called before
@@ -2386,48 +3167,14 @@
+gboolean
+gam_fen_init (void)
+{
-+ GError *err = NULL;
-+
-+ if (fen_kernel_init () != 0) {
-+ GAM_DEBUG(DEBUG_INFO, "FEN : initializing failed - fen_kernel_init.\n");
-+ return FALSE;
-+ }
-+
-+ if (!g_thread_supported ()) g_thread_init (NULL);
-+
-+ if (!fn_hash_init ()) {
-+ GAM_DEBUG(DEBUG_INFO, "FEN : initializing failed - fn_hash_init.\n");
-+ return FALSE;
-+ }
-+
-+ thread_pool = thread_pool_new (MAX_EVENT_THREADS, MAX_EVENT_QUEUE_LEN);
-+ if (thread_pool == NULL) {
-+ GAM_DEBUG(DEBUG_INFO, "FEN : initializing failed - thread_pool_new.\n");
-+ return FALSE;
-+ }
-+
-+#ifdef GAMIN_DEBUG
-+#ifdef FEN_DEBUG
-+ if (thread_pool_run (thread_pool,
-+ (thp_run_cb) fn_hash_safe_debug,
-+ NULL) != 0) {
-+ g_assert_not_reached ();
-+ }
-+#endif
-+#endif
-+ if (thread_pool_run (thread_pool,
-+ (thp_run_cb) gc_thread,
-+ NULL) != 0) {
-+ g_assert_not_reached ();
-+ }
++ if (!fen_init ())
++ return FALSE;
+
-+ gam_server_install_kernel_hooks (GAMIN_K_FEN,
-+ gam_fen_add_subscription,
-+ gam_fen_remove_subscription,
-+ gam_fen_remove_all_for,
-+ NULL, NULL);
-+
-+ GAM_DEBUG(DEBUG_INFO, "FEN : initialized.\n");
++ gam_server_install_kernel_hooks (GAMIN_K_FEN,
++ gam_fen_add_subscription,
++ gam_fen_remove_subscription,
++ gam_fen_remove_all_for,
++ NULL, NULL);
+ return TRUE;
+}
+
@@ -2441,48 +3188,13 @@
+gboolean
+gam_fen_add_subscription (GamSubscription *sub)
+{
-+ int ret;
-+ fnode_t *f;
-+ gboolean is_existing;
-+ const gchar *filename;
-+
-+ GAM_DEBUG(DEBUG_INFO, "FEN : gam_fen_add_subscription\n");
-+ filename = gam_subscription_get_path (sub);
-+ gam_listener_add_subscription (gam_subscription_get_listener (sub), sub);
-+
-+ if ((f = fn_hash_safe_node_new (filename,
-+ publish_fen_events)) == NULL) {
-+ GAM_DEBUG(DEBUG_INFO, "%s: new sub failed\n", filename);
-+ return FALSE;
-+ }
-+
-+ if (gam_subscription_is_dir (sub)) {
-+ fnode_mdref (f);
-+ }
-+
-+ is_existing = gam_fen_send_initial_events (filename, f, sub);
++ g_debug ("[ %s ] sub[0x%p]\n", __func__, sub);
+
-+ MUTEX_LOCK (f->fn_lock);
-+ switch (port_monitor_add (f, NULL)) {
-+ case 0:
-+ case -1:
-+ ret = TRUE;
-+ break;
-+ case ENOENT:
-+ default:
-+ if (!poll_monitor_add (f)) {
-+ g_assert_not_reached ();
-+ }
-+ ret = TRUE;
-+ break;
-+ }
-+ g_assert (g_list_find (f->subs, sub) == NULL);
-+ f->subs = g_list_prepend (f->subs, sub);
-+
-+ fnode_unref (f);
-+ MUTEX_UNLOCK (f->fn_lock);
-+
-+ return ret;
++ gam_listener_add_subscription (gam_subscription_get_listener (sub), sub);
++ fen_add (gam_subscription_get_path(sub),
++ sub,
++ gam_subscription_is_dir (sub));
++ return TRUE;
+}
+
+/**
@@ -2495,35 +3207,13 @@
+gboolean
+gam_fen_remove_subscription (GamSubscription *sub)
+{
-+ fnode_t *f;
-+ GList *targ;
-+
-+ GAM_DEBUG(DEBUG_INFO, "FEN : gam_fen_remove_subscription\n");
-+
-+ if ((f = fn_hash_safe_lookup (gam_subscription_get_path(sub))) == NULL) {
-+ GAM_DEBUG(DEBUG_INFO, "FEN : can't find %s\n",
-+ FN_PATH(gam_subscription_get_path(sub)));
-+ return TRUE;
-+ }
-+
-+ MUTEX_LOCK (f->fn_lock);
++ g_debug ("[ %s ] sub[0x%p]\n", __func__, sub);
+
-+ /* If no one monitor this f, remove the sub from f->subs */
-+ /* the removal of the fn will be pending in thread_pool */
-+ f->subs = g_list_remove (f->subs, sub);
-+
-+ if (f->parent == NULL && f->subs == NULL) {
-+ /* remove the childen's parent if I'm the parent */
-+ fn_hash_safe_foreach (foreach_hash_remove_parent, f);
-+ port_monitor_remove (f);
-+ }
-+ MUTEX_UNLOCK (f->fn_lock);
-+
-+ if (gam_subscription_is_dir (sub)) {
-+ fnode_mdunref (f);
-+ }
-+
++ fen_remove (gam_subscription_get_path(sub),
++ sub,
++ gam_subscription_is_dir (sub));
+ /* free subscription */
++ gam_subscription_cancel(sub);
+ gam_subscription_free(sub);
+ return TRUE;
+}