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