Initialize Tizen 2.3
[framework/base/gconf-dbus.git] / backends / xml-cache.c
1 /* GConf
2  * Copyright (C) 1999, 2000 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "xml-cache.h"
21 #include <gconf/gconf-internals.h>
22
23 #include <string.h>
24 #include <time.h>
25
26 /* This makes hash table safer when debugging */
27 #ifndef GCONF_ENABLE_DEBUG
28 #define safe_g_hash_table_insert g_hash_table_insert
29 #else
30 static void
31 safe_g_hash_table_insert(GHashTable* ht, gpointer key, gpointer value)
32 {
33   gpointer oldkey = NULL, oldval = NULL;
34
35   if (g_hash_table_lookup_extended(ht, key, &oldkey, &oldval))
36     {
37       gconf_log(GCL_WARNING, "Hash key `%s' is already in the table!",
38                 (gchar*)key);
39       return;
40     }
41   else
42     {
43       g_hash_table_insert(ht, key, value);
44     }
45 }
46 #endif
47
48 static gboolean cache_is_nonexistent    (Cache       *cache,
49                                          const gchar *key);
50 static void     cache_set_nonexistent   (Cache       *cache,
51                                          const gchar *key,
52                                          gboolean     setting);
53 static void     cache_unset_nonexistent (Cache       *cache,
54                                          const gchar *key);
55 static void     cache_insert          (Cache       *cache,
56                                        Dir         *d);
57
58 static void     cache_remove_from_parent (Cache *cache,
59                                           Dir   *d);
60 static void     cache_add_to_parent      (Cache *cache,
61                                           Dir   *d);
62
63 static GHashTable *caches_by_root_dir = NULL;
64
65 struct _Cache {
66   gchar* root_dir;
67   GHashTable* cache;
68   GHashTable* nonexistent_cache;
69   guint dir_mode;
70   guint file_mode;
71   guint refcount;
72 };
73
74 Cache*
75 cache_get (const gchar  *root_dir,
76            guint dir_mode,
77            guint file_mode)
78 {
79   Cache* cache = NULL;
80
81   if (caches_by_root_dir == NULL)
82     caches_by_root_dir = g_hash_table_new (g_str_hash, g_str_equal);
83   else
84     cache = g_hash_table_lookup (caches_by_root_dir, root_dir);
85
86   if (cache != NULL)
87     {
88       cache->refcount += 1;
89       return cache;
90     }
91
92   cache = g_new(Cache, 1);
93
94   cache->root_dir = g_strdup(root_dir);
95
96   cache->cache = g_hash_table_new(g_str_hash, g_str_equal);
97   cache->nonexistent_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
98                                                    g_free, NULL);
99
100   cache->dir_mode = dir_mode;
101   cache->file_mode = file_mode;
102   cache->refcount = 1;
103
104   safe_g_hash_table_insert (caches_by_root_dir, cache->root_dir, cache);
105   
106   return cache;
107 }
108
109 static void cache_destroy_foreach(const gchar* key,
110                                   Dir* dir, gpointer data);
111
112 void
113 cache_unref (Cache *cache)
114 {
115   g_return_if_fail (cache != NULL);
116   g_return_if_fail (cache->refcount > 0);
117
118   if (cache->refcount > 1)
119     {
120       cache->refcount -= 1;
121       return;
122     }
123
124   g_hash_table_remove (caches_by_root_dir, cache->root_dir);
125   if (g_hash_table_size (caches_by_root_dir) == 0)
126     {
127       g_hash_table_destroy (caches_by_root_dir);
128       caches_by_root_dir = NULL;
129     }
130   
131   g_free(cache->root_dir);
132   g_hash_table_foreach(cache->cache, (GHFunc)cache_destroy_foreach,
133                        NULL);
134   g_hash_table_destroy(cache->cache);
135   g_hash_table_destroy(cache->nonexistent_cache);
136   
137   g_free(cache);
138 }
139
140
141 typedef struct _SyncData SyncData;
142 struct _SyncData {
143   gboolean failed;
144   Cache* dc;
145   gboolean deleted_some;
146 };
147
148 static void
149 listify_foreach (gpointer key, gpointer value, gpointer data)
150 {
151   GSList **list = data;
152
153   *list = g_slist_prepend (*list, value);
154 }
155
156 static void
157 cache_sync_foreach (Dir      *dir,
158                     SyncData *sd)
159 {
160   GError* error = NULL;
161   gboolean deleted;
162   
163   deleted = FALSE;
164   
165   /* log errors but don't report the specific ones */
166   if (!dir_sync (dir, &deleted, &error))
167     {
168       sd->failed = TRUE;
169       g_return_if_fail (error != NULL);
170       gconf_log (GCL_ERR, "%s", error->message);
171       g_error_free (error);
172       g_return_if_fail (dir_sync_pending (dir));
173     }
174   else
175     {
176       g_return_if_fail (error == NULL);
177       g_return_if_fail (!dir_sync_pending (dir));
178
179       if (deleted)
180         {
181           /* Get rid of this directory */
182           cache_remove_from_parent (sd->dc, dir);
183           g_hash_table_remove (sd->dc->cache,
184                                dir_get_name (dir));
185           cache_set_nonexistent (sd->dc, dir_get_name (dir),
186                                  TRUE);
187           dir_destroy (dir);
188
189           sd->deleted_some = TRUE;
190         }
191     }
192 }
193
194 static int
195 dircmp (gconstpointer a,
196         gconstpointer b)
197 {
198   Dir *dir_a = (Dir*) a;
199   Dir *dir_b = (Dir*) b;
200   const char *key_a = dir_get_name (dir_a);
201   const char *key_b = dir_get_name (dir_b);
202   
203   /* This function is supposed to sort the list such that
204    * subdirectories are synced prior to their parents,
205    * thus ensuring that we are always able to get rid
206    * of directories that we don't need anymore.
207    *
208    * Keys with an ancestor/descendent relationship are always
209    * sorted with descendent before ancestor. Other keys are sorted
210    * in order to alphabetize directories, i.e. we find the common
211    * path segments and alphabetize the level below the common level.
212    * /foo/bar/a before /foo/bar/b, etc.
213    *
214    * This ensures that our sort function has proper semantics.
215    */
216
217   if (gconf_key_is_below (key_a, key_b))
218     return 1; /* a above b, so b is earlier in the list */
219   else if (gconf_key_is_below (key_b, key_a))
220     return -1;
221   else
222     {
223       const char *ap = key_a;
224       const char *bp = key_b;
225
226       while (*ap && *bp && *ap == *bp)
227         {
228           ++ap;
229           ++bp;
230         }
231       
232       if (*ap == '\0' && *bp == '\0')
233         return 0;
234       
235       /* we don't care about localization here,
236        * just some fixed order. Either
237        * *ap or *bp may be '\0' if you have keys like
238        * "foo" and "foo_bar"
239        */
240       if (*ap < *bp)
241         return -1;
242       else
243         return 1;
244     }
245 }
246
247 gboolean
248 cache_sync (Cache    *cache,
249             GError  **err)
250 {
251   SyncData sd = { FALSE, NULL, FALSE };
252   GSList *list;
253   
254   sd.dc = cache;
255
256   gconf_log (GCL_DEBUG, "Syncing the dir cache");
257
258  redo:
259   sd.failed = FALSE;
260   sd.deleted_some = FALSE;
261   
262   /* get a list of everything; we can't filter by
263    * whether a sync is pending since we may make parents
264    * of removed directories dirty when we sync their child
265    * dir.
266    */
267   list = NULL;
268   g_hash_table_foreach (cache->cache, (GHFunc)listify_foreach, &list);
269
270   /* sort subdirs before parents */
271   list = g_slist_sort (list, dircmp);
272
273   /* sync it all */
274   g_slist_foreach (list, (GFunc) cache_sync_foreach, &sd);
275
276   /* If we deleted some subdirs, we may now be able to delete
277    * more parent dirs. So go ahead and do the sync again.
278    * Yeah this could be more efficient.
279    */
280   if (!sd.failed && sd.deleted_some)
281     goto redo;
282   
283   if (sd.failed && err && *err == NULL)
284     {
285       gconf_set_error (err, GCONF_ERROR_FAILED,
286                        _("Failed to sync XML cache contents to disk"));
287     }
288   
289   return !sd.failed;  
290 }
291
292 typedef struct _CleanData CleanData;
293 struct _CleanData {
294   GTime now;
295   Cache* cache;
296   GTime length;
297 };
298
299 static gboolean
300 cache_clean_foreach(const gchar* key,
301                     Dir* dir, CleanData* cd)
302 {
303   GTime last_access;
304
305   last_access = dir_get_last_access(dir);
306
307   if ((cd->now - last_access) >= cd->length)
308     {
309       if (!dir_sync_pending(dir))
310         {
311           dir_destroy(dir);
312           return TRUE;
313         }
314       else
315         {
316           gconf_log(GCL_WARNING, _("Unable to remove directory `%s' from the XML backend cache, because it has not been successfully synced to disk"),
317                     dir_get_name(dir));
318           return FALSE;
319         }
320     }
321   else
322     return FALSE;
323 }
324
325 void
326 cache_clean      (Cache        *cache,
327                   GTime         older_than)
328 {
329   CleanData cd = { 0, NULL, 0 };
330   cd.cache = cache;
331   cd.length = older_than;
332   
333   cd.now = time(NULL); /* ha ha, it's an online store! */
334   
335   g_hash_table_foreach_remove(cache->cache, (GHRFunc)cache_clean_foreach,
336                               &cd);
337
338 #if 0
339   size = g_hash_table_size(cache->cache);
340
341   if (size != 0)
342     gconf_log (GCL_DEBUG,
343                "%u items remain in the cache after cleaning already-synced items older than %u seconds",
344                size,
345                older_than);
346 #endif
347 }
348
349 Dir*
350 cache_lookup     (Cache        *cache,
351                   const gchar  *key,
352                   gboolean create_if_missing,
353                   GError  **err)
354 {
355   Dir* dir;
356   
357   g_assert(key != NULL);
358   g_return_val_if_fail(cache != NULL, NULL);
359   
360   /* Check cache */
361   dir = g_hash_table_lookup(cache->cache, key);
362   
363   if (dir != NULL)
364     {
365       gconf_log(GCL_DEBUG, "Using dir %s from cache", key);
366       return dir;
367     }
368   else
369     {
370       /* Not in cache, check whether we already failed
371          to load it */
372       if (cache_is_nonexistent(cache, key))
373         {
374           if (!create_if_missing)
375             return NULL;
376         }
377       else
378         {
379           /* Didn't already fail to load, try to load */
380           dir = dir_load (key, cache->root_dir, err);
381           
382           if (dir != NULL)
383             {
384               g_assert(err == NULL || *err == NULL);
385               
386               /* Cache it and add to parent */
387               cache_insert (cache, dir);
388               cache_add_to_parent (cache, dir);
389               
390               return dir;
391             }
392           else
393             {
394               /* Remember that we failed to load it */
395               if (!create_if_missing)
396                 {
397                   cache_set_nonexistent(cache, key, TRUE);
398               
399                   return NULL;
400                 }
401               else
402                 {
403                   if (err && *err)
404                     {
405                       g_error_free(*err);
406                       *err = NULL;
407                     }
408                 }
409             }
410         }
411     }
412   
413   g_assert(dir == NULL);
414   g_assert(create_if_missing);
415   g_assert(err == NULL || *err == NULL);
416   
417   if (dir == NULL)
418     {
419       gconf_log(GCL_DEBUG, "Creating new dir %s", key);
420       
421       dir = dir_new(key, cache->root_dir, cache->dir_mode, cache->file_mode);
422
423       if (!dir_ensure_exists(dir, err))
424         {
425           dir_destroy(dir);
426           
427           g_return_val_if_fail((err == NULL) ||
428                                (*err != NULL) ,
429                                NULL);
430           return NULL;
431         }
432       else
433         {
434           cache_insert (cache, dir);
435           cache_add_to_parent (cache, dir);
436           cache_unset_nonexistent (cache, dir_get_name (dir));
437         }
438     }
439
440   return dir;
441 }
442
443 static gboolean
444 cache_is_nonexistent(Cache* cache,
445                      const gchar* key)
446 {
447   return GPOINTER_TO_INT(g_hash_table_lookup(cache->nonexistent_cache,
448                                              key));
449 }
450
451 static void
452 cache_set_nonexistent   (Cache* cache,
453                          const gchar* key,
454                          gboolean setting)
455 {
456   if (setting)
457     {
458       /* don't use safe_ here, doesn't matter */
459       g_hash_table_insert(cache->nonexistent_cache,
460                           g_strdup(key),
461                           GINT_TO_POINTER(TRUE));
462     }
463   else
464     g_hash_table_remove(cache->nonexistent_cache, key);
465 }
466
467 static void
468 cache_unset_nonexistent (Cache       *cache,
469                          const gchar *key)
470 {
471   char *parent_key;
472
473   g_return_if_fail (key != NULL);
474
475   cache_set_nonexistent (cache, key, FALSE);
476
477   if (strcmp (key, "/") == 0)
478     return;
479
480   parent_key = gconf_key_directory (key);
481
482   cache_unset_nonexistent (cache, parent_key);
483
484   g_free (parent_key);
485 }
486
487 static void
488 cache_insert (Cache* cache,
489               Dir* d)
490 {
491   g_return_if_fail(d != NULL);
492
493   gconf_log(GCL_DEBUG, "Caching dir %s", dir_get_name(d));
494   
495   safe_g_hash_table_insert(cache->cache, (gchar*)dir_get_name(d), d);
496 }
497
498 static void
499 cache_destroy_foreach(const gchar* key,
500                       Dir* dir, gpointer data)
501 {
502 #ifdef GCONF_ENABLE_DEBUG
503   if (dir_sync_pending (dir))
504     gconf_log(GCL_DEBUG, "Destroying a directory (%s) with sync still pending",
505               dir_get_name (dir));
506 #endif
507   dir_destroy (dir);
508 }
509
510 static void
511 cache_remove_from_parent (Cache *cache,
512                           Dir   *d)
513 {
514   Dir *parent;
515   const char *name;
516
517   /* We have to actually force a load here, to decide
518    * whether to delete the parent.
519    */
520   parent = cache_lookup (cache, dir_get_parent_name (d),
521                          TRUE, NULL);
522
523   /* parent == d means d is the root dir */
524   if (parent == NULL || parent == d)
525     return;
526   
527   name = gconf_key_key (dir_get_name (d));
528
529   dir_child_removed (parent, name);
530 }
531
532 static void
533 cache_add_to_parent (Cache *cache,
534                      Dir   *d)
535 {
536   Dir *parent;
537   const char *name;
538
539   parent = cache_lookup (cache, dir_get_parent_name (d),
540                          FALSE, NULL);
541
542   /* parent == d means d is the root dir */
543   if (parent == NULL || parent == d)
544     return;
545
546   name = gconf_key_key (dir_get_name (d));
547
548   dir_child_added (parent, name);
549 }
550
551
552 void
553 xml_test_cache (void)
554 {
555 #ifndef GCONF_DISABLE_TESTS
556   
557
558
559 #endif
560 }