]> www.fi.muni.cz Git - evince.git/blob - shell/ev-metadata-manager.c
Use g_timeout_add_seconds instead of g_timeout_add when available.
[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;
82         
83         g_return_if_fail (data != NULL);
84
85         item = (Item *)data;
86
87         if (item->values != NULL)
88                 g_hash_table_destroy (item->values);
89
90         g_free (item);
91 }
92
93 /**
94  * ev_metadata_arm_timeout
95  *
96  * Setup a timeout for saving the metadata to disk.
97  */
98 static void
99 ev_metadata_arm_timeout(void)
100 {
101         if (ev_metadata_manager->timeout_id)
102                 return;
103 #if GLIB_CHECK_VERSION (2, 13, 5)
104         ev_metadata_manager->timeout_id =
105                 g_timeout_add_seconds_full (G_PRIORITY_DEFAULT_IDLE,
106                                             2,
107                                             (GSourceFunc)ev_metadata_manager_save,
108                                             NULL,
109                                             NULL);
110 #else
111         ev_metadata_manager->timeout_id = 
112                 g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
113                                     2000, /* 2 sec */
114                                     (GSourceFunc)ev_metadata_manager_save,
115                                     NULL,
116                                     NULL);
117 #endif
118 }
119
120 /**
121  * ev_metadata_manager_init:
122  *
123  * Creates an EvMetadataManager with default values.
124  *
125  *  values_loaded   ->  %FALSE.
126  *  timeout_id      ->  the id of the event source.
127  *  items           ->  a new full empty #GHashTable.
128  */
129 void
130 ev_metadata_manager_init (void)
131 {
132         ev_metadata_manager = g_new0 (EvMetadataManager, 1);
133
134         ev_metadata_manager->values_loaded = FALSE;
135
136         ev_metadata_manager->items = 
137                 g_hash_table_new_full (g_str_hash, 
138                                        g_str_equal, 
139                                        g_free,
140                                        item_free);
141 }
142
143 /* This function must be called before exiting ev */
144 void
145 ev_metadata_manager_shutdown (void)
146 {
147         if (ev_metadata_manager == NULL)
148                 return;
149
150         if (ev_metadata_manager->timeout_id) {
151                 g_source_remove (ev_metadata_manager->timeout_id);
152                 ev_metadata_manager->timeout_id = 0;
153                 ev_metadata_manager_save (NULL);
154         }
155
156         if (ev_metadata_manager->items != NULL)
157                 g_hash_table_destroy (ev_metadata_manager->items);
158
159         g_free (ev_metadata_manager);
160         ev_metadata_manager = NULL;
161 }
162
163 static void
164 value_free (gpointer data)
165 {
166         GValue *value = (GValue *)data;
167
168         g_value_unset (value);
169         g_free (value);
170 }
171
172 static GValue *
173 parse_value (xmlChar *value, xmlChar *type)
174 {
175         GType ret_type;
176         GValue *ret;
177
178         ret_type = g_type_from_name ((char *)type);
179         ret = g_new0 (GValue, 1);
180         g_value_init (ret, ret_type);
181
182         switch (ret_type) {
183                 case G_TYPE_STRING:
184                         g_value_set_string (ret, (char *)value);
185                         break;
186                 case G_TYPE_INT:
187                         g_value_set_int (ret, g_ascii_strtoull ((char *)value, NULL, 0));
188                         break;
189                 case G_TYPE_DOUBLE:
190                         g_value_set_double (ret, g_ascii_strtod ((char *)value, NULL));
191                         break;
192                 case G_TYPE_BOOLEAN:
193                         g_value_set_boolean (ret, g_ascii_strtoull ((char *)value, NULL, 0));
194                         break;
195         }
196
197         return ret;
198 }
199
200 static void
201 parseItem (xmlDocPtr doc, xmlNodePtr cur)
202 {
203         Item *item;
204         
205         xmlChar *uri;
206         xmlChar *atime;
207         
208         if (xmlStrcmp (cur->name, (const xmlChar *)"document") != 0)
209                         return;
210
211         uri = xmlGetProp (cur, (const xmlChar *)"uri");
212         if (uri == NULL)
213                 return;
214         
215         atime = xmlGetProp (cur, (const xmlChar *)"atime");
216         if (atime == NULL)
217         {
218                 xmlFree (uri);
219                 return;
220         }
221
222         item = g_new0 (Item, 1);
223
224         item->atime = g_ascii_strtoull((char*)atime, NULL, 0);
225         
226         item->values = g_hash_table_new_full (g_str_hash, 
227                                               g_str_equal, 
228                                               g_free, 
229                                               value_free);
230
231         cur = cur->xmlChildrenNode;
232                 
233         while (cur != NULL)
234         {
235                 if (xmlStrcmp (cur->name, (const xmlChar *)"entry") == 0)
236                 {
237                         xmlChar *key;
238                         xmlChar *xml_value;
239                         xmlChar *type;
240                         GValue  *value;
241                         
242                         key = xmlGetProp (cur, (const xmlChar *)"key");
243                         xml_value = xmlGetProp (cur, (const xmlChar *)"value");
244                         type = xmlGetProp (cur, (const xmlChar *)"type");
245                         value = parse_value (xml_value, type);
246
247                         if ((key != NULL) && (value != NULL))
248                                 g_hash_table_insert (item->values,
249                                                      xmlStrdup (key), 
250                                                      value);
251
252                         if (key != NULL)
253                                 xmlFree (key);
254                         if (type != NULL)
255                                 xmlFree (type);
256                         if (xml_value != NULL)
257                                 xmlFree (xml_value);
258                 }
259                 
260                 cur = cur->next;
261         }
262
263         g_hash_table_insert (ev_metadata_manager->items,
264                              xmlStrdup (uri),
265                              item);
266
267         xmlFree (uri);
268         xmlFree (atime);
269 }
270
271 static gboolean
272 load_values ()
273 {
274         xmlDocPtr doc;
275         xmlNodePtr cur;
276         gchar *file_name;
277
278         g_return_val_if_fail (ev_metadata_manager != NULL, FALSE);
279         g_return_val_if_fail (ev_metadata_manager->values_loaded == FALSE, FALSE);
280
281         ev_metadata_manager->values_loaded = TRUE;
282                 
283         xmlKeepBlanksDefault (0);
284
285         /* FIXME: file locking - Paolo */
286         file_name = g_build_filename (ev_dot_dir (), METADATA_FILE, NULL);
287         if (!g_file_test (file_name, G_FILE_TEST_EXISTS))
288         {
289                 g_free (file_name);
290                 return FALSE;
291         }
292
293         doc = xmlParseFile (file_name);
294         g_free (file_name);
295
296         if (doc == NULL)
297         {
298                 return FALSE;
299         }
300
301         cur = xmlDocGetRootElement (doc);
302         if (cur == NULL) 
303         {
304                 g_message ("The metadata file “%s” is empty", METADATA_FILE);
305                 xmlFreeDoc (doc);
306         
307                 return FALSE;
308         }
309
310         if (xmlStrcmp (cur->name, (const xmlChar *) "metadata")) 
311         {
312                 g_message ("File “%s” is of the wrong type", METADATA_FILE);
313                 xmlFreeDoc (doc);
314                 
315                 return FALSE;
316         }
317
318         cur = xmlDocGetRootElement (doc);
319         cur = cur->xmlChildrenNode;
320         
321         while (cur != NULL)
322         {
323                 parseItem (doc, cur);
324
325                 cur = cur->next;
326         }
327
328         xmlFreeDoc (doc);
329
330         return TRUE;
331 }
332
333 #define LAST_URI "last-used-value"
334
335 static gboolean
336 ev_metadata_manager_get_last (const gchar *key,
337                                  GValue      *value,
338                                  gboolean     ignore)
339 {
340         Item *item;
341         GValue *ret;
342
343         g_assert (ev_metadata_manager->values_loaded);
344         
345         if (ignore)
346                 return FALSE;
347
348         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
349                                             LAST_URI);
350
351         if (item == NULL)
352                 return FALSE;
353
354         item->atime = time (NULL);
355         
356         if (item->values == NULL)
357                 return FALSE;
358         
359         ret = (GValue *)g_hash_table_lookup (item->values, key);
360
361         if (ret != NULL) {
362                 g_value_init (value, G_VALUE_TYPE (ret));
363                 g_value_copy (ret, value);
364                 return TRUE;
365         }
366
367         return FALSE;
368 }
369
370 static void
371 ev_metadata_manager_set_last (const gchar *key,
372                               const GValue *value)
373 {
374         Item *item;
375         
376         g_assert (ev_metadata_manager->values_loaded);
377
378         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
379                                             LAST_URI);
380
381         if (item == NULL)
382         {
383                 item = g_new0 (Item, 1);
384
385                 g_hash_table_insert (ev_metadata_manager->items,
386                                      g_strdup (LAST_URI),
387                                      item);
388         }
389         
390         if (item->values == NULL)
391                  item->values = g_hash_table_new_full (g_str_hash, 
392                                                        g_str_equal, 
393                                                        g_free, 
394                                                        value_free);
395         if (value != NULL) {
396                 GValue *new;
397
398                 new = g_new0 (GValue, 1);
399                 g_value_init (new, G_VALUE_TYPE (value));
400                 g_value_copy (value, new);
401
402                 g_hash_table_insert (item->values,
403                                      g_strdup (key),
404                                      new);
405         } else {
406                 g_hash_table_remove (item->values,
407                                      key);
408         }
409
410         item->atime = time (NULL);
411         ev_metadata_arm_timeout ();
412         return;
413 }
414                                  
415 /**
416  * ev_metadata_manager_get:
417  * @uri: Uri to set data for, if @NULL, we return default value
418  * @key: Key to set uri
419  * @value: GValue struct filled up with value
420  * @ignore_last: if @TRUE, default value is ignored
421  * 
422  * Retrieve value for uri in metadata database
423  * 
424  * Returns: @TRUE if value was taken.
425  **/
426 gboolean
427 ev_metadata_manager_get (const gchar *uri,
428                          const gchar *key,
429                          GValue      *value, 
430                          gboolean     ignore_last)
431 {
432         Item *item;
433         GValue *ret;
434         
435         g_return_val_if_fail (key != NULL, FALSE);
436
437         if (ev_metadata_manager == NULL)
438                 return FALSE;
439
440         if (!ev_metadata_manager->values_loaded)
441         {
442                 gboolean res;
443
444                 res = load_values ();
445
446                 if (!res)
447                         return ev_metadata_manager_get_last (key, value, ignore_last);
448         }
449
450         if (uri == NULL)
451                 return ev_metadata_manager_get_last (key, value, ignore_last);
452
453         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
454                                             uri);
455
456         if (item == NULL)
457                 return ev_metadata_manager_get_last (key, value, ignore_last);
458
459         item->atime = time (NULL);
460         
461         if (item->values == NULL)
462                 return ev_metadata_manager_get_last (key, value, ignore_last);
463         
464         ret = (GValue *)g_hash_table_lookup (item->values, key);
465
466         if (ret != NULL) {
467                 g_value_init (value, G_VALUE_TYPE (ret));
468                 g_value_copy (ret, value);
469                 return TRUE;
470         }
471
472         return ev_metadata_manager_get_last (key, value, ignore_last);
473 }
474
475 /**
476  * ev_metadata_manager_set:
477  * @uri: Uri to set data for, if @NULL, we set default value
478  * @key: Key to set uri
479  * @value: GValue struct containing value
480  * 
481  * Set value for key in metadata database
482  **/
483 void
484 ev_metadata_manager_set (const gchar  *uri,
485                          const gchar  *key,
486                          const GValue *value)
487 {
488         Item *item;
489
490         g_return_if_fail (key != NULL);
491
492         if (ev_metadata_manager == NULL)
493                 return;
494
495         if (!ev_metadata_manager->values_loaded)
496         {
497                 gboolean res;
498
499                 res = load_values ();
500
501                 if (!res)
502                         return;
503         }
504
505         if (uri == NULL)
506         {
507                 ev_metadata_manager_set_last (key, value);
508                 return;
509         }
510
511         item = (Item *)g_hash_table_lookup (ev_metadata_manager->items,
512                                             uri);
513
514         if (item == NULL)
515         {
516                 item = g_new0 (Item, 1);
517
518                 g_hash_table_insert (ev_metadata_manager->items,
519                                      g_strdup (uri),
520                                      item);
521         }
522         
523         if (item->values == NULL)
524                  item->values = g_hash_table_new_full (g_str_hash, 
525                                                        g_str_equal, 
526                                                        g_free, 
527                                                        value_free);
528         if (value != NULL) {
529                 GValue *new;
530
531                 new = g_new0 (GValue, 1);
532                 g_value_init (new, G_VALUE_TYPE (value));
533                 g_value_copy (value, new);
534
535                 g_hash_table_insert (item->values,
536                                      g_strdup (key),
537                                      new);
538                 ev_metadata_manager_set_last (key, value);
539         } else {
540                 g_hash_table_remove (item->values,
541                                      key);
542         }
543
544         item->atime = time (NULL);
545
546         ev_metadata_arm_timeout ();
547 }
548
549 static void
550 save_values (const gchar *key, GValue *value, xmlNodePtr parent)
551 {
552         char *string_value;
553         xmlNodePtr xml_node;
554         
555         g_return_if_fail (key != NULL);
556         
557         if (value == NULL)
558                 return;
559                 
560         xml_node = xmlNewChild (parent, NULL, (const xmlChar *)"entry", NULL);
561
562         xmlSetProp (xml_node, (const xmlChar *)"key", (const xmlChar *)key);
563         xmlSetProp (xml_node,
564                     (const xmlChar *)"type",
565                     (const xmlChar *)g_type_name (G_VALUE_TYPE (value)));
566
567         switch (G_VALUE_TYPE (value)) {
568                 case G_TYPE_STRING:
569                         string_value = g_strdup (g_value_get_string (value));
570                         break;
571                 case G_TYPE_INT:
572                         string_value = g_strdup_printf ("%d", g_value_get_int (value));
573                         break;
574                 case G_TYPE_DOUBLE:
575                         {
576                                 gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
577                                 g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, g_value_get_double (value));
578                                 string_value = g_strdup_printf ("%s", buf);
579                         }
580                         break;
581                 case G_TYPE_BOOLEAN:
582                         string_value = g_strdup_printf ("%d", g_value_get_boolean (value));
583                         break;
584                 default:
585                         string_value = NULL;
586                         g_assert_not_reached ();
587         }
588
589         xmlSetProp (xml_node, (const xmlChar *)"value", (const xmlChar *)string_value);
590
591         g_free (string_value);
592 }
593
594 static void
595 save_item (const gchar *key, const gpointer *data, xmlNodePtr parent)
596 {       
597         xmlNodePtr xml_node;
598         const Item *item = (const Item *)data;
599         gchar *atime;
600
601         g_return_if_fail (key != NULL);
602         
603         if (item == NULL)
604                 return;
605                 
606         xml_node = xmlNewChild (parent, NULL, (const xmlChar *)"document", NULL);
607         
608         xmlSetProp (xml_node, (const xmlChar *)"uri", (const xmlChar *)key);
609
610         atime = g_strdup_printf ("%ld", item->atime);
611         xmlSetProp (xml_node, (const xmlChar *)"atime", (const xmlChar *)atime);
612         g_free (atime);
613
614         g_hash_table_foreach (item->values,
615                               (GHFunc)save_values, xml_node); 
616 }
617
618 static void
619 get_oldest (const gchar *key, const gpointer value, const gchar ** key_to_remove)
620 {
621         const Item *item = (const Item *)value;
622         
623         if (*key_to_remove == NULL)
624         {
625                 *key_to_remove = key;
626         }
627         else
628         {
629                 const Item *item_to_remove = 
630                         g_hash_table_lookup (ev_metadata_manager->items,
631                                              *key_to_remove);
632
633                 g_return_if_fail (item_to_remove != NULL);
634
635                 if (item->atime < item_to_remove->atime)
636                 {
637                         *key_to_remove = key;
638                 }
639         }       
640 }
641
642 static void
643 resize_items ()
644 {
645         while (g_hash_table_size (ev_metadata_manager->items) > MAX_ITEMS)
646         {
647                 gpointer key_to_remove = NULL;
648
649                 g_hash_table_foreach (ev_metadata_manager->items,
650                                       (GHFunc)get_oldest,
651                                       &key_to_remove);
652
653                 g_return_if_fail (key_to_remove != NULL);
654                 
655                 g_hash_table_remove (ev_metadata_manager->items,
656                                      key_to_remove);
657         }
658 }
659
660 static gboolean
661 ev_metadata_manager_save (gpointer data)
662 {       
663         xmlDocPtr  doc;
664         xmlNodePtr root;
665         gchar *file_name;
666
667         ev_metadata_manager->timeout_id = 0;
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         return FALSE;
692 }
693
694 void
695 ev_metadata_manager_set_int (const gchar *uri, const gchar *key, int value)
696 {
697         GValue val = { 0, };
698
699         g_value_init (&val, G_TYPE_INT);
700         g_value_set_int (&val, value);
701
702         ev_metadata_manager_set (uri, key, &val);
703
704         g_value_unset (&val);
705 }
706
707 void
708 ev_metadata_manager_set_double (const gchar *uri, const gchar *key, double value)
709 {
710         GValue val = { 0, };
711
712         g_value_init (&val, G_TYPE_DOUBLE);
713         g_value_set_double (&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_string (const gchar *uri, const gchar *key, const gchar *value)
722 {
723         GValue val = { 0, };
724
725         g_value_init (&val, G_TYPE_STRING);
726         g_value_set_string (&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_boolean (const gchar *uri, const gchar *key, gboolean value)
735 {
736         GValue val = { 0, };
737
738         g_value_init (&val, G_TYPE_BOOLEAN);
739         g_value_set_boolean (&val, value);
740
741         ev_metadata_manager_set (uri, key, &val);
742
743         g_value_unset (&val);
744 }