]> Pileus Git - ~andy/gtk/blob - gtk/gtkrecentmanager.c
Tons of transfer annotations
[~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  * #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
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 G_DEFINE_BOXED_TYPE (GtkRecentInfo, gtk_recent_info,
1371                      gtk_recent_info_ref,
1372                      gtk_recent_info_unref)
1373
1374 static GtkRecentInfo *
1375 gtk_recent_info_new (const gchar *uri)
1376 {
1377   GtkRecentInfo *info;
1378
1379   g_assert (uri != NULL);
1380
1381   info = g_new0 (GtkRecentInfo, 1);
1382   info->uri = g_strdup (uri);
1383   
1384   info->applications = NULL;
1385   info->apps_lookup = g_hash_table_new (g_str_hash, g_str_equal);
1386   
1387   info->groups = NULL;
1388   
1389   info->ref_count = 1;
1390
1391   return info;
1392 }
1393
1394 static void
1395 gtk_recent_info_free (GtkRecentInfo *recent_info)
1396 {
1397   if (!recent_info)
1398     return;
1399
1400   g_free (recent_info->uri);
1401   g_free (recent_info->display_name);
1402   g_free (recent_info->description);
1403   g_free (recent_info->mime_type);
1404   
1405   if (recent_info->applications)
1406     {
1407       g_slist_foreach (recent_info->applications,
1408                        (GFunc) recent_app_info_free,
1409                        NULL);
1410       g_slist_free (recent_info->applications);
1411       
1412       recent_info->applications = NULL;
1413     }
1414   
1415   if (recent_info->apps_lookup)
1416     g_hash_table_destroy (recent_info->apps_lookup);
1417
1418   if (recent_info->groups)
1419     {
1420       g_slist_foreach (recent_info->groups,
1421                        (GFunc) g_free,
1422                        NULL);
1423       g_slist_free (recent_info->groups);
1424
1425       recent_info->groups = NULL;
1426     }
1427   
1428   if (recent_info->icon)
1429     g_object_unref (recent_info->icon);
1430
1431   g_free (recent_info);
1432 }
1433
1434 /**
1435  * gtk_recent_info_ref:
1436  * @info: a #GtkRecentInfo
1437  *
1438  * Increases the reference count of @recent_info by one.
1439  *
1440  * Return value: the recent info object with its reference count increased
1441  *   by one.
1442  *
1443  * Since: 2.10
1444  */
1445 GtkRecentInfo *
1446 gtk_recent_info_ref (GtkRecentInfo *info)
1447 {
1448   g_return_val_if_fail (info != NULL, NULL);
1449   g_return_val_if_fail (info->ref_count > 0, NULL);
1450   
1451   info->ref_count += 1;
1452     
1453   return info;
1454 }
1455
1456 /**
1457  * gtk_recent_info_unref:
1458  * @info: a #GtkRecentInfo
1459  *
1460  * Decreases the reference count of @info by one.  If the reference
1461  * count reaches zero, @info is deallocated, and the memory freed.
1462  *
1463  * Since: 2.10
1464  */
1465 void
1466 gtk_recent_info_unref (GtkRecentInfo *info)
1467 {
1468   g_return_if_fail (info != NULL);
1469   g_return_if_fail (info->ref_count > 0);
1470
1471   info->ref_count -= 1;
1472   
1473   if (info->ref_count == 0)
1474     gtk_recent_info_free (info);
1475 }
1476
1477 /**
1478  * gtk_recent_info_get_uri:
1479  * @info: a #GtkRecentInfo
1480  *
1481  * Gets the URI of the resource.
1482  *
1483  * Return value: the URI of the resource.  The returned string is
1484  *   owned by the recent manager, and should not be freed.
1485  *
1486  * Since: 2.10
1487  */
1488 G_CONST_RETURN gchar *
1489 gtk_recent_info_get_uri (GtkRecentInfo *info)
1490 {
1491   g_return_val_if_fail (info != NULL, NULL);
1492   
1493   return info->uri;
1494 }
1495
1496 /**
1497  * gtk_recent_info_get_display_name:
1498  * @info: a #GtkRecentInfo
1499  *
1500  * Gets the name of the resource.  If none has been defined, the basename
1501  * of the resource is obtained.
1502  *
1503  * Return value: the display name of the resource.  The returned string
1504  *   is owned by the recent manager, and should not be freed.
1505  *
1506  * Since: 2.10
1507  */
1508 G_CONST_RETURN gchar *
1509 gtk_recent_info_get_display_name (GtkRecentInfo *info)
1510 {
1511   g_return_val_if_fail (info != NULL, NULL);
1512   
1513   if (!info->display_name)
1514     info->display_name = gtk_recent_info_get_short_name (info);
1515   
1516   return info->display_name;
1517 }
1518
1519 /**
1520  * gtk_recent_info_get_description:
1521  * @info: a #GtkRecentInfo
1522  *
1523  * Gets the (short) description of the resource.
1524  *
1525  * Return value: the description of the resource.  The returned string
1526  *   is owned by the recent manager, and should not be freed.
1527  *
1528  * Since: 2.10
1529  **/
1530 G_CONST_RETURN gchar *
1531 gtk_recent_info_get_description (GtkRecentInfo *info)
1532 {
1533   g_return_val_if_fail (info != NULL, NULL);
1534   
1535   return info->description;
1536 }
1537
1538 /**
1539  * gtk_recent_info_get_mime_type:
1540  * @info: a #GtkRecentInfo
1541  *
1542  * Gets the MIME type of the resource.
1543  *
1544  * Return value: the MIME type of the resource.  The returned string
1545  *   is owned by the recent manager, and should not be freed.
1546  *
1547  * Since: 2.10
1548  */
1549 G_CONST_RETURN gchar *
1550 gtk_recent_info_get_mime_type (GtkRecentInfo *info)
1551 {
1552   g_return_val_if_fail (info != NULL, NULL);
1553   
1554   if (!info->mime_type)
1555     info->mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME);
1556   
1557   return info->mime_type;
1558 }
1559
1560 /**
1561  * gtk_recent_info_get_added:
1562  * @info: a #GtkRecentInfo
1563  *
1564  * Gets the timestamp (seconds from system's Epoch) when the resource
1565  * was added to the recently used resources list.
1566  *
1567  * Return value: the number of seconds elapsed from system's Epoch when
1568  *   the resource was added to the list, or -1 on failure.
1569  *
1570  * Since: 2.10
1571  */
1572 time_t
1573 gtk_recent_info_get_added (GtkRecentInfo *info)
1574 {
1575   g_return_val_if_fail (info != NULL, (time_t) -1);
1576   
1577   return info->added;
1578 }
1579
1580 /**
1581  * gtk_recent_info_get_modified:
1582  * @info: a #GtkRecentInfo
1583  *
1584  * Gets the timestamp (seconds from system's Epoch) when the resource
1585  * was last modified.
1586  *
1587  * Return value: the number of seconds elapsed from system's Epoch when
1588  *   the resource was last modified, or -1 on failure.
1589  *
1590  * Since: 2.10
1591  */
1592 time_t
1593 gtk_recent_info_get_modified (GtkRecentInfo *info)
1594 {
1595   g_return_val_if_fail (info != NULL, (time_t) -1);
1596   
1597   return info->modified;
1598 }
1599
1600 /**
1601  * gtk_recent_info_get_visited:
1602  * @info: a #GtkRecentInfo
1603  *
1604  * Gets the timestamp (seconds from system's Epoch) when the resource
1605  * was last visited.
1606  *
1607  * Return value: the number of seconds elapsed from system's Epoch when
1608  *   the resource was last visited, or -1 on failure.
1609  *
1610  * Since: 2.10
1611  */
1612 time_t
1613 gtk_recent_info_get_visited (GtkRecentInfo *info)
1614 {
1615   g_return_val_if_fail (info != NULL, (time_t) -1);
1616   
1617   return info->visited;
1618 }
1619
1620 /**
1621  * gtk_recent_info_get_private_hint:
1622  * @info: a #GtkRecentInfo
1623  *
1624  * Gets the value of the "private" flag.  Resources in the recently used
1625  * list that have this flag set to %TRUE should only be displayed by the
1626  * applications that have registered them.
1627  *
1628  * Return value: %TRUE if the private flag was found, %FALSE otherwise.
1629  *
1630  * Since: 2.10
1631  */
1632 gboolean
1633 gtk_recent_info_get_private_hint (GtkRecentInfo *info)
1634 {
1635   g_return_val_if_fail (info != NULL, FALSE);
1636   
1637   return info->is_private;
1638 }
1639
1640
1641 static RecentAppInfo *
1642 recent_app_info_new (const gchar *app_name)
1643 {
1644   RecentAppInfo *app_info;
1645
1646   g_assert (app_name != NULL);
1647   
1648   app_info = g_slice_new0 (RecentAppInfo);
1649   app_info->name = g_strdup (app_name);
1650   app_info->exec = NULL;
1651   app_info->count = 1;
1652   app_info->stamp = 0; 
1653   
1654   return app_info;
1655 }
1656
1657 static void
1658 recent_app_info_free (RecentAppInfo *app_info)
1659 {
1660   if (!app_info)
1661     return;
1662   
1663   g_free (app_info->name);
1664   g_free (app_info->exec);
1665   
1666   g_slice_free (RecentAppInfo, app_info);
1667 }
1668
1669 /**
1670  * gtk_recent_info_get_application_info:
1671  * @info: a #GtkRecentInfo
1672  * @app_name: the name of the application that has registered this item
1673  * @app_exec: (transfer none) (out): return location for the string containing the command line
1674  * @count: (out): return location for the number of times this item was registered
1675  * @time_: (out): return location for the timestamp this item was last registered
1676  *    for this application
1677  *
1678  * Gets the data regarding the application that has registered the resource
1679  * pointed by @info.
1680  *
1681  * If the command line contains any escape characters defined inside the
1682  * storage specification, they will be expanded.
1683  *
1684  * Return value: %TRUE if an application with @app_name has registered this
1685  *   resource inside the recently used list, or %FALSE otherwise. The
1686  *   @app_exec string is owned by the #GtkRecentInfo and should not be
1687  *   modified or freed
1688  *
1689  * Since: 2.10
1690  */
1691 gboolean
1692 gtk_recent_info_get_application_info (GtkRecentInfo  *info,
1693                                       const gchar    *app_name,
1694                                       const gchar   **app_exec,
1695                                       guint          *count,
1696                                       time_t         *time_)
1697 {
1698   RecentAppInfo *ai;
1699   
1700   g_return_val_if_fail (info != NULL, FALSE);
1701   g_return_val_if_fail (app_name != NULL, FALSE);
1702   
1703   ai = (RecentAppInfo *) g_hash_table_lookup (info->apps_lookup,
1704                                               app_name);
1705   if (!ai)
1706     {
1707       g_warning ("No registered application with name '%s' "
1708                  "for item with URI '%s' found",
1709                  app_name,
1710                  info->uri);
1711       return FALSE;
1712     }
1713   
1714   if (app_exec)
1715     *app_exec = ai->exec;
1716   
1717   if (count)
1718     *count = ai->count;
1719   
1720   if (time_)
1721     *time_ = ai->stamp;
1722
1723   return TRUE;
1724 }
1725
1726 /**
1727  * gtk_recent_info_get_applications:
1728  * @info: a #GtkRecentInfo
1729  * @length: (out) (allow-none): return location for the length of the returned list
1730  *
1731  * Retrieves the list of applications that have registered this resource.
1732  *
1733  * Return value: (array length=length zero-terminated=1) (transfer full):
1734  *     a newly allocated %NULL-terminated array of strings.
1735  *     Use g_strfreev() to free it.
1736  *
1737  * Since: 2.10
1738  */
1739 gchar **
1740 gtk_recent_info_get_applications (GtkRecentInfo *info,
1741                                   gsize         *length)
1742 {
1743   GSList *l;
1744   gchar **retval;
1745   gsize n_apps, i;
1746   
1747   g_return_val_if_fail (info != NULL, NULL);
1748   
1749   if (!info->applications)
1750     {
1751       if (length)
1752         *length = 0;
1753       
1754       return NULL;    
1755     }
1756   
1757   n_apps = g_slist_length (info->applications);
1758   
1759   retval = g_new0 (gchar *, n_apps + 1);
1760   
1761   for (l = info->applications, i = 0;
1762        l != NULL;
1763        l = l->next)
1764     {
1765       RecentAppInfo *ai = (RecentAppInfo *) l->data;
1766       
1767       g_assert (ai != NULL);
1768       
1769       retval[i++] = g_strdup (ai->name);
1770     }
1771   retval[i] = NULL;
1772   
1773   if (length)
1774     *length = i;
1775   
1776   return retval;
1777 }
1778
1779 /**
1780  * gtk_recent_info_has_application:
1781  * @info: a #GtkRecentInfo
1782  * @app_name: a string containing an application name
1783  *
1784  * Checks whether an application registered this resource using @app_name.
1785  *
1786  * Return value: %TRUE if an application with name @app_name was found,
1787  *   %FALSE otherwise.
1788  *
1789  * Since: 2.10
1790  */
1791 gboolean
1792 gtk_recent_info_has_application (GtkRecentInfo *info,
1793                                  const gchar   *app_name)
1794 {
1795   g_return_val_if_fail (info != NULL, FALSE);
1796   g_return_val_if_fail (app_name != NULL, FALSE);
1797   
1798   return (NULL != g_hash_table_lookup (info->apps_lookup, app_name));
1799 }
1800
1801 /**
1802  * gtk_recent_info_last_application:
1803  * @info: a #GtkRecentInfo
1804  *
1805  * Gets the name of the last application that have registered the
1806  * recently used resource represented by @info.
1807  *
1808  * Return value: an application name.  Use g_free() to free it.
1809  *
1810  * Since: 2.10
1811  */
1812 gchar *
1813 gtk_recent_info_last_application (GtkRecentInfo  *info)
1814 {
1815   GSList *l;
1816   time_t last_stamp = (time_t) -1;
1817   gchar *name = NULL;
1818   
1819   g_return_val_if_fail (info != NULL, NULL);
1820   
1821   for (l = info->applications; l != NULL; l = l->next)
1822     {
1823       RecentAppInfo *ai = (RecentAppInfo *) l->data;
1824       
1825       if (ai->stamp > last_stamp)
1826         {
1827           name = ai->name;
1828           last_stamp = ai->stamp;
1829         }
1830     }
1831   
1832   return g_strdup (name);
1833 }
1834
1835 static GdkPixbuf *
1836 get_icon_for_mime_type (const char *mime_type,
1837                         gint        pixel_size)
1838 {
1839   GtkIconTheme *icon_theme;
1840   char *content_type;
1841   GIcon *icon;
1842   GtkIconInfo *info;
1843   GdkPixbuf *pixbuf;
1844
1845   icon_theme = gtk_icon_theme_get_default ();
1846
1847   content_type = g_content_type_from_mime_type (mime_type);
1848
1849   if (!content_type)
1850     return NULL;
1851
1852   icon = g_content_type_get_icon (content_type);
1853   info = gtk_icon_theme_lookup_by_gicon (icon_theme, 
1854                                          icon, 
1855                                          pixel_size, 
1856                                          GTK_ICON_LOOKUP_USE_BUILTIN);
1857   g_free (content_type);
1858   g_object_unref (icon);
1859
1860   if (!info)
1861     return NULL;
1862
1863   pixbuf = gtk_icon_info_load_icon (info, NULL);
1864   gtk_icon_info_free (info);
1865
1866   return pixbuf;
1867 }
1868
1869 static GdkPixbuf *
1870 get_icon_fallback (const gchar *icon_name,
1871                    gint         size)
1872 {
1873   GtkIconTheme *icon_theme;
1874   GdkPixbuf *retval;
1875
1876   icon_theme = gtk_icon_theme_get_default ();
1877   
1878   retval = gtk_icon_theme_load_icon (icon_theme, icon_name,
1879                                      size,
1880                                      GTK_ICON_LOOKUP_USE_BUILTIN,
1881                                      NULL);
1882   g_assert (retval != NULL);
1883   
1884   return retval; 
1885 }
1886
1887 /**
1888  * gtk_recent_info_get_icon:
1889  * @info: a #GtkRecentInfo
1890  * @size: the size of the icon in pixels
1891  *
1892  * Retrieves the icon of size @size associated to the resource MIME type.
1893  *
1894  * Return value: (transfer full): a #GdkPixbuf containing the icon,
1895  *     or %NULL. Use g_object_unref() when finished using the icon.
1896  *
1897  * Since: 2.10
1898  */
1899 GdkPixbuf *
1900 gtk_recent_info_get_icon (GtkRecentInfo *info,
1901                           gint           size)
1902 {
1903   GdkPixbuf *retval = NULL;
1904   
1905   g_return_val_if_fail (info != NULL, NULL);
1906   
1907   if (info->mime_type)
1908     retval = get_icon_for_mime_type (info->mime_type, size);
1909
1910   /* this function should never fail */  
1911   if (!retval)
1912     {
1913       if (info->mime_type &&
1914           strcmp (info->mime_type, "x-directory/normal") == 0)
1915         retval = get_icon_fallback (GTK_STOCK_DIRECTORY, size);
1916       else
1917         retval = get_icon_fallback (GTK_STOCK_FILE, size);
1918     }
1919   
1920   return retval;
1921 }
1922
1923 /**
1924  * gtk_recent_info_is_local:
1925  * @info: a #GtkRecentInfo
1926  *
1927  * Checks whether the resource is local or not by looking at the
1928  * scheme of its URI.
1929  *
1930  * Return value: %TRUE if the resource is local.
1931  *
1932  * Since: 2.10
1933  */
1934 gboolean
1935 gtk_recent_info_is_local (GtkRecentInfo *info)
1936 {
1937   g_return_val_if_fail (info != NULL, FALSE);
1938   
1939   return has_case_prefix (info->uri, "file:/");
1940 }
1941
1942 /**
1943  * gtk_recent_info_exists:
1944  * @info: a #GtkRecentInfo
1945  *
1946  * Checks whether the resource pointed by @info still exists.  At
1947  * the moment this check is done only on resources pointing to local files.
1948  *
1949  * Return value: %TRUE if the resource exists
1950  *
1951  * Since: 2.10
1952  */
1953 gboolean
1954 gtk_recent_info_exists (GtkRecentInfo *info)
1955 {
1956   gchar *filename;
1957   struct stat stat_buf;
1958   gboolean retval = FALSE;
1959   
1960   g_return_val_if_fail (info != NULL, FALSE);
1961   
1962   /* we guarantee only local resources */
1963   if (!gtk_recent_info_is_local (info))
1964     return FALSE;
1965   
1966   filename = g_filename_from_uri (info->uri, NULL, NULL);
1967   if (filename)
1968     {
1969       if (stat (filename, &stat_buf) == 0)
1970         retval = TRUE;
1971      
1972       g_free (filename);
1973     }
1974   
1975   return retval;
1976 }
1977
1978 /**
1979  * gtk_recent_info_match:
1980  * @info_a: a #GtkRecentInfo
1981  * @info_b: a #GtkRecentInfo
1982  *
1983  * Checks whether two #GtkRecentInfo structures point to the same
1984  * resource.
1985  *
1986  * Return value: %TRUE if both #GtkRecentInfo structures point to se same
1987  *   resource, %FALSE otherwise.
1988  *
1989  * Since: 2.10
1990  */
1991 gboolean
1992 gtk_recent_info_match (GtkRecentInfo *info_a,
1993                        GtkRecentInfo *info_b)
1994 {
1995   g_return_val_if_fail (info_a != NULL, FALSE);
1996   g_return_val_if_fail (info_b != NULL, FALSE);
1997   
1998   return (0 == strcmp (info_a->uri, info_b->uri));
1999 }
2000
2001 /* taken from gnome-vfs-uri.c */
2002 static const gchar *
2003 get_method_string (const gchar  *substring, 
2004                    gchar       **method_string)
2005 {
2006   const gchar *p;
2007   char *method;
2008         
2009   for (p = substring;
2010        g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.';
2011        p++)
2012     ;
2013
2014   if (*p == ':'
2015 #ifdef G_OS_WIN32
2016                 &&
2017       !(p == substring + 1 && g_ascii_isalpha (*substring))
2018 #endif
2019                                                            )
2020     {
2021       /* Found toplevel method specification.  */
2022       method = g_strndup (substring, p - substring);
2023       *method_string = g_ascii_strdown (method, -1);
2024       g_free (method);
2025       p++;
2026     }
2027   else
2028     {
2029       *method_string = g_strdup ("file");
2030       p = substring;
2031     }
2032   
2033   return p;
2034 }
2035
2036 /* Stolen from gnome_vfs_make_valid_utf8() */
2037 static char *
2038 make_valid_utf8 (const char *name)
2039 {
2040   GString *string;
2041   const char *remainder, *invalid;
2042   int remaining_bytes, valid_bytes;
2043
2044   string = NULL;
2045   remainder = name;
2046   remaining_bytes = name ? strlen (name) : 0;
2047
2048   while (remaining_bytes != 0)
2049     {
2050       if (g_utf8_validate (remainder, remaining_bytes, &invalid))
2051         break;
2052       
2053       valid_bytes = invalid - remainder;
2054       
2055       if (string == NULL)
2056         string = g_string_sized_new (remaining_bytes);
2057       
2058       g_string_append_len (string, remainder, valid_bytes);
2059       g_string_append_c (string, '?');
2060       
2061       remaining_bytes -= valid_bytes + 1;
2062       remainder = invalid + 1;
2063     }
2064   
2065   if (string == NULL)
2066     return g_strdup (name);
2067
2068   g_string_append (string, remainder);
2069   g_assert (g_utf8_validate (string->str, -1, NULL));
2070
2071   return g_string_free (string, FALSE);
2072 }
2073
2074 static gchar *
2075 get_uri_shortname_for_display (const gchar *uri)
2076 {
2077   gchar *name = NULL;
2078   gboolean validated = FALSE;
2079
2080   if (has_case_prefix (uri, "file:/"))
2081     {
2082       gchar *local_file;
2083       
2084       local_file = g_filename_from_uri (uri, NULL, NULL);
2085       
2086       if (local_file)
2087         {
2088           name = g_filename_display_basename (local_file);
2089           validated = TRUE;
2090         }
2091                 
2092       g_free (local_file);
2093     } 
2094   
2095   if (!name)
2096     {
2097       gchar *method;
2098       gchar *local_file;
2099       const gchar *rest;
2100       
2101       rest = get_method_string (uri, &method);
2102       local_file = g_filename_display_basename (rest);
2103       
2104       name = g_strconcat (method, ": ", local_file, NULL);
2105       
2106       g_free (local_file);
2107       g_free (method);
2108     }
2109   
2110   g_assert (name != NULL);
2111   
2112   if (!validated && !g_utf8_validate (name, -1, NULL))
2113     {
2114       gchar *utf8_name;
2115       
2116       utf8_name = make_valid_utf8 (name);
2117       g_free (name);
2118       
2119       name = utf8_name;
2120     }
2121
2122   return name;
2123 }
2124
2125 /**
2126  * gtk_recent_info_get_short_name:
2127  * @info: an #GtkRecentInfo
2128  *
2129  * Computes a valid UTF-8 string that can be used as the name of the item in a
2130  * menu or list.  For example, calling this function on an item that refers to
2131  * "file:///foo/bar.txt" will yield "bar.txt".
2132  *
2133  * Return value: A newly-allocated string in UTF-8 encoding; free it with
2134  *   g_free().
2135  *
2136  * Since: 2.10
2137  */
2138 gchar *
2139 gtk_recent_info_get_short_name (GtkRecentInfo *info)
2140 {
2141   gchar *short_name;
2142
2143   g_return_val_if_fail (info != NULL, NULL);
2144
2145   if (info->uri == NULL)
2146     return NULL;
2147
2148   short_name = get_uri_shortname_for_display (info->uri);
2149
2150   return short_name;
2151 }
2152
2153 /**
2154  * gtk_recent_info_get_uri_display:
2155  * @info: a #GtkRecentInfo
2156  *
2157  * Gets a displayable version of the resource's URI.  If the resource
2158  * is local, it returns a local path; if the resource is not local,
2159  * it returns the UTF-8 encoded content of gtk_recent_info_get_uri().
2160  *
2161  * Return value: a newly allocated UTF-8 string containing the
2162  *   resource's URI or %NULL. Use g_free() when done using it.
2163  *
2164  * Since: 2.10
2165  */
2166 gchar *
2167 gtk_recent_info_get_uri_display (GtkRecentInfo *info)
2168 {
2169   gchar *retval;
2170   
2171   g_return_val_if_fail (info != NULL, NULL);
2172
2173   retval = NULL;
2174   if (gtk_recent_info_is_local (info))
2175     {
2176       gchar *filename;
2177
2178       filename = g_filename_from_uri (info->uri, NULL, NULL);
2179       if (!filename)
2180         return NULL;
2181       
2182       retval = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
2183       g_free (filename);
2184     }
2185   else
2186     {
2187       retval = make_valid_utf8 (info->uri);
2188     }
2189
2190   return retval;
2191 }
2192
2193 /**
2194  * gtk_recent_info_get_age:
2195  * @info: a #GtkRecentInfo
2196  *
2197  * Gets the number of days elapsed since the last update of the resource
2198  * pointed by @info.
2199  *
2200  * Return value: a positive integer containing the number of days elapsed
2201  *   since the time this resource was last modified.  
2202  *
2203  * Since: 2.10
2204  */
2205 gint
2206 gtk_recent_info_get_age (GtkRecentInfo *info)
2207 {
2208   time_t now, delta;
2209   gint retval;
2210
2211   g_return_val_if_fail (info != NULL, -1);
2212
2213   now = time (NULL);
2214   
2215   delta = now - info->modified;
2216   
2217   retval = (gint) (delta / (60 * 60 * 24));
2218   
2219   return retval;
2220 }
2221
2222 /**
2223  * gtk_recent_info_get_groups:
2224  * @info: a #GtkRecentInfo
2225  * @length: (out) (allow-none): return location for the number of groups returned
2226  *
2227  * Returns all groups registered for the recently used item @info.  The
2228  * array of returned group names will be %NULL terminated, so length might
2229  * optionally be %NULL.
2230  *
2231  * Return value:  (array length=length zero-terminated=1) (transfer full):
2232  *     a newly allocated %NULL terminated array of strings.
2233  *     Use g_strfreev() to free it.
2234  *
2235  * Since: 2.10
2236  */
2237 gchar **
2238 gtk_recent_info_get_groups (GtkRecentInfo *info,
2239                             gsize         *length)
2240 {
2241   GSList *l;
2242   gchar **retval;
2243   gsize n_groups, i;
2244   
2245   g_return_val_if_fail (info != NULL, NULL);
2246   
2247   if (!info->groups)
2248     {
2249       if (length)
2250         *length = 0;
2251       
2252       return NULL;
2253     }
2254   
2255   n_groups = g_slist_length (info->groups);
2256   
2257   retval = g_new0 (gchar *, n_groups + 1);
2258   
2259   for (l = info->groups, i = 0;
2260        l != NULL;
2261        l = l->next)
2262     {
2263       gchar *group_name = (gchar *) l->data;
2264       
2265       g_assert (group_name != NULL);
2266       
2267       retval[i++] = g_strdup (group_name);
2268     }
2269   retval[i] = NULL;
2270   
2271   if (length)
2272     *length = i;
2273   
2274   return retval;
2275 }
2276
2277 /**
2278  * gtk_recent_info_has_group:
2279  * @info: a #GtkRecentInfo
2280  * @group_name: name of a group
2281  *
2282  * Checks whether @group_name appears inside the groups registered for the
2283  * recently used item @info.
2284  *
2285  * Return value: %TRUE if the group was found.
2286  *
2287  * Since: 2.10
2288  */
2289 gboolean
2290 gtk_recent_info_has_group (GtkRecentInfo *info,
2291                            const gchar   *group_name)
2292 {
2293   GSList *l;
2294   
2295   g_return_val_if_fail (info != NULL, FALSE);
2296   g_return_val_if_fail (group_name != NULL, FALSE);
2297
2298   if (!info->groups)
2299     return FALSE;
2300
2301   for (l = info->groups; l != NULL; l = l->next)
2302     {
2303       gchar *g = (gchar *) l->data;
2304
2305       if (strcmp (g, group_name) == 0)
2306         return TRUE;
2307     }
2308
2309   return FALSE;
2310 }
2311
2312 /*
2313  * _gtk_recent_manager_sync:
2314  * 
2315  * Private function for synchronising the recent manager singleton.
2316  */
2317 void
2318 _gtk_recent_manager_sync (void)
2319 {
2320   if (recent_manager_singleton)
2321     {
2322       /* force a dump of the contents of the recent manager singleton */
2323       recent_manager_singleton->priv->is_dirty = TRUE;
2324       gtk_recent_manager_real_changed (recent_manager_singleton);
2325     }
2326 }