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