1/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/* vim:set expandtab ts=4 shiftwidth=4: */
3/*
4 * Copyright (C) 2008 Sun Microsystems, Inc. All rights reserved.
5 * Use is subject to license terms.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General
18 * Public License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20 * Boston, MA 02111-1307, USA.
21 *
22 * Authors: Lin Ma <lin.ma@sun.com>
23 */
24
25#include "config.h"
26#include <errno.h>
27#include <strings.h>
28#include <glib.h>
29#include "fen-node.h"
30#include "fen-dump.h"
31
32#define	NODE_STAT(n)	(((node_t*)(n))->stat)
33
34struct _dnode {
35    gchar* filename;
36    node_op_t* op;
37    GTimeVal tv;
38};
39
40#ifdef GIO_COMPILATION
41#define FN_W if (fn_debug_enabled) g_warning
42static gboolean fn_debug_enabled = FALSE;
43#else
44#include "gam_error.h"
45#define FN_W(...) GAM_DEBUG(DEBUG_INFO, __VA_ARGS__)
46#endif
47
48G_LOCK_EXTERN (fen_lock);
49#define	PROCESS_DELETING_INTERVAL	900 /* in second */
50
51static node_t* _head = NULL;
52static GList *deleting_nodes = NULL;
53static guint deleting_nodes_id = 0;
54
55static node_t* node_new (node_t* parent, const gchar* basename);
56static void node_delete (node_t* parent);
57static gboolean remove_node_internal (node_t* node, node_op_t* op);
58static void children_add (node_t *p, node_t *f);
59static void children_remove (node_t *p, node_t *f);
60static guint children_foreach_remove (node_t *f, GHRFunc func, gpointer user_data);
61static void children_foreach (node_t *f, GHFunc func, gpointer user_data);
62static gboolean children_remove_cb (gpointer key,
63  gpointer value,
64  gpointer user_data);
65
66static struct _dnode*
67_dnode_new (const gchar* filename, node_op_t* op)
68{
69    struct _dnode* d;
70
71    g_assert (op);
72    if ((d = g_new (struct _dnode, 1)) != NULL) {
73        d->filename = g_strdup (filename);
74        d->op = g_memdup (op, sizeof (node_op_t));
75        g_assert (d->op);
76        g_get_current_time (&d->tv);
77        g_time_val_add (&d->tv, PROCESS_DELETING_INTERVAL);
78    }
79    return d;
80}
81
82static void
83_dnode_free (struct _dnode* d)
84{
85    g_assert (d);
86    g_free (d->filename);
87    g_free (d->op);
88    g_free (d);
89}
90
91static gboolean
92g_timeval_lt (GTimeVal *val1, GTimeVal *val2)
93{
94    if (val1->tv_sec < val2->tv_sec)
95        return TRUE;
96
97    if (val1->tv_sec > val2->tv_sec)
98        return FALSE;
99
100    /* val1->tv_sec == val2->tv_sec */
101    if (val1->tv_usec < val2->tv_usec)
102        return TRUE;
103
104    return FALSE;
105}
106
107static gboolean
108scan_deleting_nodes (gpointer data)
109{
110    struct _dnode* d;
111    GTimeVal tv_now;
112    GList* i;
113    GList* deleted_list = NULL;
114    gboolean ret = TRUE;
115    node_t* node;
116
117    g_get_current_time (&tv_now);
118
119    if (G_TRYLOCK (fen_lock)) {
120        for (i = deleting_nodes; i; i = i->next) {
121            d = (struct _dnode*)i->data;
122            /* Time to free, try only once */
123            if (g_timeval_lt (&d->tv, &tv_now)) {
124                if ((node = find_node (d->filename)) != NULL) {
125                    remove_node_internal (node, d->op);
126                }
127                _dnode_free (d);
128                deleted_list = g_list_prepend (deleted_list, i);
129            }
130        }
131
132        for (i = deleted_list; i; i = i->next) {
133            deleting_nodes = g_list_remove_link (deleting_nodes,
134              (GList *)i->data);
135            g_list_free_1 ((GList *)i->data);
136        }
137        g_list_free (deleted_list);
138
139        if (deleting_nodes == NULL) {
140            deleting_nodes_id = 0;
141            ret = FALSE;
142        }
143        G_UNLOCK (fen_lock);
144    }
145    return ret;
146}
147
148gpointer
149node_get_data (node_t* node)
150{
151    g_assert (node);
152    return node->user_data;
153}
154
155gpointer
156node_set_data (node_t* node, gpointer user_data)
157{
158    gpointer data = node->user_data;
159    g_assert (node);
160    node->user_data = user_data;
161    return data;
162}
163
164void
165travel_nodes (node_t* node, node_op_t* op)
166{
167    GList* children;
168    GList* i;
169
170    if (node) {
171        if (op && op->hit) {
172            op->hit (node, op->user_data);
173        }
174    }
175    children = g_hash_table_get_values (node->children);
176    if (children) {
177        for (i = children; i; i = i->next) {
178            travel_nodes (i->data, op);
179        }
180        g_list_free (children);
181    }
182}
183
184static node_t*
185find_node_internal (node_t* node, const gchar* filename, node_op_t* op)
186{
187    gchar* str;
188    gchar* token;
189    gchar* lasts;
190    node_t* parent;
191    node_t* child;
192
193    g_assert (filename && filename[0] == '/');
194    g_assert (node);
195
196    parent = node;
197    str = g_strdup (filename + strlen (NODE_NAME(parent)));
198
199    if ((token = strtok_r (str, G_DIR_SEPARATOR_S, &lasts)) != NULL) {
200        do {
201            FN_W ("%s %s + %s\n", __func__, NODE_NAME(parent), token);
202            child = children_find (parent, token);
203            if (child) {
204                parent = child;
205            } else {
206                if (op && op->add_missing) {
207                    child = op->add_missing (parent, op->user_data);
208                    goto L_hit;
209                }
210                break;
211            }
212        } while ((token = strtok_r (NULL, G_DIR_SEPARATOR_S, &lasts)) != NULL);
213    } else {
214        /* It's the head */
215        g_assert (parent == _head);
216        child = _head;
217    }
218
219    if (token == NULL && child) {
220    L_hit:
221        if (op && op->hit) {
222            op->hit (child, op->user_data);
223        }
224    }
225    g_free (str);
226    return child;
227}
228
229node_t*
230find_node (const gchar *filename)
231{
232    return find_node_internal (_head, filename, NULL);
233}
234
235node_t*
236find_node_full (const gchar* filename, node_op_t* op)
237{
238    return find_node_internal (_head, filename, op);
239}
240
241node_t*
242add_node (node_t* parent, const gchar* filename)
243{
244    gchar* str;
245    gchar* token;
246    gchar* lasts;
247    node_t* child = NULL;
248
249    g_assert (_head);
250    g_assert (filename && filename[0] == '/');
251
252    if (parent == NULL) {
253        parent = _head;
254    }
255
256    str = g_strdup (filename + strlen (NODE_NAME(parent)));
257
258    if ((token = strtok_r (str, G_DIR_SEPARATOR_S, &lasts)) != NULL) {
259        do {
260            FN_W ("%s %s + %s\n", __func__, NODE_NAME(parent), token);
261            child = node_new (parent, token);
262            if (child) {
263                children_add (parent, child);
264                parent = child;
265            } else {
266                break;
267            }
268        } while ((token = strtok_r (NULL, G_DIR_SEPARATOR_S, &lasts)) != NULL);
269    }
270    g_free (str);
271    if (token == NULL) {
272        return child;
273    } else {
274        return NULL;
275    }
276}
277
278/**
279 * delete recursively
280 */
281static gboolean
282remove_children (node_t* node, node_op_t* op)
283{
284    FN_W ("%s 0x%p %s\n", __func__, node, NODE_NAME(node));
285    if (children_num (node) > 0) {
286        children_foreach_remove (node, children_remove_cb,
287          (gpointer)op);
288    }
289    if (children_num (node) == 0) {
290        return TRUE;
291    }
292    return FALSE;
293}
294
295static gboolean
296remove_node_internal (node_t* node, node_op_t* op)
297{
298    node_t* parent = NULL;
299    /*
300     * If the parent is passive and doesn't have children, delete it.
301     * NOTE node_delete_deep is a depth first delete recursively.
302     * Top node is deleted in node_cancel_sub
303     */
304    g_assert (node);
305    g_assert (op && op->pre_del);
306    if (node != _head) {
307        if (remove_children (node, op)) {
308            if (node->user_data) {
309                if (!op->pre_del (node, op->user_data)) {
310                    return FALSE;
311                }
312            }
313            parent = node->parent;
314            children_remove (parent, node);
315            node_delete (node);
316            if (children_num (parent) == 0) {
317                remove_node_internal (parent, op);
318            }
319            return TRUE;
320        }
321        return FALSE;
322    }
323    return TRUE;
324}
325
326void
327pending_remove_node (node_t* node, node_op_t* op)
328{
329    struct _dnode* d;
330    GList* l;
331
332    for (l = deleting_nodes; l; l=l->next) {
333        d = (struct _dnode*) l->data;
334        if (g_ascii_strcasecmp (d->filename, NODE_NAME(node)) == 0) {
335            return;
336        }
337    }
338
339    d = _dnode_new (NODE_NAME(node), op);
340    g_assert (d);
341    deleting_nodes = g_list_prepend (deleting_nodes, d);
342    if (deleting_nodes_id == 0) {
343        deleting_nodes_id = g_timeout_add_seconds (PROCESS_DELETING_INTERVAL,
344          scan_deleting_nodes,
345          NULL);
346        g_assert (deleting_nodes_id > 0);
347    }
348}
349
350void
351remove_node (node_t* node, node_op_t* op)
352{
353    remove_node_internal (node, op);
354}
355
356static node_t*
357node_new (node_t* parent, const gchar* basename)
358{
359	node_t *f = NULL;
360
361    g_assert (basename && basename[0]);
362    if ((f = g_new0 (node_t, 1)) != NULL) {
363        if (parent) {
364            f->basename = g_strdup (basename);
365            f->filename = g_build_filename (G_DIR_SEPARATOR_S,
366              NODE_NAME(parent), basename, NULL);
367        } else {
368            f->basename = g_strdup (basename);
369            f->filename = g_strdup (basename);
370        }
371        f->children = g_hash_table_new_full (g_str_hash, g_str_equal,
372          NULL, (GDestroyNotify)node_delete);
373        FN_W ("[ %s ] 0x%p %s\n", __func__, f, NODE_NAME(f));
374    }
375	return f;
376}
377
378static void
379node_delete (node_t *f)
380{
381    FN_W ("[ %s ] 0x%p %s\n", __func__, f, NODE_NAME(f));
382    g_assert (g_hash_table_size (f->children) == 0);
383    g_assert (f->user_data == NULL);
384
385    g_hash_table_unref (f->children);
386    g_free (f->basename);
387    g_free (f->filename);
388    g_free (f);
389}
390
391static void
392children_add (node_t *p, node_t *f)
393{
394    FN_W ("%s [p] %8s [c] %8s\n", __func__, p->basename, f->basename);
395    g_hash_table_insert (p->children, f->basename, f);
396    f->parent = p;
397}
398
399static void
400children_remove (node_t *p, node_t *f)
401{
402    FN_W ("%s [p] %8s [c] %8s\n", __func__, p->basename, f->basename);
403    g_hash_table_steal (p->children, f->basename);
404    f->parent = NULL;
405}
406
407guint
408children_num (node_t *f)
409{
410    return g_hash_table_size (f->children);
411}
412
413node_t *
414children_find (node_t *f, const gchar *basename)
415{
416    return (node_t *) g_hash_table_lookup (f->children, (gpointer)basename);
417}
418
419/**
420 * depth first delete recursively
421 */
422static gboolean
423children_remove_cb (gpointer key,
424  gpointer value,
425  gpointer user_data)
426{
427    node_t* f = (node_t*)value;
428    node_op_t* op = (node_op_t*) user_data;
429
430    g_assert (f->parent);
431
432    FN_W ("%s [p] %8s [c] %8s\n", __func__, f->parent->basename, f->basename);
433    if (remove_children (f, op)) {
434        if (f->user_data != NULL) {
435            return op->pre_del (f, op->user_data);
436        }
437        return TRUE;
438    }
439    return FALSE;
440}
441
442static guint
443children_foreach_remove (node_t *f, GHRFunc func, gpointer user_data)
444{
445    g_assert (f);
446
447    return g_hash_table_foreach_remove (f->children, func, user_data);
448}
449
450static void
451children_foreach (node_t *f, GHFunc func, gpointer user_data)
452{
453    g_assert (f);
454
455    g_hash_table_foreach (f->children, func, user_data);
456}
457
458gboolean
459node_class_init ()
460{
461    FN_W ("%s\n", __func__);
462    if (_head == NULL) {
463        _head = node_new (NULL, G_DIR_SEPARATOR_S);
464    }
465    return _head != NULL;
466}
467