]> www.fi.muni.cz Git - evince.git/blob - shell/ev-metadata-manager.c
402beb7096a54203932fe25ad5e47eb3c4a4c36b
[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         gboolean         modified;      /* It is true if the file 
63                                            has top be written */
64
65         guint            timeout_id;
66
67         GHashTable      *items;
68 };
69
70 static gboolean ev_metadata_manager_save (gpointer data);
71
72
73 static EvMetadataManager *ev_metadata_manager = NULL;
74
75 /**
76  * item_free:
77  * @data: a pointer to a #Item data
78  *
79  * It does free the values on the #GHashTable where data points.
80  */
81 static void
82 item_free (gpointer data)
83 {
84         Item *item;
85         
86         g_return_if_fail (data != NULL);
87
88         item = (Item *)data;
89
90         if (item->values != NULL)
91                 g_hash_table_destroy (item->values);
92
93         g_free (item);
94 }
95
96 /**
97  * ev_metadata_arm_timeout
98  *
99  * Setup a timeout for saving the metadata to disk.
100  */
101 static void
102 ev_metadata_arm_timeout(void)
103 {
104         if (ev_metadata_manager->timeout_id)
105                 return;
106         ev_metadata_manager->timeout_id = 
107                 g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
108                                     2000, /* 2 sec */
109                                     (GSourceFunc)ev_metadata_manager_save,
110                                     NULL,
111                                     NULL);
112 }
113
114 /**
115  * ev_metadata_manager_init:
116  *
117  * Creates an EvMetadataManager with default values.
118  *
119  *  values_loaded   ->  %FALSE.
120  *  modified        ->  %FALSE.
121  *  timeout_id      ->  the id of the event source.
122  *  items           ->  a new full empty #GHashTable.
123  */
124 void
125 ev_metadata_manager_init (void)
126 {
127         ev_metadata_manager = g_new0 (EvMetadataManager, 1);
128
129         ev_metadata_manager->values_loaded = FALSE;
130         ev_metadata_manager->modified = 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
149         ev_metadata_manager_save (NULL);
150
151         if (ev_metadata_manager->items != NULL)
152                 g_hash_table_destroy (ev_metadata_manager->items);
153
154         g_free (ev_metadata_manager);
155         ev_metadata_manager = NULL;
156 }
157
158 static void
159 value_free (gpointer data)
160 {
161         GValue *value = (GValue *)data;
162
163         g_value_unset (value);
164         g_free (value);
165 }
166
167 static GValue *
168 parse_value (xmlChar *value, xmlChar *type)
169 {
170         GType ret_type;
171         GValue *ret;
172
173         ret_type = g_type_from_name ((char *)type);
174         ret = g_new0 (GValue, 1);
175         g_value_init (ret, ret_type);
176
177         switch (ret_type) {
178                 case G_TYPE_STRING:
179                         g_value_set_string (ret, (char *)value);
180                         break;
181                 case G_TYPE_INT:
182                         g_value_set_int (ret, g_ascii_strtoull ((char *)value, NULL, 0));
183                         break;
184                 case G_TYPE_DOUBLE:
185                         g_value_set_double (ret, g_ascii_strtod ((char *)value, NULL));
186                         break;
187                 case G_TYPE_BOOLEAN:
188                         g_value_set_boolean (ret, g_ascii_strtoull ((char *)value, NULL, 0));
189                         break;
190         }
191
192         return ret;
193 }
194
195 static void
196 parseItem (xmlDocPtr doc, xmlNodePtr cur)
197 {
198         Item *item;
199         
200         xmlChar *uri;
201         xmlChar *atime;
202         
203         if (xmlStrcmp (cur->name, (const xmlChar *)"document") != 0)
204                         return;
205
206         uri = xmlGetProp (cur, (const xmlChar *)"uri");
207         if (uri == NULL)
208                 return;
209         
210         atime = xmlGetProp (cur, (const xmlChar *)"atime");
211         if (atime == NULL)
212         {
213                 xmlFree (uri);
214                 return;
215         }
216
217         item = g_new0 (Item, 1);
218
219         item->atime = g_ascii_strtoull((char*)atime, NULL, 0);
220         
221         item->values = g_hash_table_new_full (g_str_hash, 
222                                               g_str_equal, 
223                                               g_free, 
224                                               value_free);
225
226         cur = cur->xmlChildrenNode;
227                 
228         while (cur != NULL)
229         {
230                 if (xmlStrcmp (cur->name, (const xmlChar *)"entry") == 0)
231                 {
232                         xmlChar *key;
233                         xmlChar *xml_value;
234                         xmlChar *type;
235                         GValue  *value;
236                         
237                         key = xmlGetProp (cur, (const xmlChar *)"key");
238                         xml_value = xmlGetProp (cur, (const xmlChar *)"value");
239                         type = xmlGetProp (cur, (const xmlChar *)"type");
240                         value = parse_value (xml_value, type);
241
242                         if ((key != NULL) && (value != NULL))
243                                 g_hash_table_insert (item->values,
244                                                      xmlStrdup (key), 
245                                                      value);
246
247                         if (key != NULL)
248                                 xmlFree (key);
249                         if (type != NULL)
250                                 xmlFree (type);
251                         if (xml_value != NULL)
252                                 xmlFree (xml_value);
253                 }
254                 
255                 cur = cur->next;
256         }
257
258         g_hash_table_insert (ev_metadata_manager->items,
259                              xmlStrdup (uri),
260                              item);
261
262         xmlFree (uri);
263         xmlFree (atime);
264 }
265
266 static gboolean
267 load_values ()
268 {
269         xmlDocPtr doc;
270         xmlNodePtr cur;
271         gchar *file_name;
272
273         g_return_val_if_fail (ev_metadata_manager != NULL, FALSE);
274         g_return_val_if_fail (ev_metadata_manager->values_loaded == FALSE, FALSE);
275
276         ev_metadata_manager->values_loaded = TRUE;
277                 
278         xmlKeepBlanksDefault (0);
279
280         /* FIXME: file locking - Paolo */
281         file_name = g_build_filename (ev_dot_dir (), METADATA_FILE, NULL);
282         if (!g_file_test (file_name, G_FILE_TEST_EXISTS))
283         {
284                 g_free (file_name);
285                 return FALSE;
286         }
287
288         doc = xmlParseFile (file_name);
289         g_free (file_name);
290
291         if (doc == NULL)
292         {
293                 return FALSE;
294         }
295
296         cur = xmlDocGetRootElement (doc);
297         if (cur == NULL) 
298         {
299                 g_message ("The metadata file “%s” is empty", METADATA_FILE);
300                 xmlFreeDoc (doc);
301         
302                 return FALSE;
303         }
304
305         if (xmlStrcmp (cur->name, (const xmlChar *) "metadata")) 
306         {
307                 g_message ("File “%s” is of the wrong type", METADATA_FILE);
308                 xmlFreeDoc (doc);
309                 
310                 return FALSE;
311         }
312
313         cur = xmlDocGetRootElement (doc);
314         cur = cur->xmlChildrenNode;
315         
316         while (cur != NULL)
317         {
318                 parseItem (doc, cur);
319
320                 cur = cur->next;
321         }
322
323         xmlFreeDoc (doc);
324
325         return TRUE;
326 }
327
328 #define LAST_URI "last-used-value"
329
330 static gboolean
331 ev_metadata_manager_get_last (const gchar *key,
332                                  GValue      *value,
333                                  gboolean     ignore)
334 {
335         Item *item;
336         GValue *ret;
337
338         g_assert (ev_metadata_manager->values_loaded);
339         
340         if (ignore)
341                 return FALSE;
342
343         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
344                                             LAST_URI);
345
346         if (item == NULL)
347                 return FALSE;
348
349         item->atime = time (NULL);
350         
351         if (item->values == NULL)
352                 return FALSE;
353         
354         ret = (GValue *)g_hash_table_lookup (item->values, key);
355
356         if (ret != NULL) {
357                 g_value_init (value, G_VALUE_TYPE (ret));
358                 g_value_copy (ret, value);
359                 return TRUE;
360         }
361
362         return FALSE;
363 }
364
365 static void
366 ev_metadata_manager_set_last (const gchar *key,
367                               const GValue *value)
368 {
369         Item *item;
370         
371         g_assert (ev_metadata_manager->values_loaded);
372
373         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
374                                             LAST_URI);
375
376         if (item == NULL)
377         {
378                 item = g_new0 (Item, 1);
379
380                 g_hash_table_insert (ev_metadata_manager->items,
381                                      g_strdup (LAST_URI),
382                                      item);
383         }
384         
385         if (item->values == NULL)
386                  item->values = g_hash_table_new_full (g_str_hash, 
387                                                        g_str_equal, 
388                                                        g_free, 
389                                                        value_free);
390         if (value != NULL) {
391                 GValue *new;
392
393                 new = g_new0 (GValue, 1);
394                 g_value_init (new, G_VALUE_TYPE (value));
395                 g_value_copy (value, new);
396
397                 g_hash_table_insert (item->values,
398                                      g_strdup (key),
399                                      new);
400         } else {
401                 g_hash_table_remove (item->values,
402                                      key);
403         }
404
405         item->atime = time (NULL);
406         ev_metadata_manager->modified = TRUE;
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_new0 (Item, 1);
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_new0 (GValue, 1);
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_manager->modified = TRUE;
543         ev_metadata_arm_timeout ();
544 }
545
546 static void
547 save_values (const gchar *key, GValue *value, xmlNodePtr parent)
548 {
549         char *string_value;
550         xmlNodePtr xml_node;
551         
552         g_return_if_fail (key != NULL);
553         
554         if (value == NULL)
555                 return;
556                 
557         xml_node = xmlNewChild (parent, NULL, (const xmlChar *)"entry", NULL);
558
559         xmlSetProp (xml_node, (const xmlChar *)"key", (const xmlChar *)key);
560         xmlSetProp (xml_node,
561                     (const xmlChar *)"type",
562                     (const xmlChar *)g_type_name (G_VALUE_TYPE (value)));
563
564         switch (G_VALUE_TYPE (value)) {
565                 case G_TYPE_STRING:
566                         string_value = g_strdup (g_value_get_string (value));
567                         break;
568                 case G_TYPE_INT:
569                         string_value = g_strdup_printf ("%d", g_value_get_int (value));
570                         break;
571                 case G_TYPE_DOUBLE:
572                         {
573                                 gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
574                                 g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, g_value_get_double (value));
575                                 string_value = g_strdup_printf ("%s", buf);
576                         }
577                         break;
578                 case G_TYPE_BOOLEAN:
579                         string_value = g_strdup_printf ("%d", g_value_get_boolean (value));
580                         break;
581                 default:
582                         string_value = NULL;
583                         g_assert_not_reached ();
584         }
585
586         xmlSetProp (xml_node, (const xmlChar *)"value", (const xmlChar *)string_value);
587
588         g_free (string_value);
589 }
590
591 static void
592 save_item (const gchar *key, const gpointer *data, xmlNodePtr parent)
593 {       
594         xmlNodePtr xml_node;
595         const Item *item = (const Item *)data;
596         gchar *atime;
597
598         g_return_if_fail (key != NULL);
599         
600         if (item == NULL)
601                 return;
602                 
603         xml_node = xmlNewChild (parent, NULL, (const xmlChar *)"document", NULL);
604         
605         xmlSetProp (xml_node, (const xmlChar *)"uri", (const xmlChar *)key);
606
607         atime = g_strdup_printf ("%ld", item->atime);
608         xmlSetProp (xml_node, (const xmlChar *)"atime", (const xmlChar *)atime);
609         g_free (atime);
610
611         g_hash_table_foreach (item->values,
612                               (GHFunc)save_values, xml_node); 
613 }
614
615 static void
616 get_oldest (const gchar *key, const gpointer value, const gchar ** key_to_remove)
617 {
618         const Item *item = (const Item *)value;
619         
620         if (*key_to_remove == NULL)
621         {
622                 *key_to_remove = key;
623         }
624         else
625         {
626                 const Item *item_to_remove = 
627                         g_hash_table_lookup (ev_metadata_manager->items,
628                                              *key_to_remove);
629
630                 g_return_if_fail (item_to_remove != NULL);
631
632                 if (item->atime < item_to_remove->atime)
633                 {
634                         *key_to_remove = key;
635                 }
636         }       
637 }
638
639 static void
640 resize_items ()
641 {
642         while (g_hash_table_size (ev_metadata_manager->items) > MAX_ITEMS)
643         {
644                 gpointer key_to_remove = NULL;
645
646                 g_hash_table_foreach (ev_metadata_manager->items,
647                                       (GHFunc)get_oldest,
648                                       &key_to_remove);
649
650                 g_return_if_fail (key_to_remove != NULL);
651                 
652                 g_hash_table_remove (ev_metadata_manager->items,
653                                      key_to_remove);
654         }
655 }
656
657 static gboolean
658 ev_metadata_manager_save (gpointer data)
659 {       
660         xmlDocPtr  doc;
661         xmlNodePtr root;
662         gchar *file_name;
663
664         ev_metadata_manager->timeout_id = 0;
665
666         if (!ev_metadata_manager->modified)
667                 return FALSE;
668
669         resize_items ();
670                 
671         xmlIndentTreeOutput = TRUE;
672
673         doc = xmlNewDoc ((const xmlChar *)"1.0");
674         if (doc == NULL)
675                 return TRUE;
676
677         /* Create metadata root */
678         root = xmlNewDocNode (doc, NULL, (const xmlChar *)"metadata", NULL);
679         xmlDocSetRootElement (doc, root);
680
681         g_hash_table_foreach (ev_metadata_manager->items,
682                           (GHFunc)save_item, root);        
683
684         /* FIXME: lock file - Paolo */
685         file_name = g_build_filename (ev_dot_dir (), METADATA_FILE, NULL);
686         xmlSaveFormatFile (file_name, doc, 1);
687         g_free (file_name);
688         
689         xmlFreeDoc (doc); 
690
691         ev_metadata_manager->modified = FALSE;
692
693         return FALSE;
694 }
695
696 void ev_metadata_arm_timeout(void)
697 {
698         if (ev_metadata_manager->timeout_id)
699                 return;
700         ev_metadata_manager->timeout_id = 
701                 g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
702                                     2000, /* 2 sec */
703                                     (GSourceFunc)ev_metadata_manager_save,
704                                     NULL,
705                                     NULL);
706 }
707 void
708 ev_metadata_manager_set_int (const gchar *uri, const gchar *key, int value)
709 {
710         GValue val = { 0, };
711
712         g_value_init (&val, G_TYPE_INT);
713         g_value_set_int (&val, value);
714
715         ev_metadata_manager_set (uri, key, &val);
716
717         g_value_unset (&val);
718 }
719
720 void
721 ev_metadata_manager_set_double (const gchar *uri, const gchar *key, double value)
722 {
723         GValue val = { 0, };
724
725         g_value_init (&val, G_TYPE_DOUBLE);
726         g_value_set_double (&val, value);
727
728         ev_metadata_manager_set (uri, key, &val);
729
730         g_value_unset (&val);
731 }
732
733 void
734 ev_metadata_manager_set_string (const gchar *uri, const gchar *key, const gchar *value)
735 {
736         GValue val = { 0, };
737
738         g_value_init (&val, G_TYPE_STRING);
739         g_value_set_string (&val, value);
740
741         ev_metadata_manager_set (uri, key, &val);
742
743         g_value_unset (&val);
744 }
745
746 void
747 ev_metadata_manager_set_boolean (const gchar *uri, const gchar *key, gboolean value)
748 {
749         GValue val = { 0, };
750
751         g_value_init (&val, G_TYPE_BOOLEAN);
752         g_value_set_boolean (&val, value);
753
754         ev_metadata_manager_set (uri, key, &val);
755
756         g_value_unset (&val);
757 }