source: trunk/third/GConf/backends/xml-cache.c @ 17087

Revision 17087, 12.2 KB checked in by ghudson, 23 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r17086, which included commits to RCS files with non-trunk default branches.
Line 
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 <time.h>
24
25/* This makes hash table safer when debugging */
26#ifndef GCONF_ENABLE_DEBUG
27#define safe_g_hash_table_insert g_hash_table_insert
28#else
29static void
30safe_g_hash_table_insert(GHashTable* ht, gpointer key, gpointer value)
31{
32  gpointer oldkey = NULL, oldval = NULL;
33
34  if (g_hash_table_lookup_extended(ht, key, &oldkey, &oldval))
35    {
36      gconf_log(GCL_WARNING, "Hash key `%s' is already in the table!",
37                (gchar*)key);
38      return;
39    }
40  else
41    {
42      g_hash_table_insert(ht, key, value);
43    }
44}
45#endif
46
47static gboolean
48cache_is_nonexistent(Cache* cache,
49                     const gchar* key);
50
51static void
52cache_set_nonexistent   (Cache* cache,
53                         const gchar* key,
54                         gboolean setting);
55
56static void
57cache_insert (Cache* cache,
58              Dir* d);
59
60struct _Cache {
61  gchar* root_dir;
62  GHashTable* cache;
63  GHashTable* nonexistent_cache;
64  /*
65    List of lists of dirs marked deleted, in the
66    proper order; should be synced by deleting each
67    list from front to end, starting with the first
68    list.
69  */
70  GSList* deleted;
71  guint dir_mode;
72  guint file_mode;
73};
74
75Cache*
76cache_new (const gchar  *root_dir,
77           guint dir_mode,
78           guint file_mode)
79{
80  Cache* cache;
81
82  cache = g_new(Cache, 1);
83
84  cache->root_dir = g_strdup(root_dir);
85
86  cache->cache = g_hash_table_new(g_str_hash, g_str_equal);
87  cache->nonexistent_cache = g_hash_table_new(g_str_hash, g_str_equal);
88
89  cache->deleted = NULL;
90
91  cache->dir_mode = dir_mode;
92  cache->file_mode = file_mode;
93 
94  return cache;
95}
96
97static void cache_destroy_foreach(const gchar* key,
98                                  Dir* dir, gpointer data);
99
100static void cache_destroy_nonexistent_foreach(gchar* key,
101                                              gpointer val,
102                                              gpointer data);
103
104void
105cache_destroy (Cache        *cache)
106{
107  GSList *iter;
108 
109  g_free(cache->root_dir);
110  g_hash_table_foreach(cache->cache, (GHFunc)cache_destroy_foreach,
111                       NULL);
112  g_hash_table_foreach(cache->nonexistent_cache,
113                       (GHFunc)cache_destroy_nonexistent_foreach,
114                       NULL);
115  g_hash_table_destroy(cache->cache);
116  g_hash_table_destroy(cache->nonexistent_cache);
117
118  if (cache->deleted != NULL)
119    gconf_log(GCL_WARNING, _("Unsynced directory deletions when shutting down XML backend"));
120 
121  iter = cache->deleted;
122
123  while (iter != NULL)
124    {
125      g_slist_free(iter->data);
126
127      iter = g_slist_next(iter);
128    }
129  g_slist_free(cache->deleted);
130 
131  g_free(cache);
132}
133
134
135typedef struct _SyncData SyncData;
136struct _SyncData {
137  gboolean failed;
138  Cache* dc;
139};
140
141static void
142cache_sync_foreach(const gchar* key,
143                   Dir* dir,
144                   SyncData* sd)
145{
146  GError* error = NULL;
147 
148  /* log errors but don't report the specific ones */
149  if (!dir_sync(dir, &error))
150    {
151      sd->failed = TRUE;
152      g_return_if_fail(error != NULL);
153      gconf_log(GCL_ERR, "%s", error->message);
154      g_error_free(error);
155      g_return_if_fail(dir_sync_pending(dir));
156    }
157  else
158    {
159      g_return_if_fail(error == NULL);
160      g_return_if_fail(!dir_sync_pending(dir));
161    }
162}
163
164gboolean
165cache_sync       (Cache        *cache,
166                  GError  **err)
167{
168  SyncData sd = { FALSE, NULL };
169  GSList* delete_list;
170  sd.dc = cache;
171
172  /* First delete pending directories */
173  delete_list = cache->deleted;
174
175  while (delete_list != NULL)
176    {
177      GSList* tmp;
178
179      tmp = delete_list->data;
180
181      while (tmp != NULL)
182        {
183          Dir* d = tmp->data;
184
185          if (!dir_sync(d, NULL)) /* don't get errors, they'd pile up */
186            sd.failed = TRUE;
187         
188          tmp = g_slist_next(tmp);
189        }
190
191      g_slist_free(delete_list->data);
192     
193      delete_list = g_slist_next(delete_list);
194    }
195
196  g_slist_free(cache->deleted);
197  cache->deleted = NULL;
198 
199  g_hash_table_foreach(cache->cache, (GHFunc)cache_sync_foreach,
200                       &sd);
201
202  return !sd.failed; 
203}
204
205typedef struct _CleanData CleanData;
206struct _CleanData {
207  GTime now;
208  Cache* cache;
209  GTime length;
210};
211
212static gboolean
213cache_clean_foreach(const gchar* key,
214                    Dir* dir, CleanData* cd)
215{
216  GTime last_access;
217
218  last_access = dir_get_last_access(dir);
219
220  if ((cd->now - last_access) >= cd->length)
221    {
222      if (!dir_sync_pending(dir))
223        {
224          dir_destroy(dir);
225          return TRUE;
226        }
227      else
228        {
229          gconf_log(GCL_WARNING, _("Unable to remove directory `%s' from the XML backend cache, because it has not been successfully synced to disk"),
230                    dir_get_name(dir));
231          return FALSE;
232        }
233    }
234  else
235    return FALSE;
236}
237
238void
239cache_clean      (Cache        *cache,
240                  GTime         older_than)
241{
242  CleanData cd = { 0, 0, 0 };
243  guint size;
244  cd.cache = cache;
245  cd.length = older_than;
246 
247  cd.now = time(NULL); /* ha ha, it's an online store! */
248 
249  g_hash_table_foreach_remove(cache->cache, (GHRFunc)cache_clean_foreach,
250                              &cd);
251
252  size = g_hash_table_size(cache->cache);
253
254  if (size != 0)
255    gconf_log (GCL_DEBUG,
256               _("%u items remain in the cache after cleaning already-synced items older than %u seconds"),
257               size,
258               older_than);
259}
260
261static void
262cache_delete_dir_by_pointer(Cache* cache,
263                            Dir * d,
264                            GError** err);
265
266static void
267cache_delete_recursive(Cache* cache, Dir* d, GSList** hit_list, GError** err)
268
269  GSList* subdirs;
270  GSList* tmp;
271  gboolean failure = FALSE;
272 
273  subdirs = dir_all_subdirs(d, err);
274
275  if (subdirs == NULL && err && *err != NULL)
276    failure = TRUE;
277 
278  tmp = subdirs;
279  while (tmp != NULL && !failure)
280    {
281      Dir* subd;
282      gchar* fullkey;
283
284      fullkey = gconf_concat_dir_and_key(dir_get_name(d), (gchar*)tmp->data);
285     
286      subd = cache_lookup(cache, fullkey, FALSE, err);
287
288      g_free(tmp->data);
289      g_free(fullkey);
290     
291      if (subd == NULL && err && *err)
292        failure = TRUE;
293      else if (subd != NULL &&
294               !dir_is_deleted(subd))
295        {
296          /* recurse, whee! (unless the subdir is already deleted) */
297          cache_delete_dir_by_pointer(cache, subd, err);
298
299          if (err && *err)
300            failure = TRUE;
301        }
302         
303      tmp = g_slist_next(tmp);
304    }
305
306  g_slist_free(subdirs);
307 
308  /* The first directories to be deleted (fringes) go on the front
309     of the list. */
310  *hit_list = g_slist_prepend(*hit_list, d);
311 
312  /* We go ahead and mark the dir deleted */
313  dir_mark_deleted(d);
314
315  /* be sure we set error if failure occurred */
316  g_return_if_fail( (!failure) || (err == NULL) || (*err != NULL));
317}
318
319
320static void
321cache_delete_dir_by_pointer(Cache* cache,
322                            Dir * d,
323                            GError** err)
324{
325  GSList* hit_list = NULL;
326
327  cache_delete_recursive(cache, d, &hit_list, err);
328
329  /* If you first dir_cache_delete() a subdir, then dir_cache_delete()
330     its parent, without syncing, first the list generated by
331     the subdir delete then the list from the parent delete should
332     be nuked. If you first delete a parent, then its subdir,
333     really only the parent list should be nuked, but
334     in effect it's OK to nuke the parent first then
335     fail to nuke the subdir. So, if we prepend here,
336     then nuke the list in order, it will work fine.
337  */
338 
339  cache->deleted = g_slist_prepend(cache->deleted, hit_list);
340}
341
342void
343cache_delete_dir (Cache        *cache,
344                  const gchar  *key,
345                  GError  **err)
346{
347  Dir* d;
348
349  d = cache_lookup(cache, key, FALSE, err);
350
351  if (d != NULL)
352    {
353      g_assert(err == NULL || *err == NULL);
354      cache_delete_dir_by_pointer(cache, d, err);
355    }
356}
357
358Dir*
359cache_lookup     (Cache        *cache,
360                  const gchar  *key,
361                  gboolean create_if_missing,
362                  GError  **err)
363{
364  Dir* dir;
365 
366  g_assert(key != NULL);
367  g_return_val_if_fail(cache != NULL, NULL);
368 
369  /* Check cache */
370  dir = g_hash_table_lookup(cache->cache, key);
371 
372  if (dir != NULL)
373    {
374      gconf_log(GCL_DEBUG, "Using dir %s from cache", key);
375      return dir;
376    }
377  else
378    {
379      /* Not in cache, check whether we already failed
380         to load it */
381      if (cache_is_nonexistent(cache, key))
382        {
383          if (!create_if_missing)
384            return NULL;
385        }
386      else
387        {
388          /* Didn't already fail to load, try to load */
389          dir = dir_load(key, cache->root_dir, err);
390         
391          if (dir != NULL)
392            {
393              g_assert(err == NULL || *err == NULL);
394             
395              /* Cache it */
396              cache_insert(cache, dir);
397             
398              return dir;
399            }
400          else
401            {
402              /* Remember that we failed to load it */
403              if (!create_if_missing)
404                {
405                  cache_set_nonexistent(cache, key, TRUE);
406             
407                  return NULL;
408                }
409              else
410                {
411                  if (err && *err)
412                    {
413                      g_error_free(*err);
414                      *err = NULL;
415                    }
416                }
417            }
418        }
419    }
420 
421  g_assert(dir == NULL);
422  g_assert(create_if_missing);
423  g_assert(err == NULL || *err == NULL);
424 
425  if (dir == NULL)
426    {
427      gconf_log(GCL_DEBUG, "Creating new dir %s", key);
428     
429      dir = dir_new(key, cache->root_dir, cache->dir_mode, cache->file_mode);
430
431      if (!dir_ensure_exists(dir, err))
432        {
433          dir_destroy(dir);
434         
435          g_return_val_if_fail((err == NULL) ||
436                               (*err != NULL) ,
437                               NULL);
438          return NULL;
439        }
440      else
441        cache_insert(cache, dir);
442    }
443
444  return dir;
445}
446
447static gboolean
448cache_is_nonexistent(Cache* cache,
449                     const gchar* key)
450{
451  return GPOINTER_TO_INT(g_hash_table_lookup(cache->nonexistent_cache,
452                                             key));
453}
454
455static void
456cache_set_nonexistent   (Cache* cache,
457                         const gchar* key,
458                         gboolean setting)
459{
460  if (setting)
461    {
462      /* don't use safe_ here, doesn't matter */
463      g_hash_table_insert(cache->nonexistent_cache,
464                          g_strdup(key),
465                          GINT_TO_POINTER(TRUE));
466    }
467  else
468    {
469      gpointer origkey;
470      gpointer origval;
471
472      if (g_hash_table_lookup_extended(cache->nonexistent_cache,
473                                       key,
474                                       &origkey, &origval))
475        {
476          g_free(origkey);
477          g_hash_table_remove(cache->nonexistent_cache,
478                              key);
479        }
480    }
481}
482
483static void
484cache_insert (Cache* cache,
485              Dir* d)
486{
487  g_return_if_fail(d != NULL);
488
489  gconf_log(GCL_DEBUG, "Caching dir %s", dir_get_name(d));
490 
491  safe_g_hash_table_insert(cache->cache, (gchar*)dir_get_name(d), d);
492}
493
494static void
495cache_destroy_foreach(const gchar* key,
496                      Dir* dir, gpointer data)
497{
498#ifdef GCONF_ENABLE_DEBUG
499  if (dir_sync_pending(dir))
500    gconf_log(GCL_DEBUG, "Destroying a directory (%s) with sync still pending",
501              dir_get_name(dir));
502#endif
503  dir_destroy(dir);
504}
505
506static void
507cache_destroy_nonexistent_foreach(gchar* key,
508                                  gpointer val,
509                                  gpointer data)
510{
511  g_free(key);
512}
513
514
515
516
Note: See TracBrowser for help on using the repository browser.