2008-03-10 Lin Ma <[email protected]> VERMILLION_86
authorlin
Mon, 10 Mar 2008 03:54:54 +0000
changeset 11824 744edb4ef657
parent 11823 713064302fbb
child 11825 2d5bb68c2c1a
2008-03-10 Lin Ma <[email protected]> * patches/gamin-01-all.diff: Re-work the patch for the performance tune.
ChangeLog
patches/gamin-01-all.diff
--- a/ChangeLog	Sun Mar 09 18:09:07 2008 +0000
+++ b/ChangeLog	Mon Mar 10 03:54:54 2008 +0000
@@ -1,3 +1,8 @@
+2008-03-10  Lin Ma  <[email protected]>
+
+	* patches/gamin-01-all.diff: Re-work the patch for the performance
+	tune.
+
 2008-03-09  Damien Carbery <[email protected]>
 
 	* SUNWgnome-games.spec: Fix perms for -devel package.
--- 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;
 +}