]> Pileus Git - ~andy/gtk/blob - gtk/gtkrecentmanager.c
Bug 535830 – wrong content type adding an item
[~andy/gtk] / gtk / gtkrecentmanager.c
1 /* GTK - The GIMP Toolkit
2  * gtkrecentmanager.c: a manager for the recently used resources
3  *
4  * Copyright (C) 2006 Emmanuele Bassi
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  */
20
21 #include "config.h"
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 #include <errno.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <glib.h>
32 #include <glib/gstdio.h>
33 #include <gio/gio.h>
34
35 #include "gtkrecentmanager.h"
36 #include "gtkintl.h"
37 #include "gtkstock.h"
38 #include "gtkicontheme.h"
39 #include "gtktypebuiltins.h"
40 #include "gtkprivate.h"
41 #include "gtkmarshalers.h"
42 #include "gtkalias.h"
43
44 #ifdef G_OS_UNIX
45 #define XDG_PREFIX _gtk_xdg
46 #include "xdgmime/xdgmime.h"
47 #endif
48
49 /* the file where we store the recently used items */
50 #define GTK_RECENTLY_USED_FILE  ".recently-used.xbel"
51
52 /* return all items by default */
53 #define DEFAULT_LIMIT   -1
54
55 /* keep in sync with xdgmime */
56 #define GTK_RECENT_DEFAULT_MIME "application/octet-stream"
57
58 typedef struct
59 {
60   gchar *name;
61   gchar *exec;
62   
63   guint count;
64   
65   time_t stamp;
66 } RecentAppInfo;
67
68 struct _GtkRecentInfo
69 {
70   gchar *uri;
71   
72   gchar *display_name;
73   gchar *description;
74   
75   time_t added;
76   time_t modified;
77   time_t visited;
78   
79   gchar *mime_type;
80   
81   GSList *applications;
82   GHashTable *apps_lookup;
83   
84   GSList *groups;
85   
86   gboolean is_private;
87   
88   GdkPixbuf *icon;
89   
90   gint ref_count;
91 };
92
93 #define GTK_RECENT_MANAGER_GET_PRIVATE(obj)     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RECENT_MANAGER, GtkRecentManagerPrivate))
94
95 struct _GtkRecentManagerPrivate
96 {
97   gchar *filename;
98
99   guint is_dirty : 1;
100   
101   gint limit;
102   gint size;
103
104   GBookmarkFile *recent_items;
105
106   GFileMonitor *monitor;
107 };
108
109 enum
110 {
111   PROP_0,
112
113   PROP_FILENAME,  
114   PROP_LIMIT,
115   PROP_SIZE
116 };
117
118 static void     gtk_recent_manager_dispose             (GObject           *object);
119 static void     gtk_recent_manager_finalize            (GObject           *object);
120 static void     gtk_recent_manager_set_property        (GObject           *object,
121                                                         guint              prop_id,
122                                                         const GValue      *value,
123                                                         GParamSpec        *pspec);
124 static void     gtk_recent_manager_get_property        (GObject           *object,
125                                                         guint              prop_id,
126                                                         GValue            *value,
127                                                         GParamSpec        *pspec);
128 static void     gtk_recent_manager_add_item_query_info (GObject           *source_object,
129                                                         GAsyncResult      *res,
130                                                         gpointer           user_data);
131 static void     gtk_recent_manager_monitor_changed     (GFileMonitor      *monitor,
132                                                         GFile             *file,
133                                                         GFile             *other_file,
134                                                         GFileMonitorEvent  event_type,
135                                                         gpointer           user_data);
136 static void     gtk_recent_manager_changed             (GtkRecentManager  *manager);
137 static void     gtk_recent_manager_real_changed        (GtkRecentManager  *manager);
138 static void     gtk_recent_manager_set_filename        (GtkRecentManager  *manager,
139                                                         const gchar       *filename);
140 static void     gtk_recent_manager_clamp_to_age        (GtkRecentManager  *manager,
141                                                         gint               age);
142
143
144 static void build_recent_items_list (GtkRecentManager  *manager);
145 static void purge_recent_items_list (GtkRecentManager  *manager,
146                                      GError           **error);
147
148 static RecentAppInfo *recent_app_info_new  (const gchar   *app_name);
149 static void           recent_app_info_free (RecentAppInfo *app_info);
150
151 static GtkRecentInfo *gtk_recent_info_new  (const gchar   *uri);
152 static void           gtk_recent_info_free (GtkRecentInfo *recent_info);
153
154 static guint signal_changed = 0;
155
156 static GtkRecentManager *recent_manager_singleton = NULL;
157
158 G_DEFINE_TYPE (GtkRecentManager, gtk_recent_manager, G_TYPE_OBJECT)
159
160 static void
161 filename_warning (const gchar *format, 
162                   const gchar *filename, 
163                   const gchar *message)
164 {
165   gchar *utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
166   g_warning (format, utf8 ? utf8 : "(invalid filename)", message);
167   g_free (utf8);
168 }
169
170 /* Test of haystack has the needle prefix, comparing case
171  * insensitive. haystack may be UTF-8, but needle must
172  * contain only lowercase ascii. */
173 static gboolean
174 has_case_prefix (const gchar *haystack, 
175                  const gchar *needle)
176 {
177   const gchar *h, *n;
178
179   /* Eat one character at a time. */
180   h = haystack;
181   n = needle;
182
183   while (*n && *h && *n == g_ascii_tolower (*h))
184     {
185       n++;
186       h++;
187     }
188
189   return *n == '\0';
190 }
191
192 GQuark
193 gtk_recent_manager_error_quark (void)
194 {
195   return g_quark_from_static_string ("gtk-recent-manager-error-quark");
196 }
197
198 static void
199 gtk_recent_manager_class_init (GtkRecentManagerClass *klass)
200 {
201   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
202   
203   gobject_class->set_property = gtk_recent_manager_set_property;
204   gobject_class->get_property = gtk_recent_manager_get_property;
205   gobject_class->dispose = gtk_recent_manager_dispose;
206   gobject_class->finalize = gtk_recent_manager_finalize;
207   
208   /**
209    * GtkRecentManager:filename
210    *
211    * The full path to the file to be used to store and read the recently
212    * used resources list
213    *
214    * Since: 2.10
215    */
216   g_object_class_install_property (gobject_class,
217                                    PROP_FILENAME,
218                                    g_param_spec_string ("filename",
219                                                         P_("Filename"),
220                                                         P_("The full path to the file to be used to store and read the list"),
221                                                         NULL,
222                                                         (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)));
223   /**
224    * GtkRecentManager:limit
225    *
226    * The maximum number of items to be returned by the
227    * gtk_recent_manager_get_items() function.
228    *
229    * Since: 2.10
230    */
231   g_object_class_install_property (gobject_class,
232                                    PROP_LIMIT,
233                                    g_param_spec_int ("limit",
234                                                      P_("Limit"),
235                                                      P_("The maximum number of items to be returned by gtk_recent_manager_get_items()"),
236                                                      -1,
237                                                      G_MAXINT,
238                                                      DEFAULT_LIMIT,
239                                                      G_PARAM_READWRITE));
240   /**
241    * GtkRecentManager:size
242    * 
243    * The size of the recently used resources list.
244    *
245    * Since: 2.10
246    */
247   g_object_class_install_property (gobject_class,
248                                    PROP_SIZE,
249                                    g_param_spec_int ("size",
250                                                      P_("Size"),
251                                                      P_("The size of the recently used resources list"),
252                                                      -1,
253                                                      G_MAXINT,
254                                                      0,
255                                                      G_PARAM_READABLE));
256   
257   /**
258    * GtkRecentManager::changed
259    * @recent_manager: the recent manager
260    *
261    * Emitted when the current recently used resources manager changes its
262    * contents.
263    *
264    * Since: 2.10
265    */
266   signal_changed =
267     g_signal_new (I_("changed"),
268                   G_TYPE_FROM_CLASS (klass),
269                   G_SIGNAL_RUN_FIRST,
270                   G_STRUCT_OFFSET (GtkRecentManagerClass, changed),
271                   NULL, NULL,
272                   g_cclosure_marshal_VOID__VOID,
273                   G_TYPE_NONE, 0);
274   
275   klass->changed = gtk_recent_manager_real_changed;
276   
277   g_type_class_add_private (klass, sizeof (GtkRecentManagerPrivate));
278 }
279
280 static void
281 gtk_recent_manager_init (GtkRecentManager *manager)
282 {
283   GtkRecentManagerPrivate *priv;
284
285   manager->priv = priv = GTK_RECENT_MANAGER_GET_PRIVATE (manager);
286   
287   priv->limit = DEFAULT_LIMIT;
288   priv->size = 0;
289
290   priv->filename = NULL;
291 }
292
293 static void
294 gtk_recent_manager_set_property (GObject               *object,
295                                  guint                  prop_id,
296                                  const GValue          *value,
297                                  GParamSpec            *pspec)
298 {
299   GtkRecentManager *recent_manager = GTK_RECENT_MANAGER (object);
300  
301   switch (prop_id)
302     {
303     case PROP_FILENAME:
304       gtk_recent_manager_set_filename (recent_manager, g_value_get_string (value));
305       break;      
306     case PROP_LIMIT:
307       gtk_recent_manager_set_limit (recent_manager, g_value_get_int (value));
308       break;
309     default:
310       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
311       break;
312     }
313 }
314
315 static void
316 gtk_recent_manager_get_property (GObject               *object,
317                                  guint                  prop_id,
318                                  GValue                *value,
319                                  GParamSpec            *pspec)
320 {
321   GtkRecentManager *recent_manager = GTK_RECENT_MANAGER (object);
322   
323   switch (prop_id)
324     {
325     case PROP_FILENAME:
326       g_value_set_string (value, recent_manager->priv->filename);
327       break;
328     case PROP_LIMIT:
329       g_value_set_int (value, recent_manager->priv->limit);
330       break;
331     case PROP_SIZE:
332       g_value_set_int (value, recent_manager->priv->size);
333       break;
334     default:
335       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
336       break;
337     }
338
339
340 static void
341 gtk_recent_manager_dispose (GObject *object)
342 {
343   GtkRecentManager *manager = GTK_RECENT_MANAGER (object);
344   GtkRecentManagerPrivate *priv = manager->priv;
345
346   if (priv->monitor)
347     {
348       g_signal_handlers_disconnect_by_func (priv->monitor,
349                                             G_CALLBACK (gtk_recent_manager_monitor_changed),
350                                             manager);
351       g_object_unref (priv->monitor);
352       priv->monitor = NULL;
353     }
354
355   G_OBJECT_CLASS (gtk_recent_manager_parent_class)->dispose (object);
356 }
357
358 static void
359 gtk_recent_manager_finalize (GObject *object)
360 {
361   GtkRecentManager *manager = GTK_RECENT_MANAGER (object);
362   GtkRecentManagerPrivate *priv = manager->priv;
363
364   g_free (priv->filename);
365   
366   if (priv->recent_items)
367     g_bookmark_file_free (priv->recent_items);
368
369   G_OBJECT_CLASS (gtk_recent_manager_parent_class)->finalize (object);
370 }
371
372 static void
373 gtk_recent_manager_real_changed (GtkRecentManager *manager)
374 {
375   GtkRecentManagerPrivate *priv = manager->priv;
376
377   g_object_freeze_notify (G_OBJECT (manager));
378
379   if (priv->is_dirty)
380     {
381       GError *write_error;
382       
383       /* we are marked as dirty, so we dump the content of our
384        * recently used items list
385        */
386       g_assert (priv->filename != NULL);
387
388       if (!priv->recent_items)
389         {
390           /* if no container object has been defined, we create a new
391            * empty container, and dump it
392            */
393           priv->recent_items = g_bookmark_file_new ();
394           priv->size = 0;
395         }
396       else
397         {
398           GtkSettings *settings = gtk_settings_get_default ();
399           gint age = 30;
400
401           g_object_get (G_OBJECT (settings), "gtk-recent-files-max-age", &age, NULL);
402           if (age > 0)
403             gtk_recent_manager_clamp_to_age (manager, age);
404           else if (age == 0)
405             {
406               g_bookmark_file_free (priv->recent_items);
407               priv->recent_items = NULL;
408
409               priv->recent_items = g_bookmark_file_new ();
410             }
411         }
412
413       write_error = NULL;
414       g_bookmark_file_to_file (priv->recent_items, priv->filename, &write_error);
415       if (write_error)
416         {
417           filename_warning ("Attempting to store changes into `%s', "
418                             "but failed: %s",
419                             priv->filename,
420                             write_error->message);
421           g_error_free (write_error);
422         }
423
424       /* mark us as clean */
425       priv->is_dirty = FALSE;
426     }
427   else
428     {
429       /* we are not marked as dirty, so we have been called
430        * because the recently used resources file has been
431        * changed (and not from us).
432        */
433       build_recent_items_list (manager);
434     }
435
436   g_object_thaw_notify (G_OBJECT (manager));
437 }
438
439 static void
440 gtk_recent_manager_monitor_changed (GFileMonitor      *monitor,
441                                     GFile             *file,
442                                     GFile             *other_file,
443                                     GFileMonitorEvent  event_type,
444                                     gpointer           user_data)
445 {
446   GtkRecentManager *manager = user_data;
447
448   switch (event_type)
449     {
450     case G_FILE_MONITOR_EVENT_CHANGED:
451     case G_FILE_MONITOR_EVENT_CREATED:
452       gtk_recent_manager_changed (manager);
453       break;
454
455     case G_FILE_MONITOR_EVENT_DELETED:
456       break;
457
458     default:
459       break;
460     }
461 }
462
463 static void
464 gtk_recent_manager_set_filename (GtkRecentManager *manager,
465                                  const gchar      *filename)
466 {
467   GtkRecentManagerPrivate *priv;
468   GFile *file;
469   GError *error;
470   
471   g_assert (GTK_IS_RECENT_MANAGER (manager));
472
473   priv = manager->priv;
474
475   /* if a filename is already set and filename is not NULL, then copy
476    * it and reset the monitor; otherwise, if it's NULL we're being
477    * called from the finalization sequence, so we simply disconnect the
478    * monitoring and return.
479    *
480    * if no filename is set and filename is NULL, use the default.
481    */
482   if (priv->filename)
483     {
484       g_free (priv->filename);
485
486       if (priv->monitor)
487         {
488           g_signal_handlers_disconnect_by_func (priv->monitor,
489                                                 G_CALLBACK (gtk_recent_manager_monitor_changed),
490                                                 manager);
491           g_object_unref (priv->monitor);
492           priv->monitor = NULL;
493         }
494
495       if (!filename || *filename == '\0')
496         return;
497       else
498         priv->filename = g_strdup (filename);
499     }
500   else
501     {
502       if (!filename || *filename == '\0')
503         priv->filename = g_build_filename (g_get_home_dir (),
504                                            GTK_RECENTLY_USED_FILE,
505                                            NULL);
506       else
507         priv->filename = g_strdup (filename);
508     }
509
510   g_assert (priv->filename != NULL);
511   file = g_file_new_for_path (priv->filename);
512
513   error = NULL;
514   priv->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
515   if (error)
516     {
517       filename_warning ("Unable to monitor `%s': %s\n"
518                         "The GtkRecentManager will not update its contents "
519                         "if the file is changed from other instances",
520                         priv->filename,
521                         error->message);
522       g_error_free (error);
523     }
524   else
525     g_signal_connect (priv->monitor, "changed",
526                       G_CALLBACK (gtk_recent_manager_monitor_changed),
527                       manager);
528
529   g_object_unref (file);
530
531   priv->is_dirty = FALSE;
532   build_recent_items_list (manager);
533 }
534
535 /* reads the recently used resources file and builds the items list.
536  * we keep the items list inside the parser object, and build the
537  * RecentInfo object only on user's demand to avoid useless replication.
538  * this function resets the dirty bit of the manager.
539  */
540 static void
541 build_recent_items_list (GtkRecentManager *manager)
542 {
543   GtkRecentManagerPrivate *priv = manager->priv;
544   GError *read_error;
545   gint size;
546
547   g_assert (priv->filename != NULL);
548   
549   if (!priv->recent_items)
550     {
551       priv->recent_items = g_bookmark_file_new ();
552       priv->size = 0;
553     }
554
555   /* the file exists, and it's valid (we hope); if not, destroy the container
556    * object and hope for a better result when the next "changed" signal is
557    * fired. */
558   read_error = NULL;
559   g_bookmark_file_load_from_file (priv->recent_items, priv->filename, &read_error);
560   if (read_error)
561     {
562       /* if the file does not exist we just wait for the first write
563        * operation on this recent manager instance, to avoid creating
564        * empty files and leading to spurious file system events (Sabayon
565        * will not be happy about those)
566        */
567       if (read_error->domain == G_FILE_ERROR &&
568           read_error->code != G_FILE_ERROR_NOENT)
569         filename_warning ("Attempting to read the recently used resources "
570                           "file at `%s', but the parser failed: %s.",
571                           priv->filename,
572                           read_error->message);
573
574       g_bookmark_file_free (priv->recent_items);
575       priv->recent_items = NULL;
576
577       g_error_free (read_error);
578     }
579   else
580     {
581       size = g_bookmark_file_get_size (priv->recent_items);
582       if (priv->size != size)
583         {
584           priv->size = size;
585
586           g_object_notify (G_OBJECT (manager), "size");
587         }
588     }
589
590   priv->is_dirty = FALSE;
591 }
592
593
594 /********************
595  * GtkRecentManager *
596  ********************/
597
598
599 /**
600  * gtk_recent_manager_new:
601  * 
602  * Creates a new recent manager object.  Recent manager objects are used to
603  * handle the list of recently used resources.  A #GtkRecentManager object
604  * monitors the recently used resources list, and emits the "changed" signal
605  * each time something inside the list changes.
606  *
607  * #GtkRecentManager objects are expensive: be sure to create them only when
608  * needed. You should use gtk_recent_manager_get_default() instead.
609  *
610  * Return value: A newly created #GtkRecentManager object.
611  *
612  * Since: 2.10
613  */
614 GtkRecentManager *
615 gtk_recent_manager_new (void)
616 {
617   return g_object_new (GTK_TYPE_RECENT_MANAGER, NULL);
618 }
619
620 /**
621  * gtk_recent_manager_get_default:
622  *
623  * Gets a unique instance of #GtkRecentManager, that you can share
624  * in your application without caring about memory management. The
625  * returned instance will be freed when you application terminates.
626  *
627  * Return value: A unique #GtkRecentManager. Do not ref or unref it.
628  *
629  * Since: 2.10
630  */
631 GtkRecentManager *
632 gtk_recent_manager_get_default (void)
633 {
634   if (G_UNLIKELY (!recent_manager_singleton))
635     recent_manager_singleton = gtk_recent_manager_new ();
636
637   return recent_manager_singleton;
638 }
639
640 /**
641  * gtk_recent_manager_get_for_screen:
642  * @screen: a #GdkScreen
643  *
644  * Gets the recent manager object associated with @screen; if this
645  * function has not previously been called for the given screen,
646  * a new recent manager object will be created and associated with
647  * the screen. Recent manager objects are fairly expensive to create,
648  * so using this function is usually a better choice than calling 
649  * gtk_recent_manager_new() and setting the screen yourself; by using
650  * this function a single recent manager object will be shared between
651  * users.
652  *
653  * Return value: A unique #GtkRecentManager associated with the given
654  *   screen. This recent manager is associated to the with the screen
655  *   and can be used as long as the screen is open. Do not ref or
656  *   unref it.
657  *
658  * Deprecated: 2.12: This function has been deprecated and should
659  *   not be used in newly written code. Calling this function is
660  *   equivalent to calling gtk_recent_manager_get_default().
661  *
662  * Since: 2.10
663  */
664 GtkRecentManager *
665 gtk_recent_manager_get_for_screen (GdkScreen *screen)
666 {
667   return gtk_recent_manager_get_default ();
668 }
669
670 /**
671  * gtk_recent_manager_set_screen:
672  * @manager: a #GtkRecentManager
673  * @screen: a #GdkScreen
674  *
675  * Sets the screen for a recent manager; the screen is used to
676  * track the user's currently configured recently used documents
677  * storage.
678  * 
679  * Since: 2.10
680  *
681  * Deprecated: 2.12: This function has been deprecated and should
682  *   not be used in newly written code. Calling this function has
683  *   no effect.
684  */
685 void
686 gtk_recent_manager_set_screen (GtkRecentManager *manager,
687                                GdkScreen        *screen)
688 {
689
690 }
691
692 /**
693  * gtk_recent_manager_set_limit:
694  * @manager: a #GtkRecentManager
695  * @limit: the maximum number of items to return, or -1.
696  *
697  * Sets the maximum number of item that the gtk_recent_manager_get_items()
698  * function should return.  If @limit is set to -1, then return all the
699  * items.
700  *
701  * Since: 2.10
702  */
703 void
704 gtk_recent_manager_set_limit (GtkRecentManager *manager,
705                               gint              limit)
706 {
707   GtkRecentManagerPrivate *priv;
708   
709   g_return_if_fail (GTK_IS_RECENT_MANAGER (manager));
710   
711   priv = manager->priv;
712   priv->limit = limit;
713 }
714
715 /**
716  * gtk_recent_manager_get_limit:
717  * @manager: a #GtkRecentManager
718  *
719  * Gets the maximum number of items that the gtk_recent_manager_get_items()
720  * function should return.
721  *
722  * Return value: the number of items to return, or -1 for every item.
723  *
724  * Since: 2.10
725  */
726 gint
727 gtk_recent_manager_get_limit (GtkRecentManager *manager)
728 {
729   GtkRecentManagerPrivate *priv;
730   
731   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), DEFAULT_LIMIT);
732   
733   priv = manager->priv;
734   return priv->limit;
735 }
736
737 static void
738 gtk_recent_manager_add_item_query_info (GObject      *source_object,
739                                         GAsyncResult *res,
740                                         gpointer      user_data)
741 {
742   GFile *file = G_FILE (source_object);
743   GtkRecentManager *manager = user_data;
744   GtkRecentData recent_data;
745   GFileInfo *file_info;
746   gchar *uri;
747   GError *error;
748
749   uri = g_file_get_uri (file);
750
751   error = NULL;
752   file_info = g_file_query_info_finish (file, res, &error);
753   if (error)
754     {
755       g_warning ("Unable to retrieve the file info for `%s': %s",
756                  uri,
757                  error->message);
758       g_error_free (error);
759       goto out;
760     }
761
762   recent_data.display_name = NULL;
763   recent_data.description = NULL;
764
765   if (file_info)
766     {
767       gchar *content_type;
768
769       content_type = g_file_info_get_attribute_as_string (file_info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
770
771       if (G_LIKELY (content_type))
772         recent_data.mime_type = g_content_type_get_mime_type (content_type);
773       else
774         recent_data.mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME);
775
776       g_free (content_type);
777       g_object_unref (file_info);
778     }
779   else
780     recent_data.mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME);
781
782   recent_data.app_name = g_strdup (g_get_application_name ());
783   recent_data.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL);
784   recent_data.groups = NULL;
785   recent_data.is_private = FALSE;
786
787   /* Ignore return value, this can't fail anyway since all required
788    * fields are set */
789   gtk_recent_manager_add_full (manager, uri, &recent_data);
790
791   manager->priv->is_dirty = TRUE;
792   gtk_recent_manager_changed (manager);
793
794   g_free (recent_data.mime_type);
795   g_free (recent_data.app_name);
796   g_free (recent_data.app_exec);
797
798 out:
799   g_object_unref (manager);
800   g_free (uri);
801 }
802
803 /**
804  * gtk_recent_manager_add_item:
805  * @manager: a #GtkRecentManager
806  * @uri: a valid URI
807  *
808  * Adds a new resource, pointed by @uri, into the recently used
809  * resources list.
810  *
811  * This function automatically retrieves some of the needed
812  * metadata and setting other metadata to common default values; it
813  * then feeds the data to gtk_recent_manager_add_full().
814  * 
815  * See gtk_recent_manager_add_full() if you want to explicitly
816  * define the metadata for the resource pointed by @uri.
817  *
818  * Return value: %TRUE if the new item was successfully added
819  *   to the recently used resources list
820  *
821  * Since: 2.10
822  */
823 gboolean
824 gtk_recent_manager_add_item (GtkRecentManager  *manager,
825                              const gchar       *uri)
826 {
827   GFile* file;
828   
829   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE);
830   g_return_val_if_fail (uri != NULL, FALSE);
831
832   file = g_file_new_for_uri (uri);
833
834   g_file_query_info_async (file,
835                            G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
836                            G_PRIORITY_DEFAULT,
837                            G_FILE_QUERY_INFO_NONE,
838                            NULL,
839                            gtk_recent_manager_add_item_query_info,
840                            g_object_ref (manager));
841
842   g_object_unref (file);
843
844   return TRUE;
845 }
846
847 /**
848  * gtk_recent_manager_add_full:
849  * @manager: a #GtkRecentManager
850  * @uri: a valid URI
851  * @recent_data: metadata of the resource
852  *
853  * Adds a new resource, pointed by @uri, into the recently used
854  * resources list, using the metadata specified inside the #GtkRecentData
855  * structure passed in @recent_data.
856  *
857  * The passed URI will be used to identify this resource inside the
858  * list.
859  *
860  * In order to register the new recently used resource, metadata about
861  * the resource must be passed as well as the URI; the metadata is
862  * stored in a #GtkRecentData structure, which must contain the MIME
863  * type of the resource pointed by the URI; the name of the application
864  * that is registering the item, and a command line to be used when
865  * launching the item.
866  *
867  * Optionally, a #GtkRecentData structure might contain a UTF-8 string
868  * to be used when viewing the item instead of the last component of the
869  * URI; a short description of the item; whether the item should be
870  * considered private - that is, should be displayed only by the
871  * applications that have registered it.
872  *
873  * Return value: %TRUE if the new item was successfully added to the
874  * recently used resources list, %FALSE otherwise.
875  *
876  * Since: 2.10
877  */
878 gboolean
879 gtk_recent_manager_add_full (GtkRecentManager     *manager,
880                              const gchar          *uri,
881                              const GtkRecentData  *data)
882 {
883   GtkRecentManagerPrivate *priv;
884   
885   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE);
886   g_return_val_if_fail (uri != NULL, FALSE);
887   g_return_val_if_fail (data != NULL, FALSE);
888
889   /* sanity checks */
890   if ((data->display_name) &&
891       (!g_utf8_validate (data->display_name, -1, NULL)))
892     {
893       g_warning ("Attempting to add `%s' to the list of recently used "
894                  "resources, but the display name is not a valid UTF-8 "
895                  "encoded string",
896                  uri);
897       return FALSE;
898     }
899   
900   if ((data->description) &&
901       (!g_utf8_validate (data->description, -1, NULL)))
902     {
903       g_warning ("Attempting to add `%s' to the list of recently used "
904                  "resources, but the description is not a valid UTF-8 "
905                  "encoded string",
906                  uri);
907       return FALSE;
908     }
909
910  
911   if (!data->mime_type)
912     {
913       g_warning ("Attempting to add `%s' to the list of recently used "
914                  "resources, but not MIME type was defined",
915                  uri);
916       return FALSE;
917     }
918   
919   if (!data->app_name)
920     {
921       g_warning ("Attempting to add `%s' to the list of recently used "
922                  "resources, but no name of the application that is "
923                  "registering it was defined",
924                  uri);
925       return FALSE;
926     }
927   
928   if (!data->app_exec)
929     {
930       g_warning ("Attempting to add `%s' to the list of recently used "
931                  "resources, but no command line for the application "
932                  "that is registering it was defined",
933                  uri);
934       return FALSE;
935     }
936   
937   priv = manager->priv;
938
939   if (!priv->recent_items)
940     {
941       priv->recent_items = g_bookmark_file_new ();
942       priv->size = 0;
943     }
944
945   if (data->display_name)  
946     g_bookmark_file_set_title (priv->recent_items, uri, data->display_name);
947   
948   if (data->description)
949     g_bookmark_file_set_description (priv->recent_items, uri, data->description);
950
951   g_bookmark_file_set_mime_type (priv->recent_items, uri, data->mime_type);
952   
953   if (data->groups && data->groups[0] != '\0')
954     {
955       gint j;
956       
957       for (j = 0; (data->groups)[j] != NULL; j++)
958         g_bookmark_file_add_group (priv->recent_items, uri, (data->groups)[j]);
959     }
960   
961   /* register the application; this will take care of updating the
962    * registration count and time in case the application has
963    * already registered the same document inside the list
964    */
965   g_bookmark_file_add_application (priv->recent_items, uri,
966                                    data->app_name,
967                                    data->app_exec);
968   
969   g_bookmark_file_set_is_private (priv->recent_items, uri,
970                                   data->is_private);
971   
972   /* mark us as dirty, so that when emitting the "changed" signal we
973    * will dump our changes
974    */
975   priv->is_dirty = TRUE;
976   
977   gtk_recent_manager_changed (manager);
978   
979   return TRUE;
980 }
981
982 /**
983  * gtk_recent_manager_remove_item:
984  * @manager: a #GtkRecentManager
985  * @uri: the URI of the item you wish to remove
986  * @error: return location for a #GError, or %NULL
987  *
988  * Removes a resource pointed by @uri from the recently used resources
989  * list handled by a recent manager.
990  *
991  * Return value: %TRUE if the item pointed by @uri has been successfully
992  *   removed by the recently used resources list, and %FALSE otherwise.
993  *
994  * Since: 2.10
995  */
996 gboolean
997 gtk_recent_manager_remove_item (GtkRecentManager  *manager,
998                                 const gchar       *uri,
999                                 GError           **error)
1000 {
1001   GtkRecentManagerPrivate *priv;
1002   GError *remove_error = NULL;
1003
1004   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE);
1005   g_return_val_if_fail (uri != NULL, FALSE);
1006   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1007   
1008   priv = manager->priv;
1009   
1010   if (!priv->recent_items)
1011     {
1012       priv->recent_items = g_bookmark_file_new ();
1013       priv->size = 0;
1014
1015       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
1016                    GTK_RECENT_MANAGER_ERROR_NOT_FOUND,
1017                    _("Unable to find an item with URI '%s'"),
1018                    uri);
1019
1020       return FALSE;
1021     }
1022
1023   g_bookmark_file_remove_item (priv->recent_items, uri, &remove_error);
1024   if (remove_error)
1025     {
1026       g_propagate_error (error, remove_error);
1027       
1028       return FALSE;
1029     }
1030
1031   priv->is_dirty = TRUE;
1032
1033   gtk_recent_manager_changed (manager);
1034   
1035   return TRUE;
1036 }
1037
1038 /**
1039  * gtk_recent_manager_has_item:
1040  * @manager: a #GtkRecentManager
1041  * @uri: a URI
1042  *
1043  * Checks whether there is a recently used resource registered
1044  * with @uri inside the recent manager.
1045  *
1046  * Return value: %TRUE if the resource was found, %FALSE otherwise.
1047  *
1048  * Since: 2.10
1049  */
1050 gboolean
1051 gtk_recent_manager_has_item (GtkRecentManager *manager,
1052                              const gchar      *uri)
1053 {
1054   GtkRecentManagerPrivate *priv;
1055
1056   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE);
1057   g_return_val_if_fail (uri != NULL, FALSE);
1058
1059   priv = manager->priv;
1060   g_return_val_if_fail (priv->recent_items != NULL, FALSE);
1061
1062   return g_bookmark_file_has_item (priv->recent_items, uri);
1063 }
1064
1065 static void
1066 build_recent_info (GBookmarkFile  *bookmarks,
1067                    GtkRecentInfo  *info)
1068 {
1069   gchar **apps, **groups;
1070   gsize apps_len, groups_len, i;
1071
1072   g_assert (bookmarks != NULL);
1073   g_assert (info != NULL);
1074   
1075   info->display_name = g_bookmark_file_get_title (bookmarks, info->uri, NULL);
1076   info->description = g_bookmark_file_get_description (bookmarks, info->uri, NULL);
1077   info->mime_type = g_bookmark_file_get_mime_type (bookmarks, info->uri, NULL);
1078     
1079   info->is_private = g_bookmark_file_get_is_private (bookmarks, info->uri, NULL);
1080   
1081   info->added = g_bookmark_file_get_added (bookmarks, info->uri, NULL);
1082   info->modified = g_bookmark_file_get_modified (bookmarks, info->uri, NULL);
1083   info->visited = g_bookmark_file_get_visited (bookmarks, info->uri, NULL);
1084   
1085   groups = g_bookmark_file_get_groups (bookmarks, info->uri, &groups_len, NULL);
1086   for (i = 0; i < groups_len; i++)
1087     {
1088       gchar *group_name = g_strdup (groups[i]);
1089       
1090       info->groups = g_slist_append (info->groups, group_name);
1091     }
1092
1093   g_strfreev (groups);
1094   
1095   apps = g_bookmark_file_get_applications (bookmarks, info->uri, &apps_len, NULL);
1096   for (i = 0; i < apps_len; i++)
1097     {
1098       gchar *app_name, *app_exec;
1099       guint count;
1100       time_t stamp;
1101       RecentAppInfo *app_info;
1102       gboolean res;
1103       
1104       app_name = apps[i];
1105       
1106       res = g_bookmark_file_get_app_info (bookmarks, info->uri, app_name,
1107                                           &app_exec,
1108                                           &count,
1109                                           &stamp,
1110                                           NULL);
1111       if (!res)
1112         continue;
1113       
1114       app_info = recent_app_info_new (app_name);
1115       app_info->exec = app_exec;
1116       app_info->count = count;
1117       app_info->stamp = stamp;
1118       
1119       info->applications = g_slist_prepend (info->applications, app_info);
1120       g_hash_table_replace (info->apps_lookup, app_info->name, app_info);
1121     }
1122   
1123   g_strfreev (apps);
1124 }
1125
1126 /**
1127  * gtk_recent_manager_lookup_item:
1128  * @manager: a #GtkRecentManager
1129  * @uri: a URI
1130  * @error: a return location for a #GError, or %NULL
1131  *
1132  * Searches for a URI inside the recently used resources list, and
1133  * returns a structure containing informations about the resource
1134  * like its MIME type, or its display name.
1135  *
1136  * Return value: a #GtkRecentInfo structure containing information
1137  *   about the resource pointed by @uri, or %NULL if the URI was
1138  *   not registered in the recently used resources list.  Free with
1139  *   gtk_recent_info_unref().
1140  *
1141  * Since: 2.10
1142  */
1143 GtkRecentInfo *
1144 gtk_recent_manager_lookup_item (GtkRecentManager  *manager,
1145                                 const gchar       *uri,
1146                                 GError           **error)
1147 {
1148   GtkRecentManagerPrivate *priv;
1149   GtkRecentInfo *info = NULL;
1150   
1151   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), NULL);
1152   g_return_val_if_fail (uri != NULL, NULL);
1153   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1154   
1155   priv = manager->priv;
1156   if (!priv->recent_items)
1157     {
1158       priv->recent_items = g_bookmark_file_new ();
1159       priv->size = 0;
1160
1161       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
1162                    GTK_RECENT_MANAGER_ERROR_NOT_FOUND,
1163                    _("Unable to find an item with URI '%s'"),
1164                    uri);
1165
1166       return NULL;
1167     }
1168   
1169   if (!g_bookmark_file_has_item (priv->recent_items, uri))
1170     {
1171       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
1172                    GTK_RECENT_MANAGER_ERROR_NOT_FOUND,
1173                    _("Unable to find an item with URI '%s'"),
1174                    uri);
1175       return NULL;
1176     }
1177   
1178   info = gtk_recent_info_new (uri);
1179   g_return_val_if_fail (info != NULL, NULL);
1180   
1181   /* fill the RecentInfo structure with the data retrieved by our
1182    * parser object from the storage file 
1183    */
1184   build_recent_info (priv->recent_items, info);
1185
1186   return info;
1187 }
1188
1189 /**
1190  * gtk_recent_manager_move_item:
1191  * @manager: a #GtkRecentManager
1192  * @uri: the URI of a recently used resource
1193  * @new_uri: the new URI of the recently used resource, or %NULL to
1194  *    remove the item pointed by @uri in the list
1195  * @error: a return location for a #GError, or %NULL
1196  *
1197  * Changes the location of a recently used resource from @uri to @new_uri.
1198  * 
1199  * Please note that this function will not affect the resource pointed
1200  * by the URIs, but only the URI used in the recently used resources list.
1201  *
1202  * Return value: %TRUE on success.
1203  *
1204  * Since: 2.10
1205  */ 
1206 gboolean
1207 gtk_recent_manager_move_item (GtkRecentManager  *recent_manager,
1208                               const gchar       *uri,
1209                               const gchar       *new_uri,
1210                               GError           **error)
1211 {
1212   GtkRecentManagerPrivate *priv;
1213   GError *move_error;
1214   gboolean res;
1215   
1216   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), FALSE);
1217   g_return_val_if_fail (uri != NULL, FALSE);
1218   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1219   
1220   priv = recent_manager->priv;
1221
1222   if (!g_bookmark_file_has_item (priv->recent_items, uri))
1223     {
1224       g_set_error (error, GTK_RECENT_MANAGER_ERROR,
1225                    GTK_RECENT_MANAGER_ERROR_NOT_FOUND,
1226                    _("Unable to find an item with URI '%s'"),
1227                    uri);
1228       return FALSE;
1229     }
1230   
1231   move_error = NULL;
1232   res = g_bookmark_file_move_item (priv->recent_items,
1233                                    uri, new_uri,
1234                                    &move_error);
1235   if (move_error)
1236     {
1237       g_propagate_error (error, move_error);
1238       return FALSE;
1239     }
1240   
1241   priv->is_dirty = TRUE;
1242
1243   gtk_recent_manager_changed (recent_manager);
1244   
1245   return TRUE;
1246 }
1247
1248 /**
1249  * gtk_recent_manager_get_items:
1250  * @manager: a #GtkRecentManager
1251  *
1252  * Gets the list of recently used resources.
1253  *
1254  * Return value: a list of newly allocated #GtkRecentInfo objects. Use
1255  *   gtk_recent_info_unref() on each item inside the list, and then
1256  *   free the list itself using g_list_free().
1257  *
1258  * Since: 2.10
1259  */
1260 GList *
1261 gtk_recent_manager_get_items (GtkRecentManager *manager)
1262 {
1263   GtkRecentManagerPrivate *priv;
1264   GList *retval = NULL;
1265   gchar **uris;
1266   gsize uris_len, i;
1267   
1268   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), NULL);
1269   
1270   priv = manager->priv;
1271   if (!priv->recent_items)
1272     return NULL;
1273
1274   if (priv->limit == 0)
1275     return NULL;
1276   
1277   uris = g_bookmark_file_get_uris (priv->recent_items, &uris_len);
1278   for (i = 0; i < uris_len; i++)
1279     {
1280       GtkRecentInfo *info;
1281       
1282       if (priv->limit != -1 && i == priv->limit)
1283         break;
1284       
1285       info = gtk_recent_info_new (uris[i]);
1286       build_recent_info (priv->recent_items, info);
1287       
1288       retval = g_list_prepend (retval, info);
1289     }
1290   
1291   g_strfreev (uris);
1292   
1293   return retval;
1294 }
1295
1296 static void
1297 purge_recent_items_list (GtkRecentManager  *manager,
1298                          GError           **error)
1299 {
1300   GtkRecentManagerPrivate *priv = manager->priv;
1301
1302   if (!priv->recent_items)
1303     return;
1304   
1305   g_bookmark_file_free (priv->recent_items);
1306   priv->recent_items = NULL;
1307       
1308   priv->recent_items = g_bookmark_file_new ();
1309   priv->size = 0;
1310   priv->is_dirty = TRUE;
1311       
1312   /* emit the changed signal, to ensure that the purge is written */
1313   gtk_recent_manager_changed (manager);
1314 }
1315
1316 /**
1317  * gtk_recent_manager_purge_items:
1318  * @manager: a #GtkRecentManager
1319  * @error: a return location for a #GError, or %NULL
1320  *
1321  * Purges every item from the recently used resources list.
1322  *
1323  * Return value: the number of items that have been removed from the
1324  *   recently used resources list.
1325  *
1326  * Since: 2.10
1327  */
1328 gint
1329 gtk_recent_manager_purge_items (GtkRecentManager  *manager,
1330                                 GError           **error)
1331 {
1332   GtkRecentManagerPrivate *priv;
1333   gint count, purged;
1334   
1335   g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), -1);
1336
1337   priv = manager->priv;
1338   if (!priv->recent_items)
1339     return 0;
1340   
1341   count = g_bookmark_file_get_size (priv->recent_items);
1342   if (!count)
1343     return 0;
1344   
1345   purge_recent_items_list (manager, error);
1346   
1347   purged = count - g_bookmark_file_get_size (priv->recent_items);
1348
1349   return purged;
1350 }
1351
1352 static void
1353 gtk_recent_manager_changed (GtkRecentManager *recent_manager)
1354 {
1355   g_signal_emit (recent_manager, signal_changed, 0);
1356 }
1357
1358 static void
1359 gtk_recent_manager_clamp_to_age (GtkRecentManager *manager,
1360                                  gint              age)
1361 {
1362   GtkRecentManagerPrivate *priv = manager->priv;
1363   gchar **uris;
1364   gsize n_uris, i;
1365   time_t now;
1366
1367   if (G_UNLIKELY (!priv->recent_items))
1368     return;
1369
1370   now = time (NULL);
1371
1372   uris = g_bookmark_file_get_uris (priv->recent_items, &n_uris);
1373
1374   for (i = 0; i < n_uris; i++)
1375     {
1376       const gchar *uri = uris[i];
1377       time_t modified;
1378       gint item_age;
1379
1380       modified = g_bookmark_file_get_modified (priv->recent_items, uri, NULL);
1381       item_age = (gint) ((now - modified) / (60 * 60 * 24));
1382       if (item_age > age)
1383         g_bookmark_file_remove_item (priv->recent_items, uri, NULL);
1384     }
1385
1386   g_strfreev (uris);
1387 }
1388
1389 /*****************
1390  * GtkRecentInfo *
1391  *****************/
1392  
1393 GType
1394 gtk_recent_info_get_type (void)
1395 {
1396   static GType info_type = 0;
1397   
1398   if (!info_type)
1399     info_type = g_boxed_type_register_static (I_("GtkRecentInfo"),
1400                                               (GBoxedCopyFunc) gtk_recent_info_ref,
1401                                               (GBoxedFreeFunc) gtk_recent_info_unref);
1402   return info_type;
1403 }
1404
1405 static GtkRecentInfo *
1406 gtk_recent_info_new (const gchar *uri)
1407 {
1408   GtkRecentInfo *info;
1409
1410   g_assert (uri != NULL);
1411
1412   info = g_new0 (GtkRecentInfo, 1);
1413   info->uri = g_strdup (uri);
1414   
1415   info->applications = NULL;
1416   info->apps_lookup = g_hash_table_new (g_str_hash, g_str_equal);
1417   
1418   info->groups = NULL;
1419   
1420   info->ref_count = 1;
1421
1422   return info;
1423 }
1424
1425 static void
1426 gtk_recent_info_free (GtkRecentInfo *recent_info)
1427 {
1428   if (!recent_info)
1429     return;
1430
1431   g_free (recent_info->uri);
1432   g_free (recent_info->display_name);
1433   g_free (recent_info->description);
1434   g_free (recent_info->mime_type);
1435   
1436   if (recent_info->applications)
1437     {
1438       g_slist_foreach (recent_info->applications,
1439                        (GFunc) recent_app_info_free,
1440                        NULL);
1441       g_slist_free (recent_info->applications);
1442       
1443       recent_info->applications = NULL;
1444     }
1445   
1446   if (recent_info->apps_lookup)
1447     g_hash_table_destroy (recent_info->apps_lookup);
1448
1449   if (recent_info->groups)
1450     {
1451       g_slist_foreach (recent_info->groups,
1452                        (GFunc) g_free,
1453                        NULL);
1454       g_slist_free (recent_info->groups);
1455
1456       recent_info->groups = NULL;
1457     }
1458   
1459   if (recent_info->icon)
1460     g_object_unref (recent_info->icon);
1461
1462   g_free (recent_info);
1463 }
1464
1465 /**
1466  * gtk_recent_info_ref:
1467  * @info: a #GtkRecentInfo
1468  *
1469  * Increases the reference count of @recent_info by one.
1470  *
1471  * Return value: the recent info object with its reference count increased
1472  *   by one.
1473  *
1474  * Since: 2.10
1475  */
1476 GtkRecentInfo *
1477 gtk_recent_info_ref (GtkRecentInfo *info)
1478 {
1479   g_return_val_if_fail (info != NULL, NULL);
1480   g_return_val_if_fail (info->ref_count > 0, NULL);
1481   
1482   info->ref_count += 1;
1483     
1484   return info;
1485 }
1486
1487 /**
1488  * gtk_recent_info_unref:
1489  * @info: a #GtkRecentInfo
1490  *
1491  * Decreases the reference count of @info by one.  If the reference
1492  * count reaches zero, @info is deallocated, and the memory freed.
1493  *
1494  * Since: 2.10
1495  */
1496 void
1497 gtk_recent_info_unref (GtkRecentInfo *info)
1498 {
1499   g_return_if_fail (info != NULL);
1500   g_return_if_fail (info->ref_count > 0);
1501
1502   info->ref_count -= 1;
1503   
1504   if (info->ref_count == 0)
1505     gtk_recent_info_free (info);
1506 }
1507
1508 /**
1509  * gtk_recent_info_get_uri:
1510  * @info: a #GtkRecentInfo
1511  *
1512  * Gets the URI of the resource.
1513  *
1514  * Return value: the URI of the resource.  The returned string is
1515  *   owned by the recent manager, and should not be freed.
1516  *
1517  * Since: 2.10
1518  */
1519 G_CONST_RETURN gchar *
1520 gtk_recent_info_get_uri (GtkRecentInfo *info)
1521 {
1522   g_return_val_if_fail (info != NULL, NULL);
1523   
1524   return info->uri;
1525 }
1526
1527 /**
1528  * gtk_recent_info_get_display_name:
1529  * @info: a #GtkRecentInfo
1530  *
1531  * Gets the name of the resource.  If none has been defined, the basename
1532  * of the resource is obtained.
1533  *
1534  * Return value: the display name of the resource.  The returned string
1535  *   is owned by the recent manager, and should not be freed.
1536  *
1537  * Since: 2.10
1538  */
1539 G_CONST_RETURN gchar *
1540 gtk_recent_info_get_display_name (GtkRecentInfo *info)
1541 {
1542   g_return_val_if_fail (info != NULL, NULL);
1543   
1544   if (!info->display_name)
1545     info->display_name = gtk_recent_info_get_short_name (info);
1546   
1547   return info->display_name;
1548 }
1549
1550 /**
1551  * gtk_recent_info_get_description:
1552  * @info: a #GtkRecentInfo
1553  *
1554  * Gets the (short) description of the resource.
1555  *
1556  * Return value: the description of the resource.  The returned string
1557  *   is owned by the recent manager, and should not be freed.
1558  *
1559  * Since: 2.10
1560  **/
1561 G_CONST_RETURN gchar *
1562 gtk_recent_info_get_description (GtkRecentInfo *info)
1563 {
1564   g_return_val_if_fail (info != NULL, NULL);
1565   
1566   return info->description;
1567 }
1568
1569 /**
1570  * gtk_recent_info_get_mime_type:
1571  * @info: a #GtkRecentInfo
1572  *
1573  * Gets the MIME type of the resource.
1574  *
1575  * Return value: the MIME type of the resource.  The returned string
1576  *   is owned by the recent manager, and should not be freed.
1577  *
1578  * Since: 2.10
1579  */
1580 G_CONST_RETURN gchar *
1581 gtk_recent_info_get_mime_type (GtkRecentInfo *info)
1582 {
1583   g_return_val_if_fail (info != NULL, NULL);
1584   
1585   if (!info->mime_type)
1586     info->mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME);
1587   
1588   return info->mime_type;
1589 }
1590
1591 /**
1592  * gtk_recent_info_get_added:
1593  * @info: a #GtkRecentInfo
1594  *
1595  * Gets the timestamp (seconds from system's Epoch) when the resource
1596  * was added to the recently used resources list.
1597  *
1598  * Return value: the number of seconds elapsed from system's Epoch when
1599  *   the resource was added to the list, or -1 on failure.
1600  *
1601  * Since: 2.10
1602  */
1603 time_t
1604 gtk_recent_info_get_added (GtkRecentInfo *info)
1605 {
1606   g_return_val_if_fail (info != NULL, (time_t) -1);
1607   
1608   return info->added;
1609 }
1610
1611 /**
1612  * gtk_recent_info_get_modified:
1613  * @info: a #GtkRecentInfo
1614  *
1615  * Gets the timestamp (seconds from system's Epoch) when the resource
1616  * was last modified.
1617  *
1618  * Return value: the number of seconds elapsed from system's Epoch when
1619  *   the resource was last modified, or -1 on failure.
1620  *
1621  * Since: 2.10
1622  */
1623 time_t
1624 gtk_recent_info_get_modified (GtkRecentInfo *info)
1625 {
1626   g_return_val_if_fail (info != NULL, (time_t) -1);
1627   
1628   return info->modified;
1629 }
1630
1631 /**
1632  * gtk_recent_info_get_visited:
1633  * @info: a #GtkRecentInfo
1634  *
1635  * Gets the timestamp (seconds from system's Epoch) when the resource
1636  * was last visited.
1637  *
1638  * Return value: the number of seconds elapsed from system's Epoch when
1639  *   the resource was last visited, or -1 on failure.
1640  *
1641  * Since: 2.10
1642  */
1643 time_t
1644 gtk_recent_info_get_visited (GtkRecentInfo *info)
1645 {
1646   g_return_val_if_fail (info != NULL, (time_t) -1);
1647   
1648   return info->visited;
1649 }
1650
1651 /**
1652  * gtk_recent_info_get_private_hint:
1653  * @info: a #GtkRecentInfo
1654  *
1655  * Gets the value of the "private" flag.  Resources in the recently used
1656  * list that have this flag set to %TRUE should only be displayed by the
1657  * applications that have registered them.
1658  *
1659  * Return value: %TRUE if the private flag was found, %FALSE otherwise.
1660  *
1661  * Since: 2.10
1662  */
1663 gboolean
1664 gtk_recent_info_get_private_hint (GtkRecentInfo *info)
1665 {
1666   g_return_val_if_fail (info != NULL, FALSE);
1667   
1668   return info->is_private;
1669 }
1670
1671
1672 static RecentAppInfo *
1673 recent_app_info_new (const gchar *app_name)
1674 {
1675   RecentAppInfo *app_info;
1676
1677   g_assert (app_name != NULL);
1678   
1679   app_info = g_new0 (RecentAppInfo, 1);
1680   app_info->name = g_strdup (app_name);
1681   app_info->exec = NULL;
1682   app_info->count = 1;
1683   app_info->stamp = time (NULL);
1684   
1685   return app_info;
1686 }
1687
1688 static void
1689 recent_app_info_free (RecentAppInfo *app_info)
1690 {
1691   if (!app_info)
1692     return;
1693   
1694   g_free (app_info->name);
1695   
1696   g_free (app_info->exec);
1697   
1698   g_free (app_info);
1699 }
1700
1701 /**
1702  * gtk_recent_info_get_application_info:
1703  * @info: a #GtkRecentInfo
1704  * @app_name: the name of the application that has registered this item
1705  * @app_exec: return location for the string containing the command line
1706  * @count: return location for the number of times this item was registered
1707  * @time_: return location for the timestamp this item was last registered
1708  *    for this application
1709  *
1710  * Gets the data regarding the application that has registered the resource
1711  * pointed by @info.
1712  *
1713  * If the command line contains any escape characters defined inside the
1714  * storage specification, they will be expanded.
1715  *
1716  * Return value: %TRUE if an application with @app_name has registered this
1717  *   resource inside the recently used list, or %FALSE otherwise.  You should
1718  *   free the returned command line using g_free().
1719  *
1720  * Since: 2.10
1721  */
1722 gboolean
1723 gtk_recent_info_get_application_info (GtkRecentInfo  *info,
1724                                       const gchar    *app_name,
1725                                       gchar         **app_exec,
1726                                       guint          *count,
1727                                       time_t         *time_)
1728 {
1729   RecentAppInfo *ai;
1730   
1731   g_return_val_if_fail (info != NULL, FALSE);
1732   g_return_val_if_fail (app_name != NULL, FALSE);
1733   
1734   ai = (RecentAppInfo *) g_hash_table_lookup (info->apps_lookup,
1735                                               app_name);
1736   if (!ai)
1737     {
1738       g_warning ("No registered application with name '%s' "
1739                  "for item with URI '%s' found",
1740                  app_name,
1741                  info->uri);
1742       return FALSE;
1743     }
1744   
1745   if (app_exec)
1746     *app_exec = ai->exec;
1747   
1748   if (count)
1749     *count = ai->count;
1750   
1751   if (time_)
1752     *time_ = ai->stamp;
1753
1754   return TRUE;
1755 }
1756
1757 /**
1758  * gtk_recent_info_get_applications:
1759  * @info: a #GtkRecentInfo
1760  * @length: return location for the length of the returned list, or %NULL
1761  *
1762  * Retrieves the list of applications that have registered this resource.
1763  *
1764  * Return value: a newly allocated %NULL-terminated array of strings.
1765  *   Use g_strfreev() to free it.
1766  *
1767  * Since: 2.10
1768  */                           
1769 gchar **
1770 gtk_recent_info_get_applications (GtkRecentInfo *info,
1771                                   gsize         *length)
1772 {
1773   GSList *l;
1774   gchar **retval;
1775   gsize n_apps, i;
1776   
1777   g_return_val_if_fail (info != NULL, NULL);
1778   
1779   if (!info->applications)
1780     {
1781       if (length)
1782         *length = 0;
1783       
1784       return NULL;    
1785     }
1786   
1787   n_apps = g_slist_length (info->applications);
1788   
1789   retval = g_new0 (gchar *, n_apps + 1);
1790   
1791   for (l = info->applications, i = 0;
1792        l != NULL;
1793        l = l->next)
1794     {
1795       RecentAppInfo *ai = (RecentAppInfo *) l->data;
1796       
1797       g_assert (ai != NULL);
1798       
1799       retval[i++] = g_strdup (ai->name);
1800     }
1801   retval[i] = NULL;
1802   
1803   if (length)
1804     *length = i;
1805   
1806   return retval;
1807 }
1808
1809 /**
1810  * gtk_recent_info_has_application:
1811  * @info: a #GtkRecentInfo
1812  * @app_name: a string containing an application name
1813  *
1814  * Checks whether an application registered this resource using @app_name.
1815  *
1816  * Return value: %TRUE if an application with name @app_name was found,
1817  *   %FALSE otherwise.
1818  *
1819  * Since: 2.10
1820  */
1821 gboolean
1822 gtk_recent_info_has_application (GtkRecentInfo *info,
1823                                  const gchar   *app_name)
1824 {
1825   g_return_val_if_fail (info != NULL, FALSE);
1826   g_return_val_if_fail (app_name != NULL, FALSE);
1827   
1828   return (NULL != g_hash_table_lookup (info->apps_lookup, app_name));
1829 }
1830
1831 /**
1832  * gtk_recent_info_last_application:
1833  * @info: a #GtkRecentInfo
1834  *
1835  * Gets the name of the last application that have registered the
1836  * recently used resource represented by @info.
1837  *
1838  * Return value: an application name.  Use g_free() to free it.
1839  *
1840  * Since: 2.10
1841  */
1842 gchar *
1843 gtk_recent_info_last_application (GtkRecentInfo  *info)
1844 {
1845   GSList *l;
1846   time_t last_stamp = (time_t) -1;
1847   gchar *name = NULL;
1848   
1849   g_return_val_if_fail (info != NULL, NULL);
1850   
1851   for (l = info->applications; l != NULL; l = l->next)
1852     {
1853       RecentAppInfo *ai = (RecentAppInfo *) l->data;
1854       
1855       if (ai->stamp > last_stamp)
1856         {
1857           name = ai->name;
1858           last_stamp = ai->stamp;
1859         }
1860     }
1861   
1862   return g_strdup (name);
1863 }
1864
1865 static GdkPixbuf *
1866 get_icon_for_mime_type (const char *mime_type,
1867                         gint        pixel_size)
1868 {
1869   GtkIconTheme *icon_theme;
1870   const char *separator;
1871   GString *icon_name;
1872   GdkPixbuf *pixbuf;
1873
1874   separator = strchr (mime_type, '/');
1875   if (!separator)
1876     return NULL;
1877
1878   icon_theme = gtk_icon_theme_get_default ();
1879
1880   /* try with the three icon name variants for MIME types */
1881
1882   /* canonicalize MIME type: foo/x-bar -> foo-x-bar */
1883   icon_name = g_string_new (NULL);
1884   g_string_append_len (icon_name, mime_type, separator - mime_type);
1885   g_string_append_c (icon_name, '-');
1886   g_string_append (icon_name, separator + 1);
1887   pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str,
1888                                      pixel_size,
1889                                      0,
1890                                      NULL);
1891   g_string_free (icon_name, TRUE);
1892   if (pixbuf)
1893     return pixbuf;
1894
1895   /* canonicalize MIME type, and prepend "gnome-mime-" */
1896   icon_name = g_string_new ("gnome-mime-");
1897   g_string_append_len (icon_name, mime_type, separator - mime_type);
1898   g_string_append_c (icon_name, '-');
1899   g_string_append (icon_name, separator + 1);
1900   pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str,
1901                                      pixel_size,
1902                                      0,
1903                                      NULL);
1904   g_string_free (icon_name, TRUE);
1905   if (pixbuf)
1906     return pixbuf;
1907
1908   /* try the MIME family icon */
1909   icon_name = g_string_new ("gnome-mime-");
1910   g_string_append_len (icon_name, mime_type, separator - mime_type);
1911   pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str,
1912                                      pixel_size,
1913                                      0,
1914                                      NULL);
1915   g_string_free (icon_name, TRUE);
1916
1917   return pixbuf;
1918 }
1919
1920 static GdkPixbuf *
1921 get_icon_fallback (const gchar *icon_name,
1922                    gint         size)
1923 {
1924   GtkIconTheme *icon_theme;
1925   GdkPixbuf *retval;
1926
1927   icon_theme = gtk_icon_theme_get_default ();
1928   
1929   retval = gtk_icon_theme_load_icon (icon_theme, icon_name,
1930                                      size,
1931                                      GTK_ICON_LOOKUP_USE_BUILTIN,
1932                                      NULL);
1933   g_assert (retval != NULL);
1934   
1935   return retval; 
1936 }
1937
1938 /**
1939  * gtk_recent_info_get_icon:
1940  * @info: a #GtkRecentInfo
1941  * @size: the size of the icon in pixels
1942  *
1943  * Retrieves the icon of size @size associated to the resource MIME type.
1944  *
1945  * Return value: a #GdkPixbuf containing the icon, or %NULL. Use
1946  *   g_object_unref() when finished using the icon.
1947  *
1948  * Since: 2.10
1949  */
1950 GdkPixbuf *
1951 gtk_recent_info_get_icon (GtkRecentInfo *info,
1952                           gint           size)
1953 {
1954   GdkPixbuf *retval = NULL;
1955   
1956   g_return_val_if_fail (info != NULL, NULL);
1957   
1958   if (info->mime_type)
1959     retval = get_icon_for_mime_type (info->mime_type, size);
1960
1961   /* this function should never fail */  
1962   if (!retval)
1963     {
1964       if (info->mime_type &&
1965           strcmp (info->mime_type, "x-directory/normal") == 0)
1966         retval = get_icon_fallback (GTK_STOCK_DIRECTORY, size);
1967       else
1968         retval = get_icon_fallback (GTK_STOCK_FILE, size);
1969     }
1970   
1971   return retval;
1972 }
1973
1974 /**
1975  * gtk_recent_info_is_local:
1976  * @info: a #GtkRecentInfo
1977  *
1978  * Checks whether the resource is local or not by looking at the
1979  * scheme of its URI.
1980  *
1981  * Return value: %TRUE if the resource is local.
1982  *
1983  * Since: 2.10
1984  */
1985 gboolean
1986 gtk_recent_info_is_local (GtkRecentInfo *info)
1987 {
1988   g_return_val_if_fail (info != NULL, FALSE);
1989   
1990   return has_case_prefix (info->uri, "file:/");
1991 }
1992
1993 /**
1994  * gtk_recent_info_exists:
1995  * @info: a #GtkRecentInfo
1996  *
1997  * Checks whether the resource pointed by @info still exists.  At
1998  * the moment this check is done only on resources pointing to local files.
1999  *
2000  * Return value: %TRUE if the resource exists
2001  *
2002  * Since: 2.10
2003  */
2004 gboolean
2005 gtk_recent_info_exists (GtkRecentInfo *info)
2006 {
2007   gchar *filename;
2008   struct stat stat_buf;
2009   gboolean retval = FALSE;
2010   
2011   g_return_val_if_fail (info != NULL, FALSE);
2012   
2013   /* we guarantee only local resources */
2014   if (!gtk_recent_info_is_local (info))
2015     return FALSE;
2016   
2017   filename = g_filename_from_uri (info->uri, NULL, NULL);
2018   if (filename)
2019     {
2020       if (stat (filename, &stat_buf) == 0)
2021         retval = TRUE;
2022      
2023       g_free (filename);
2024     }
2025   
2026   return retval;
2027 }
2028
2029 /**
2030  * gtk_recent_info_match:
2031  * @info_a: a #GtkRecentInfo
2032  * @info_b: a #GtkRecentInfo
2033  *
2034  * Checks whether two #GtkRecentInfo structures point to the same
2035  * resource.
2036  *
2037  * Return value: %TRUE if both #GtkRecentInfo structures point to se same
2038  *   resource, %FALSE otherwise.
2039  *
2040  * Since: 2.10
2041  */
2042 gboolean
2043 gtk_recent_info_match (GtkRecentInfo *info_a,
2044                        GtkRecentInfo *info_b)
2045 {
2046   g_return_val_if_fail (info_a != NULL, FALSE);
2047   g_return_val_if_fail (info_b != NULL, FALSE);
2048   
2049   return (0 == strcmp (info_a->uri, info_b->uri));
2050 }
2051
2052 /* taken from gnome-vfs-uri.c */
2053 static const gchar *
2054 get_method_string (const gchar  *substring, 
2055                    gchar       **method_string)
2056 {
2057   const gchar *p;
2058   char *method;
2059         
2060   for (p = substring;
2061        g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.';
2062        p++)
2063     ;
2064
2065   if (*p == ':'
2066 #ifdef G_OS_WIN32
2067                 &&
2068       !(p == substring + 1 && g_ascii_isalpha (*substring))
2069 #endif
2070                                                            )
2071     {
2072       /* Found toplevel method specification.  */
2073       method = g_strndup (substring, p - substring);
2074       *method_string = g_ascii_strdown (method, -1);
2075       g_free (method);
2076       p++;
2077     }
2078   else
2079     {
2080       *method_string = g_strdup ("file");
2081       p = substring;
2082     }
2083   
2084   return p;
2085 }
2086
2087 /* Stolen from gnome_vfs_make_valid_utf8() */
2088 static char *
2089 make_valid_utf8 (const char *name)
2090 {
2091   GString *string;
2092   const char *remainder, *invalid;
2093   int remaining_bytes, valid_bytes;
2094
2095   string = NULL;
2096   remainder = name;
2097   remaining_bytes = name ? strlen (name) : 0;
2098
2099   while (remaining_bytes != 0)
2100     {
2101       if (g_utf8_validate (remainder, remaining_bytes, &invalid))
2102         break;
2103       
2104       valid_bytes = invalid - remainder;
2105       
2106       if (string == NULL)
2107         string = g_string_sized_new (remaining_bytes);
2108       
2109       g_string_append_len (string, remainder, valid_bytes);
2110       g_string_append_c (string, '?');
2111       
2112       remaining_bytes -= valid_bytes + 1;
2113       remainder = invalid + 1;
2114     }
2115   
2116   if (string == NULL)
2117     return g_strdup (name);
2118
2119   g_string_append (string, remainder);
2120   g_assert (g_utf8_validate (string->str, -1, NULL));
2121
2122   return g_string_free (string, FALSE);
2123 }
2124
2125 static gchar *
2126 get_uri_shortname_for_display (const gchar *uri)
2127 {
2128   gchar *name = NULL;
2129   gboolean validated = FALSE;
2130
2131   if (has_case_prefix (uri, "file:/"))
2132     {
2133       gchar *local_file;
2134       
2135       local_file = g_filename_from_uri (uri, NULL, NULL);
2136       
2137       if (local_file)
2138         {
2139           name = g_filename_display_basename (local_file);
2140           validated = TRUE;
2141         }
2142                 
2143       g_free (local_file);
2144     } 
2145   
2146   if (!name)
2147     {
2148       gchar *method;
2149       gchar *local_file;
2150       const gchar *rest;
2151       
2152       rest = get_method_string (uri, &method);
2153       local_file = g_filename_display_basename (rest);
2154       
2155       name = g_strconcat (method, ": ", local_file, NULL);
2156       
2157       g_free (local_file);
2158       g_free (method);
2159     }
2160   
2161   g_assert (name != NULL);
2162   
2163   if (!validated && !g_utf8_validate (name, -1, NULL))
2164     {
2165       gchar *utf8_name;
2166       
2167       utf8_name = make_valid_utf8 (name);
2168       g_free (name);
2169       
2170       name = utf8_name;
2171     }
2172
2173   return name;
2174 }
2175
2176 /**
2177  * gtk_recent_info_get_short_name:
2178  * @info: an #GtkRecentInfo
2179  *
2180  * Computes a valid UTF-8 string that can be used as the name of the item in a
2181  * menu or list.  For example, calling this function on an item that refers to
2182  * "file:///foo/bar.txt" will yield "bar.txt".
2183  *
2184  * Return value: A newly-allocated string in UTF-8 encoding; free it with
2185  *   g_free().
2186  *
2187  * Since: 2.10
2188  */
2189 gchar *
2190 gtk_recent_info_get_short_name (GtkRecentInfo *info)
2191 {
2192   gchar *short_name;
2193
2194   g_return_val_if_fail (info != NULL, NULL);
2195
2196   if (info->uri == NULL)
2197     return NULL;
2198
2199   short_name = get_uri_shortname_for_display (info->uri);
2200
2201   return short_name;
2202 }
2203
2204 /**
2205  * gtk_recent_info_get_uri_display:
2206  * @info: a #GtkRecentInfo
2207  *
2208  * Gets a displayable version of the resource's URI.  If the resource
2209  * is local, it returns a local path; if the resource is not local,
2210  * it returns the UTF-8 encoded content of gtk_recent_info_get_uri().
2211  *
2212  * Return value: a newly allocated UTF-8 string containing the
2213  *   resource's URI or %NULL. Use g_free() when done using it.
2214  *
2215  * Since: 2.10
2216  */
2217 gchar *
2218 gtk_recent_info_get_uri_display (GtkRecentInfo *info)
2219 {
2220   gchar *retval;
2221   
2222   g_return_val_if_fail (info != NULL, NULL);
2223
2224   retval = NULL;
2225   if (gtk_recent_info_is_local (info))
2226     {
2227       gchar *filename;
2228
2229       filename = g_filename_from_uri (info->uri, NULL, NULL);
2230       if (!filename)
2231         return NULL;
2232       
2233       retval = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
2234       g_free (filename);
2235     }
2236   else
2237     {
2238       retval = make_valid_utf8 (info->uri);
2239     }
2240
2241   return retval;
2242 }
2243
2244 /**
2245  * gtk_recent_info_get_age:
2246  * @info: a #GtkRecentInfo
2247  *
2248  * Gets the number of days elapsed since the last update of the resource
2249  * pointed by @info.
2250  *
2251  * Return value: a positive integer containing the number of days elapsed
2252  *   since the time this resource was last modified.  
2253  *
2254  * Since: 2.10
2255  */
2256 gint
2257 gtk_recent_info_get_age (GtkRecentInfo *info)
2258 {
2259   time_t now, delta;
2260   gint retval;
2261
2262   g_return_val_if_fail (info != NULL, -1);
2263
2264   now = time (NULL);
2265   
2266   delta = now - info->modified;
2267   
2268   retval = (gint) (delta / (60 * 60 * 24));
2269   
2270   return retval;
2271 }
2272
2273 /**
2274  * gtk_recent_info_get_groups:
2275  * @info: a #GtkRecentInfo
2276  * @length: return location for the number of groups returned, or %NULL
2277  *
2278  * Returns all groups registered for the recently used item @info.  The
2279  * array of returned group names will be %NULL terminated, so length might
2280  * optionally be %NULL.
2281  *
2282  * Return value: a newly allocated %NULL terminated array of strings.  Use
2283  *   g_strfreev() to free it.
2284  *
2285  * Since: 2.10
2286  */
2287 gchar **
2288 gtk_recent_info_get_groups (GtkRecentInfo *info,
2289                             gsize         *length)
2290 {
2291   GSList *l;
2292   gchar **retval;
2293   gsize n_groups, i;
2294   
2295   g_return_val_if_fail (info != NULL, NULL);
2296   
2297   if (!info->groups)
2298     {
2299       if (length)
2300         *length = 0;
2301       
2302       return NULL;
2303     }
2304   
2305   n_groups = g_slist_length (info->groups);
2306   
2307   retval = g_new0 (gchar *, n_groups + 1);
2308   
2309   for (l = info->groups, i = 0;
2310        l != NULL;
2311        l = l->next)
2312     {
2313       gchar *group_name = (gchar *) l->data;
2314       
2315       g_assert (group_name != NULL);
2316       
2317       retval[i++] = g_strdup (group_name);
2318     }
2319   retval[i] = NULL;
2320   
2321   if (length)
2322     *length = i;
2323   
2324   return retval;
2325 }
2326
2327 /**
2328  * gtk_recent_info_has_group:
2329  * @info: a #GtkRecentInfo
2330  * @group_name: name of a group
2331  *
2332  * Checks whether @group_name appears inside the groups registered for the
2333  * recently used item @info.
2334  *
2335  * Return value: %TRUE if the group was found.
2336  *
2337  * Since: 2.10
2338  */
2339 gboolean
2340 gtk_recent_info_has_group (GtkRecentInfo *info,
2341                            const gchar   *group_name)
2342 {
2343   GSList *l;
2344   
2345   g_return_val_if_fail (info != NULL, FALSE);
2346   g_return_val_if_fail (group_name != NULL, FALSE);
2347
2348   if (!info->groups)
2349     return FALSE;
2350
2351   for (l = info->groups; l != NULL; l = l->next)
2352     {
2353       gchar *g = (gchar *) l->data;
2354
2355       if (strcmp (g, group_name) == 0)
2356         return TRUE;
2357     }
2358
2359   return FALSE;
2360 }
2361
2362 /*
2363  * _gtk_recent_manager_sync:
2364  * 
2365  * Private function for synchronising the recent manager singleton.
2366  */
2367 void
2368 _gtk_recent_manager_sync (void)
2369 {
2370   if (recent_manager_singleton)
2371     {
2372       /* force a dump of the contents of the recent manager singleton */
2373       recent_manager_singleton->priv->is_dirty = TRUE;
2374       gtk_recent_manager_real_changed (recent_manager_singleton);
2375     }
2376 }
2377
2378 #define __GTK_RECENT_MANAGER_C__
2379 #include "gtkaliasdef.c"