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