| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* vim:set expandtab ts=4 shiftwidth=4: */ |
| /* |
| * Copyright (C) 2008 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General |
| * Public License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place, Suite 330, |
| * Boston, MA 02111-1307, USA. |
| * |
| * Authors: Lin Ma <lin.ma@sun.com> |
| */ |
| |
| #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; |
| }; |
| |
| #ifdef GIO_COMPILATION |
| #define FN_W if (fn_debug_enabled) g_warning |
| static gboolean fn_debug_enabled = FALSE; |
| #else |
| #include "gam_error.h" |
| #define FN_W(...) GAM_DEBUG(DEBUG_INFO, __VA_ARGS__) |
| #endif |
| |
| 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) |
| { |
| 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; |
| } |
| } |
| |
| /* |
| * delete recursively |
| */ |
| static gboolean |
| remove_children (node_t* node, node_op_t* op) |
| { |
| FN_W ("%s 0x%p %s\n", __func__, node, NODE_NAME(node)); |
| 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; |
| } |
| |
| 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) |
| { |
| 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); |
| } |
| |
| /* |
| * depth first delete recursively |
| */ |
| static gboolean |
| children_remove_cb (gpointer key, |
| gpointer value, |
| gpointer user_data) |
| { |
| node_t* f = (node_t*)value; |
| node_op_t* op = (node_op_t*) user_data; |
| |
| g_assert (f->parent); |
| |
| FN_W ("%s [p] %8s [c] %8s\n", __func__, f->parent->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; |
| } |
| |
| 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); |
| } |
| |
| static void |
| children_foreach (node_t *f, GHFunc func, gpointer user_data) |
| { |
| g_assert (f); |
| |
| g_hash_table_foreach (f->children, func, user_data); |
| } |
| |
| gboolean |
| _node_class_init () |
| { |
| FN_W ("%s\n", __func__); |
| if (_head == NULL) { |
| _head = node_new (NULL, G_DIR_SEPARATOR_S); |
| } |
| return _head != NULL; |
| } |