]> www.fi.muni.cz Git - evince.git/blob - shell/ev-metadata-manager.c
Added si
[evince.git] / shell / ev-metadata-manager.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * ev-metadata-manager.c
4  * This file is part of ev
5  *
6  * Copyright (C) 2003  Paolo Maggi 
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, 
21  * Boston, MA 02111-1307, USA. 
22  */
23  
24 /*
25  * Modified by the ev Team, 2003. See the AUTHORS file for a 
26  * list of people on the ev Team.  
27  * See the ChangeLog files for a list of changes. 
28  */
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33
34 #include <time.h>
35 #include <stdlib.h>
36
37 #include <libxml/xmlreader.h>
38
39 #include "ev-metadata-manager.h"
40 #include "ev-file-helpers.h"
41
42 #define METADATA_FILE   "ev-metadata.xml"
43
44 #define MAX_ITEMS       50
45
46 typedef struct _EvMetadataManager EvMetadataManager;
47
48 typedef struct _Item Item;
49
50 struct _Item
51 {
52         time_t           atime; /* time of last access */
53
54         GHashTable      *values;
55 };
56         
57 struct _EvMetadataManager
58 {
59         gboolean         values_loaded; /* It is true if the file 
60                                            has been read */
61
62         guint            timeout_id;
63
64         GHashTable      *items;
65 };
66
67 static gboolean ev_metadata_manager_save (gpointer data);
68
69
70 static EvMetadataManager *ev_metadata_manager = NULL;
71
72 /**
73  * item_free:
74  * @data: a pointer to a #Item data
75  *
76  * It does free the values on the #GHashTable where data points.
77  */
78 static void
79 item_free (gpointer data)
80 {
81         Item *item = (Item *) data;
82
83         if (item->values != NULL)
84                 g_hash_table_destroy (item->values);
85
86         g_slice_free (Item, item);
87 }
88
89 /**
90  * ev_metadata_arm_timeout
91  *
92  * Setup a timeout for saving the metadata to disk.
93  */
94 static void
95 ev_metadata_arm_timeout(void)
96 {
97         if (ev_metadata_manager->timeout_id)
98                 return;
99 #if GLIB_CHECK_VERSION (2, 13, 5)
100         ev_metadata_manager->timeout_id =
101                 g_timeout_add_seconds_full (G_PRIORITY_DEFAULT_IDLE,
102                                             2,
103                                             (GSourceFunc)ev_metadata_manager_save,
104                                             NULL,
105                                             NULL);
106 #else
107         ev_metadata_manager->timeout_id = 
108                 g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
109                                     2000, /* 2 sec */
110                                     (GSourceFunc)ev_metadata_manager_save,
111                                     NULL,
112                                     NULL);
113 #endif
114 }
115
116 /**
117  * ev_metadata_manager_init:
118  *
119  * Creates an EvMetadataManager with default values.
120  *
121  *  values_loaded   ->  %FALSE.
122  *  timeout_id      ->  the id of the event source.
123  *  items           ->  a new full empty #GHashTable.
124  */
125 void
126 ev_metadata_manager_init (void)
127 {
128         ev_metadata_manager = g_slice_new0 (EvMetadataManager);
129
130         ev_metadata_manager->values_loaded = FALSE;
131
132         ev_metadata_manager->items = 
133                 g_hash_table_new_full (g_str_hash, 
134                                        g_str_equal, 
135                                        g_free,
136                                        item_free);
137 }
138
139 /* This function must be called before exiting ev */
140 void
141 ev_metadata_manager_shutdown (void)
142 {
143         if (ev_metadata_manager == NULL)
144                 return;
145
146         if (ev_metadata_manager->timeout_id) {
147                 g_source_remove (ev_metadata_manager->timeout_id);
148                 ev_metadata_manager->timeout_id = 0;
149                 ev_metadata_manager_save (NULL);
150         }
151
152         if (ev_metadata_manager->items != NULL)
153                 g_hash_table_destroy (ev_metadata_manager->items);
154
155         g_slice_free (EvMetadataManager, ev_metadata_manager);
156         ev_metadata_manager = NULL;
157 }
158
159 static void
160 value_free (gpointer data)
161 {
162         GValue *value = (GValue *)data;
163
164         g_value_unset (value);
165         g_slice_free (GValue, value);
166 }
167
168 static GValue *
169 parse_value (xmlChar *value, xmlChar *type)
170 {
171         GType ret_type;
172         GValue *ret;
173
174         ret_type = g_type_from_name ((char *)type);
175         ret = g_slice_new0 (GValue);
176         g_value_init (ret, ret_type);
177
178         switch (ret_type) {
179                 case G_TYPE_STRING:
180                         g_value_set_string (ret, (char *)value);
181                         break;
182                 case G_TYPE_INT:
183                         g_value_set_int (ret, g_ascii_strtoull ((char *)value, NULL, 0));
184                         break;
185                 case G_TYPE_DOUBLE:
186                         g_value_set_double (ret, g_ascii_strtod ((char *)value, NULL));
187                         break;
188                 case G_TYPE_BOOLEAN:
189                         g_value_set_boolean (ret, g_ascii_strtoull ((char *)value, NULL, 0));
190                         break;
191         }
192
193         return ret;
194 }
195
196 static void
197 parseItem (xmlDocPtr doc, xmlNodePtr cur)
198 {
199         Item *item;
200         
201         xmlChar *uri;
202         xmlChar *atime;
203         
204         if (xmlStrcmp (cur->name, (const xmlChar *)"document") != 0)
205                         return;
206
207         uri = xmlGetProp (cur, (const xmlChar *)"uri");
208         if (uri == NULL)
209                 return;
210         
211         atime = xmlGetProp (cur, (const xmlChar *)"atime");
212         if (atime == NULL)
213         {
214                 xmlFree (uri);
215                 return;
216         }
217
218         item = g_slice_new0 (Item);
219
220         item->atime = g_ascii_strtoull((char*)atime, NULL, 0);
221         
222         item->values = g_hash_table_new_full (g_str_hash, 
223                                               g_str_equal, 
224                                               g_free, 
225                                               value_free);
226
227         cur = cur->xmlChildrenNode;
228                 
229         while (cur != NULL)
230         {
231                 if (xmlStrcmp (cur->name, (const xmlChar *)"entry") == 0)
232                 {
233                         xmlChar *key;
234                         xmlChar *xml_value;
235                         xmlChar *type;
236                         GValue  *value;
237                         
238                         key = xmlGetProp (cur, (const xmlChar *)"key");
239                         xml_value = xmlGetProp (cur, (const xmlChar *)"value");
240                         type = xmlGetProp (cur, (const xmlChar *)"type");
241                         value = parse_value (xml_value, type);
242
243                         if ((key != NULL) && (value != NULL))
244                                 g_hash_table_insert (item->values,
245                                                      xmlStrdup (key), 
246                                                      value);
247
248                         if (key != NULL)
249                                 xmlFree (key);
250                         if (type != NULL)
251                                 xmlFree (type);
252                         if (xml_value != NULL)
253                                 xmlFree (xml_value);
254                 }
255                 
256                 cur = cur->next;
257         }
258
259         g_hash_table_insert (ev_metadata_manager->items,
260                              xmlStrdup (uri),
261                              item);
262
263         xmlFree (uri);
264         xmlFree (atime);
265 }
266
267 static gboolean
268 load_values ()
269 {
270         xmlDocPtr doc;
271         xmlNodePtr cur;
272         gchar *file_name;
273
274         g_return_val_if_fail (ev_metadata_manager != NULL, FALSE);
275         g_return_val_if_fail (ev_metadata_manager->values_loaded == FALSE, FALSE);
276
277         ev_metadata_manager->values_loaded = TRUE;
278                 
279         xmlKeepBlanksDefault (0);
280
281         /* FIXME: file locking - Paolo */
282         file_name = g_build_filename (ev_dot_dir (), METADATA_FILE, NULL);
283         if (!g_file_test (file_name, G_FILE_TEST_EXISTS))
284         {
285                 g_free (file_name);
286                 return FALSE;
287         }
288
289         doc = xmlParseFile (file_name);
290         g_free (file_name);
291
292         if (doc == NULL)
293         {
294                 return FALSE;
295         }
296
297         cur = xmlDocGetRootElement (doc);
298         if (cur == NULL) 
299         {
300                 g_message ("The metadata file “%s” is empty", METADATA_FILE);
301                 xmlFreeDoc (doc);
302         
303                 return FALSE;
304         }
305
306         if (xmlStrcmp (cur->name, (const xmlChar *) "metadata")) 
307         {
308                 g_message ("File “%s” is of the wrong type", METADATA_FILE);
309                 xmlFreeDoc (doc);
310                 
311                 return FALSE;
312         }
313
314         cur = xmlDocGetRootElement (doc);
315         cur = cur->xmlChildrenNode;
316         
317         while (cur != NULL)
318         {
319                 parseItem (doc, cur);
320
321                 cur = cur->next;
322         }
323
324         xmlFreeDoc (doc);
325
326         return TRUE;
327 }
328
329 #define LAST_URI "last-used-value"
330
331 static gboolean
332 ev_metadata_manager_get_last (const gchar *key,
333                                  GValue      *value,
334                                  gboolean     ignore)
335 {
336         Item *item;
337         GValue *ret;
338
339         g_assert (ev_metadata_manager->values_loaded);
340         
341         if (ignore)
342                 return FALSE;
343
344         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
345                                             LAST_URI);
346
347         if (item == NULL)
348                 return FALSE;
349
350         item->atime = time (NULL);
351         
352         if (item->values == NULL)
353                 return FALSE;
354         
355         ret = (GValue *)g_hash_table_lookup (item->values, key);
356
357         if (ret != NULL) {
358                 g_value_init (value, G_VALUE_TYPE (ret));
359                 g_value_copy (ret, value);
360                 return TRUE;
361         }
362
363         return FALSE;
364 }
365
366 static void
367 ev_metadata_manager_set_last (const gchar *key,
368                               const GValue *value)
369 {
370         Item *item;
371         
372         g_assert (ev_metadata_manager->values_loaded);
373
374         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
375                                             LAST_URI);
376
377         if (item == NULL)
378         {
379                 item = g_slice_new0 (Item);
380
381                 g_hash_table_insert (ev_metadata_manager->items,
382                                      g_strdup (LAST_URI),
383                                      item);
384         }
385         
386         if (item->values == NULL)
387                  item->values = g_hash_table_new_full (g_str_hash, 
388                                                        g_str_equal, 
389                                                        g_free, 
390                                                        value_free);
391         if (value != NULL) {
392                 GValue *new;
393
394                 new = g_slice_new0 (GValue);
395                 g_value_init (new, G_VALUE_TYPE (value));
396                 g_value_copy (value, new);
397
398                 g_hash_table_insert (item->values,
399                                      g_strdup (key),
400                                      new);
401         } else {
402                 g_hash_table_remove (item->values,
403                                      key);
404         }
405
406         item->atime = time (NULL);
407         ev_metadata_arm_timeout ();
408         return;
409 }
410                                  
411 /**
412  * ev_metadata_manager_get:
413  * @uri: Uri to set data for, if @NULL, we return default value
414  * @key: Key to set uri
415  * @value: GValue struct filled up with value
416  * @ignore_last: if @TRUE, default value is ignored
417  * 
418  * Retrieve value for uri in metadata database
419  * 
420  * Returns: @TRUE if value was taken.
421  **/
422 gboolean
423 ev_metadata_manager_get (const gchar *uri,
424                          const gchar *key,
425                          GValue      *value, 
426                          gboolean     ignore_last)
427 {
428         Item *item;
429         GValue *ret;
430         
431         g_return_val_if_fail (key != NULL, FALSE);
432
433         if (ev_metadata_manager == NULL)
434                 return FALSE;
435
436         if (!ev_metadata_manager->values_loaded)
437         {
438                 gboolean res;
439
440                 res = load_values ();
441
442                 if (!res)
443                         return ev_metadata_manager_get_last (key, value, ignore_last);
444         }
445
446         if (uri == NULL)
447                 return ev_metadata_manager_get_last (key, value, ignore_last);
448
449         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
450                                             uri);
451
452         if (item == NULL)
453                 return ev_metadata_manager_get_last (key, value, ignore_last);
454
455         item->atime = time (NULL);
456         
457         if (item->values == NULL)
458                 return ev_metadata_manager_get_last (key, value, ignore_last);
459         
460         ret = (GValue *)g_hash_table_lookup (item->values, key);
461
462         if (ret != NULL) {
463                 g_value_init (value, G_VALUE_TYPE (ret));
464                 g_value_copy (ret, value);
465                 return TRUE;
466         }
467
468         return ev_metadata_manager_get_last (key, value, ignore_last);
469 }
470
471 /**
472  * ev_metadata_manager_set:
473  * @uri: Uri to set data for, if @NULL, we set default value
474  * @key: Key to set uri
475  * @value: GValue struct containing value
476  * 
477  * Set value for key in metadata database
478  **/
479 void
480 ev_metadata_manager_set (const gchar  *uri,
481                          const gchar  *key,
482                          const GValue *value)
483 {
484         Item *item;
485
486         g_return_if_fail (key != NULL);
487
488         if (ev_metadata_manager == NULL)
489                 return;
490
491         if (!ev_metadata_manager->values_loaded)
492         {
493                 gboolean res;
494
495                 res = load_values ();
496
497                 if (!res)
498                         return;
499         }
500
501         if (uri == NULL)
502         {
503                 ev_metadata_manager_set_last (key, value);
504                 return;
505         }
506
507         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
508                                             uri);
509
510         if (item == NULL)
511         {
512                 item = g_slice_new0 (Item);
513
514                 g_hash_table_insert (ev_metadata_manager->items,
515                                      g_strdup (uri),
516                                      item);
517         }
518         
519         if (item->values == NULL)
520                  item->values = g_hash_table_new_full (g_str_hash, 
521                                                        g_str_equal, 
522                                                        g_free, 
523                                                        value_free);
524         if (value != NULL) {
525                 GValue *new;
526
527                 new = g_slice_new0 (GValue);
528                 g_value_init (new, G_VALUE_TYPE (value));
529                 g_value_copy (value, new);
530
531                 g_hash_table_insert (item->values,
532                                      g_strdup (key),
533                                      new);
534                 ev_metadata_manager_set_last (key, value);
535         } else {
536                 g_hash_table_remove (item->values,
537                                      key);
538         }
539
540         item->atime = time (NULL);
541
542         ev_metadata_arm_timeout ();
543 }
544
545 static void
546 save_values (const gchar *key, GValue *value, xmlNodePtr parent)
547 {
548         char *string_value;
549         xmlNodePtr xml_node;
550         
551         g_return_if_fail (key != NULL);
552         
553         if (value == NULL)
554                 return;
555                 
556         xml_node = xmlNewChild (parent, NULL, (const xmlChar *)"entry", NULL);
557
558         xmlSetProp (xml_node, (const xmlChar *)"key", (const xmlChar *)key);
559         xmlSetProp (xml_node,
560                     (const xmlChar *)"type",
561                     (const xmlChar *)g_type_name (G_VALUE_TYPE (value)));
562
563         switch (G_VALUE_TYPE (value)) {
564                 case G_TYPE_STRING:
565                         string_value = g_value_dup_string (value);
566                         break;
567                 case G_TYPE_INT:
568                         string_value = g_strdup_printf ("%d", g_value_get_int (value));
569                         break;
570                 case G_TYPE_DOUBLE:
571                         {
572                                 gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
573                                 g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, g_value_get_double (value));
574                                 string_value = g_strdup (buf);
575                         }
576                         break;
577                 case G_TYPE_BOOLEAN:
578                         string_value = g_strdup_printf ("%d", g_value_get_boolean (value));
579                         break;
580                 default:
581                         string_value = NULL;
582                         g_assert_not_reached ();
583         }
584
585         xmlSetProp (xml_node, (const xmlChar *)"value", (const xmlChar *)string_value);
586
587         g_free (string_value);
588 }
589
590 static void
591 save_item (const gchar *key, const gpointer *data, xmlNodePtr parent)
592 {       
593         xmlNodePtr xml_node;
594         const Item *item = (const Item *)data;
595         gchar *atime;
596
597         g_return_if_fail (key != NULL);
598         
599         if (item == NULL)
600                 return;
601                 
602         xml_node = xmlNewChild (parent, NULL, (const xmlChar *)"document", NULL);
603         
604         xmlSetProp (xml_node, (const xmlChar *)"uri", (const xmlChar *)key);
605
606         atime = g_strdup_printf ("%ld", item->atime);
607         xmlSetProp (xml_node, (const xmlChar *)"atime", (const xmlChar *)atime);
608         g_free (atime);
609
610         g_hash_table_foreach (item->values,
611                               (GHFunc)save_values, xml_node); 
612 }
613
614 static void
615 get_oldest (const gchar *key, const gpointer value, const gchar ** key_to_remove)
616 {
617         const Item *item = (const Item *)value;
618         
619         if (*key_to_remove == NULL)
620         {
621                 *key_to_remove = key;
622         }
623         else
624         {
625                 const Item *item_to_remove = 
626                         g_hash_table_lookup (ev_metadata_manager->items,
627                                              *key_to_remove);
628
629                 g_return_if_fail (item_to_remove != NULL);
630
631                 if (item->atime < item_to_remove->atime)
632                 {
633                         *key_to_remove = key;
634                 }
635         }       
636 }
637
638 static void
639 resize_items ()
640 {
641         while (g_hash_table_size (ev_metadata_manager->items) > MAX_ITEMS)
642         {
643                 gpointer key_to_remove = NULL;
644
645                 g_hash_table_foreach (ev_metadata_manager->items,
646                                       (GHFunc)get_oldest,
647                                       &key_to_remove);
648
649                 g_return_if_fail (key_to_remove != NULL);
650                 
651                 g_hash_table_remove (ev_metadata_manager->items,
652                                      key_to_remove);
653         }
654 }
655
656 static gboolean
657 ev_metadata_manager_save (gpointer data)
658 {       
659         xmlDocPtr  doc;
660         xmlNodePtr root;
661         gchar *file_name;
662
663         ev_metadata_manager->timeout_id = 0;
664
665         resize_items ();
666                 
667         xmlIndentTreeOutput = TRUE;
668
669         doc = xmlNewDoc ((const xmlChar *)"1.0");
670         if (doc == NULL)
671                 return TRUE;
672
673         /* Create metadata root */
674         root = xmlNewDocNode (doc, NULL, (const xmlChar *)"metadata", NULL);
675         xmlDocSetRootElement (doc, root);
676
677         g_hash_table_foreach (ev_metadata_manager->items,
678                           (GHFunc)save_item, root);        
679
680         /* FIXME: lock file - Paolo */
681         file_name = g_build_filename (ev_dot_dir (), METADATA_FILE, NULL);
682         xmlSaveFormatFile (file_name, doc, 1);
683         g_free (file_name);
684         
685         xmlFreeDoc (doc); 
686
687         return FALSE;
688 }
689
690 void
691 ev_metadata_manager_set_int (const gchar *uri, const gchar *key, int value)
692 {
693         GValue val = { 0, };
694
695         g_value_init (&val, G_TYPE_INT);
696         g_value_set_int (&val, value);
697
698         ev_metadata_manager_set (uri, key, &val);
699
700         g_value_unset (&val);
701 }
702
703 void
704 ev_metadata_manager_set_double (const gchar *uri, const gchar *key, double value)
705 {
706         GValue val = { 0, };
707
708         g_value_init (&val, G_TYPE_DOUBLE);
709         g_value_set_double (&val, value);
710
711         ev_metadata_manager_set (uri, key, &val);
712
713         g_value_unset (&val);
714 }
715
716 void
717 ev_metadata_manager_set_string (const gchar *uri, const gchar *key, const gchar *value)
718 {
719         GValue val = { 0, };
720
721         g_value_init (&val, G_TYPE_STRING);
722         g_value_set_string (&val, value);
723
724         ev_metadata_manager_set (uri, key, &val);
725
726         g_value_unset (&val);
727 }
728
729 void
730 ev_metadata_manager_set_boolean (const gchar *uri, const gchar *key, gboolean value)
731 {
732         GValue val = { 0, };
733
734         g_value_init (&val, G_TYPE_BOOLEAN);
735         g_value_set_boolean (&val, value);
736
737         ev_metadata_manager_set (uri, key, &val);
738
739         g_value_unset (&val);
740 }