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