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