]> www.fi.muni.cz Git - evince.git/blob - cut-n-paste/recent-files/egg-recent-model.c
Initial revision
[evince.git] / cut-n-paste / recent-files / egg-recent-model.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /**
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of the
6  * License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
16  *
17  * Authors:
18  *   James Willcox <jwillcox@cs.indiana.edu>
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <stdio.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <sys/time.h>
32 #include <time.h>
33 #include <gtk/gtk.h>
34 #include <libgnomevfs/gnome-vfs.h>
35 #include <libgnomevfs/gnome-vfs-mime-utils.h>
36 #include <gconf/gconf-client.h>
37 #include "egg-recent-model.h"
38 #include "egg-recent-item.h"
39
40 #define EGG_RECENT_MODEL_FILE_PATH "/.recently-used"
41 #define EGG_RECENT_MODEL_BUFFER_SIZE 8192
42
43 #define EGG_RECENT_MODEL_MAX_ITEMS 500
44 #define EGG_RECENT_MODEL_DEFAULT_LIMIT 10
45 #define EGG_RECENT_MODEL_TIMEOUT_LENGTH 200
46
47 #define EGG_RECENT_MODEL_KEY_DIR "/desktop/gnome/recent_files"
48 #define EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY EGG_RECENT_MODEL_KEY_DIR "/default_limit"
49 #define EGG_RECENT_MODEL_EXPIRE_KEY EGG_RECENT_MODEL_KEY_DIR "/expire"
50
51 struct _EggRecentModelPrivate {
52         GSList *mime_filter_values;     /* list of mime types we allow */
53         GSList *group_filter_values;    /* list of groups we allow */
54         GSList *scheme_filter_values;   /* list of URI schemes we allow */
55
56         EggRecentModelSort sort_type; /* type of sorting to be done */
57
58         int limit;                      /* soft limit for length of the list */
59         int expire_days;                /* number of days to hold an item */
60
61         char *path;                     /* path to the file we store stuff in */
62
63         GHashTable *monitors;
64
65         GnomeVFSMonitorHandle *monitor;
66
67         GConfClient *client;
68         gboolean use_default_limit;
69
70         guint limit_change_notify_id;
71         guint expiration_change_notify_id;
72
73         guint changed_timeout;
74 };
75
76 /* signals */
77 enum {
78         CHANGED,
79         LAST_SIGNAL
80 };
81
82 static GType model_signals[LAST_SIGNAL] = { 0 };
83
84 /* properties */
85 enum {
86         PROP_BOGUS,
87         PROP_MIME_FILTERS,
88         PROP_GROUP_FILTERS,
89         PROP_SCHEME_FILTERS,
90         PROP_SORT_TYPE,
91         PROP_LIMIT
92 };
93
94 typedef struct {
95         GSList *states;
96         GList *items;
97         EggRecentItem *current_item;
98 }ParseInfo;
99
100 typedef enum {
101         STATE_START,
102         STATE_RECENT_FILES,
103         STATE_RECENT_ITEM,
104         STATE_URI,
105         STATE_MIME_TYPE,
106         STATE_TIMESTAMP,
107         STATE_PRIVATE,
108         STATE_GROUPS,
109         STATE_GROUP
110 } ParseState;
111
112 typedef struct _ChangedData {
113         EggRecentModel *model;
114         GList *list;
115 }ChangedData;
116
117 #define TAG_RECENT_FILES "RecentFiles"
118 #define TAG_RECENT_ITEM "RecentItem"
119 #define TAG_URI "URI"
120 #define TAG_MIME_TYPE "Mime-Type"
121 #define TAG_TIMESTAMP "Timestamp"
122 #define TAG_PRIVATE "Private"
123 #define TAG_GROUPS "Groups"
124 #define TAG_GROUP "Group"
125
126 static void start_element_handler (GMarkupParseContext *context,
127                               const gchar *element_name,
128                               const gchar **attribute_names,
129                               const gchar **attribute_values,
130                               gpointer user_data,
131                               GError **error);
132
133 static void end_element_handler (GMarkupParseContext *context,
134                             const gchar *element_name,
135                             gpointer user_data,
136                             GError **error);
137
138 static void text_handler (GMarkupParseContext *context,
139                      const gchar *text,
140                      gsize text_len,
141                      gpointer user_data,
142                      GError **error);
143
144 static void error_handler (GMarkupParseContext *context,
145                       GError *error,
146                       gpointer user_data);
147
148 static GMarkupParser parser = {start_element_handler, end_element_handler,
149                         text_handler,
150                         NULL,
151                         error_handler};
152
153 static gboolean
154 egg_recent_model_string_match (const GSList *list, const gchar *str)
155 {
156         const GSList *tmp;
157
158         if (list == NULL || str == NULL)
159                 return TRUE;
160
161         tmp = list;
162         
163         while (tmp) {
164                 if (g_pattern_match_string (tmp->data, str))
165                         return TRUE;
166                 
167                 tmp = tmp->next;
168         }
169
170         return FALSE;
171 }
172
173 static gboolean
174 egg_recent_model_write_raw (EggRecentModel *model, FILE *file,
175                               const gchar *content)
176 {
177         int len;
178         int fd;
179         struct stat sbuf;
180
181         rewind (file);
182
183         len = strlen (content);
184         fd = fileno (file);
185
186         if (fstat (fd, &sbuf) < 0)
187                 g_warning ("Couldn't stat XML document.");
188
189         if ((off_t)len < sbuf.st_size) {
190                 ftruncate (fd, len);
191         }
192
193         if (fputs (content, file) == EOF)
194                 return FALSE;
195
196         fsync (fd);
197         rewind (file);
198
199         return TRUE;
200 }
201
202 static GList *
203 egg_recent_model_delete_from_list (GList *list,
204                                        const gchar *uri)
205 {
206         GList *tmp;
207
208         if (!uri)
209                 return list;
210
211         tmp = list;
212
213         while (tmp) {
214                 EggRecentItem *item = tmp->data;
215                 GList         *next;
216
217                 next = tmp->next;
218
219                 if (!strcmp (egg_recent_item_peek_uri (item), uri)) {
220                         egg_recent_item_unref (item);
221
222                         list = g_list_remove_link (list, tmp);
223                         g_list_free_1 (tmp);
224                 }
225
226                 tmp = next;
227         }
228
229         return list;
230 }
231
232 static void
233 egg_recent_model_add_new_groups (EggRecentItem *item,
234                                  EggRecentItem *upd_item)
235 {
236         const GList *tmp;
237
238         tmp = egg_recent_item_get_groups (upd_item);
239
240         while (tmp) {
241                 char *group = tmp->data;
242
243                 if (!egg_recent_item_in_group (item, group))
244                         egg_recent_item_add_group (item, group);
245
246                 tmp = tmp->next;
247         }
248 }
249
250 static gboolean
251 egg_recent_model_update_item (GList *items, EggRecentItem *upd_item)
252 {
253         GList      *tmp;
254         const char *uri;
255
256         uri = egg_recent_item_peek_uri (upd_item);
257
258         tmp = items;
259
260         while (tmp) {
261                 EggRecentItem *item = tmp->data;
262
263                 if (gnome_vfs_uris_match (egg_recent_item_peek_uri (item), uri)) {
264                         egg_recent_item_set_timestamp (item, (time_t) -1);
265
266                         egg_recent_model_add_new_groups (item, upd_item);
267
268                         return TRUE;
269                 }
270
271                 tmp = tmp->next;
272         }
273
274         return FALSE;
275 }
276
277 static gchar *
278 egg_recent_model_read_raw (EggRecentModel *model, FILE *file)
279 {
280         GString *string;
281         char buf[EGG_RECENT_MODEL_BUFFER_SIZE];
282
283         rewind (file);
284
285         string = g_string_new (NULL);
286         while (fgets (buf, EGG_RECENT_MODEL_BUFFER_SIZE, file)) {
287                 string = g_string_append (string, buf);
288         }
289
290         rewind (file);
291
292         return g_string_free (string, FALSE);
293 }
294
295
296
297 static void
298 parse_info_init (ParseInfo *info)
299 {
300         info->states = g_slist_prepend (NULL, STATE_START);
301         info->items = NULL;
302 }
303
304 static void
305 parse_info_free (ParseInfo *info)
306 {
307         g_slist_free (info->states);
308 }
309
310 static void
311 push_state (ParseInfo  *info,
312             ParseState  state)
313 {
314   info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
315 }
316
317 static void
318 pop_state (ParseInfo *info)
319 {
320   g_return_if_fail (info->states != NULL);
321
322   info->states = g_slist_remove (info->states, info->states->data);
323 }
324
325 static ParseState
326 peek_state (ParseInfo *info)
327 {
328   g_return_val_if_fail (info->states != NULL, STATE_START);
329
330   return GPOINTER_TO_INT (info->states->data);
331 }
332
333 #define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
334
335 static void
336 start_element_handler (GMarkupParseContext *context,
337                               const gchar *element_name,
338                               const gchar **attribute_names,
339                               const gchar **attribute_values,
340                               gpointer user_data,
341                               GError **error)
342 {
343         ParseInfo *info = (ParseInfo *)user_data;
344
345         if (ELEMENT_IS (TAG_RECENT_FILES))
346                 push_state (info, STATE_RECENT_FILES);
347         else if (ELEMENT_IS (TAG_RECENT_ITEM)) {
348                 info->current_item = egg_recent_item_new ();
349                 push_state (info, STATE_RECENT_ITEM);
350         } else if (ELEMENT_IS (TAG_URI))
351                 push_state (info, STATE_URI);
352         else if (ELEMENT_IS (TAG_MIME_TYPE))
353                 push_state (info, STATE_MIME_TYPE);
354         else if (ELEMENT_IS (TAG_TIMESTAMP))
355                 push_state (info, STATE_TIMESTAMP);
356         else if (ELEMENT_IS (TAG_PRIVATE)) {
357                 push_state (info, STATE_PRIVATE);
358                 egg_recent_item_set_private (info->current_item, TRUE);
359         } else if (ELEMENT_IS (TAG_GROUPS))
360                 push_state (info, STATE_GROUPS);
361         else if (ELEMENT_IS (TAG_GROUP)) 
362                 push_state (info, STATE_GROUP);
363 }
364
365 static gint
366 list_compare_func_mru (gpointer a, gpointer b)
367 {
368         EggRecentItem *item_a = (EggRecentItem *)a;
369         EggRecentItem *item_b = (EggRecentItem *)b;
370
371         return item_a->timestamp < item_b->timestamp;
372 }
373
374 static gint
375 list_compare_func_lru (gpointer a, gpointer b)
376 {
377         EggRecentItem *item_a = (EggRecentItem *)a;
378         EggRecentItem *item_b = (EggRecentItem *)b;
379
380         return item_a->timestamp > item_b->timestamp;
381 }
382
383
384
385 static void
386 end_element_handler (GMarkupParseContext *context,
387                             const gchar *element_name,
388                             gpointer user_data,
389                             GError **error)
390 {
391         ParseInfo *info = (ParseInfo *)user_data;
392
393         switch (peek_state (info)) {
394                 case STATE_RECENT_ITEM:
395                         info->items = g_list_append (info->items,
396                                                     info->current_item);
397                         if (info->current_item->uri == NULL ||
398                             strlen (info->current_item->uri) == 0)
399                                 g_warning ("URI NOT LOADED");
400                 break;
401                 default:
402                 break;
403         }
404
405         pop_state (info);
406 }
407
408 static void
409 text_handler (GMarkupParseContext *context,
410                      const gchar *text,
411                      gsize text_len,
412                      gpointer user_data,
413                      GError **error)
414 {
415         ParseInfo *info = (ParseInfo *)user_data;
416
417         switch (peek_state (info)) {
418                 case STATE_START:
419                 case STATE_RECENT_FILES:
420                 case STATE_RECENT_ITEM:
421                 case STATE_PRIVATE:
422                 case STATE_GROUPS:
423                 break;
424                 case STATE_URI:
425                         egg_recent_item_set_uri (info->current_item, text);
426                 break;
427                 case STATE_MIME_TYPE:
428                         egg_recent_item_set_mime_type (info->current_item,
429                                                          text);
430                 break;
431                 case STATE_TIMESTAMP:
432                         egg_recent_item_set_timestamp (info->current_item,
433                                                          (time_t)atoi (text));
434                 break;
435                 case STATE_GROUP:
436                         egg_recent_item_add_group (info->current_item,
437                                                      text);
438                 break;
439         }
440                         
441 }
442
443 static void
444 error_handler (GMarkupParseContext *context,
445                       GError *error,
446                       gpointer user_data)
447 {
448         g_warning ("Error in parse: %s", error->message);
449 }
450
451 static void
452 egg_recent_model_enforce_limit (GList *list, int limit)
453 {
454         int len;
455         GList *end;
456
457         /* limit < 0 means unlimited */
458         if (limit <= 0)
459                 return;
460
461         len = g_list_length (list);
462
463         if (len > limit) {
464                 GList *next;
465
466                 end = g_list_nth (list, limit-1);
467                 next = end->next;
468
469                 end->next = NULL;
470
471                 EGG_RECENT_ITEM_LIST_UNREF (next);
472         }
473 }
474
475 static GList *
476 egg_recent_model_sort (EggRecentModel *model, GList *list)
477 {
478         switch (model->priv->sort_type) {
479                 case EGG_RECENT_MODEL_SORT_MRU:
480                         list = g_list_sort (list,
481                                         (GCompareFunc)list_compare_func_mru);   
482                 break;
483                 case EGG_RECENT_MODEL_SORT_LRU:
484                         list = g_list_sort (list,
485                                         (GCompareFunc)list_compare_func_lru);
486                 break;
487                 case EGG_RECENT_MODEL_SORT_NONE:
488                 break;
489         }
490
491         return list;
492 }
493
494 static gboolean
495 egg_recent_model_group_match (EggRecentItem *item, GSList *groups)
496 {
497         GSList *tmp;
498
499         tmp = groups;
500
501         while (tmp != NULL) {
502                 const gchar * group = (const gchar *)tmp->data;
503
504                 if (egg_recent_item_in_group (item, group))
505                         return TRUE;
506
507                 tmp = tmp->next;
508         }
509
510         return FALSE;
511 }
512
513 static GList *
514 egg_recent_model_filter (EggRecentModel *model,
515                                 GList *list)
516 {
517         EggRecentItem *item;
518         GList *newlist = NULL;
519         gchar *mime_type;
520         gchar *uri;
521
522         g_return_val_if_fail (list != NULL, NULL);
523
524         while (list) {
525                 gboolean pass_mime_test = FALSE;
526                 gboolean pass_group_test = FALSE;
527                 gboolean pass_scheme_test = FALSE;
528                 item = (EggRecentItem *)list->data;
529                 list = list->next;
530
531                 uri = egg_recent_item_get_uri (item);
532
533                 /* filter by mime type */
534                 if (model->priv->mime_filter_values != NULL) {
535                         mime_type = egg_recent_item_get_mime_type (item);
536
537                         if (egg_recent_model_string_match
538                                         (model->priv->mime_filter_values,
539                                          mime_type))
540                                 pass_mime_test = TRUE;
541
542                         g_free (mime_type);
543                 } else
544                         pass_mime_test = TRUE;
545
546                 /* filter by group */
547                 if (pass_mime_test && model->priv->group_filter_values != NULL) {
548                         if (egg_recent_model_group_match
549                                         (item, model->priv->group_filter_values))
550                                 pass_group_test = TRUE;
551                 } else if (egg_recent_item_get_private (item)) {
552                         pass_group_test = FALSE;
553                 } else
554                         pass_group_test = TRUE;
555
556                 /* filter by URI scheme */
557                 if (pass_mime_test && pass_group_test &&
558                     model->priv->scheme_filter_values != NULL) {
559                         gchar *scheme;
560                         
561                         scheme = gnome_vfs_get_uri_scheme (uri);
562
563                         if (egg_recent_model_string_match
564                                 (model->priv->scheme_filter_values, scheme))
565                                 pass_scheme_test = TRUE;
566
567                         g_free (scheme);
568                 } else
569                         pass_scheme_test = TRUE;
570
571                 if (pass_mime_test && pass_group_test && pass_scheme_test)
572                         newlist = g_list_prepend (newlist, item);
573
574                 g_free (uri);
575         }
576
577         if (newlist) {
578                 newlist = g_list_reverse (newlist);
579                 g_list_free (list);
580         }
581
582         
583         return newlist;
584 }
585
586
587
588 #if 0
589 static void
590 egg_recent_model_monitor_list_cb (GnomeVFSMonitorHandle *handle,
591                                const gchar *monitor_uri,
592                                const gchar *info_uri,
593                                GnomeVFSMonitorEventType event_type,
594                                gpointer user_data)
595 {
596         EggRecentModel *model;
597
598         model = EGG_RECENT_MODEL (user_data);
599
600         if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED) {
601                 egg_recent_model_delete (model, monitor_uri);
602                 g_hash_table_remove (model->priv->monitors, monitor_uri);
603         }
604 }
605
606
607
608 static void
609 egg_recent_model_monitor_list (EggRecentModel *model, GList *list)
610 {
611         GList *tmp;
612
613         tmp = list;
614         while (tmp) {
615                 EggRecentItem *item = (EggRecentItem *)tmp->data;
616                 GnomeVFSMonitorHandle *handle;
617                 GnomeVFSResult res;
618                 gchar *uri;
619
620                 tmp = tmp->next;
621                 
622                 uri = egg_recent_item_get_uri (item);
623                 if (g_hash_table_lookup (model->priv->monitors, uri)) {
624                         /* already monitoring this one */
625                         g_free (uri);
626                         continue;
627                 }
628
629                 res = gnome_vfs_monitor_add (&handle, uri,
630                                              GNOME_VFS_MONITOR_FILE,
631                                              egg_recent_model_monitor_list_cb,
632                                              model);
633                 
634                 if (res == GNOME_VFS_OK)
635                         g_hash_table_insert (model->priv->monitors, uri, handle);
636                 else
637                         g_free (uri);
638         }
639 }
640 #endif
641
642
643 static gboolean
644 egg_recent_model_changed_timeout (EggRecentModel *model)
645 {
646         egg_recent_model_changed (model);
647
648         return FALSE;
649 }
650
651 static void
652 egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
653                                const gchar *monitor_uri,
654                                const gchar *info_uri,
655                                GnomeVFSMonitorEventType event_type,
656                                gpointer user_data)
657 {
658         EggRecentModel *model;
659
660         g_return_if_fail (user_data != NULL);
661         g_return_if_fail (EGG_IS_RECENT_MODEL (user_data));
662         model = EGG_RECENT_MODEL (user_data);
663
664         if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
665                 if (model->priv->changed_timeout > 0) {
666                         g_source_remove (model->priv->changed_timeout);
667                 }
668
669                 model->priv->changed_timeout = g_timeout_add (
670                         EGG_RECENT_MODEL_TIMEOUT_LENGTH,
671                         (GSourceFunc)egg_recent_model_changed_timeout,
672                         model);
673         }
674 }
675
676 static void
677 egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
678 {
679         if (should_monitor && model->priv->monitor == NULL) {
680
681                 gnome_vfs_monitor_add (&model->priv->monitor,
682                                              model->priv->path,
683                                              GNOME_VFS_MONITOR_FILE,
684                                              egg_recent_model_monitor_cb,
685                                              model);
686
687                 /* if the above fails, don't worry about it.
688                  * local notifications will still happen
689                  */
690
691         } else if (!should_monitor && model->priv->monitor != NULL) {
692                 gnome_vfs_monitor_cancel (model->priv->monitor);
693                 model->priv->monitor = NULL;
694         }
695 }
696
697 static void
698 egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
699 {
700         model->priv->limit = limit;
701
702         if (limit <= 0)
703                 egg_recent_model_monitor (model, FALSE);
704         else {
705                 egg_recent_model_monitor (model, TRUE);
706                 egg_recent_model_changed (model);
707         }
708 }
709
710 static GList *
711 egg_recent_model_read (EggRecentModel *model, FILE *file)
712 {
713         GList *list=NULL;
714         gchar *content;
715         GMarkupParseContext *ctx;
716         ParseInfo info;
717         GError *error;
718
719         content = egg_recent_model_read_raw (model, file);
720
721         if (strlen (content) <= 0) {
722                 g_free (content);
723                 return NULL;
724         }
725
726         parse_info_init (&info);
727         
728         ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
729         
730         error = NULL;
731         if (!g_markup_parse_context_parse (ctx, content, strlen (content),
732                                            &error)) {
733                 g_warning (error->message);
734                 g_error_free (error);
735                 error = NULL;
736                 goto out;
737         }
738
739         error = NULL;
740         if (!g_markup_parse_context_end_parse (ctx, &error))
741                 goto out;
742         
743         g_markup_parse_context_free (ctx);
744 out:
745         list = info.items;
746
747         parse_info_free (&info);
748
749         g_free (content);
750
751         /*
752         g_print ("Total items: %d\n", g_list_length (list));
753         */
754
755         return list;
756 }
757
758
759 static gboolean
760 egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
761 {
762         GString *string;
763         gchar *data;
764         EggRecentItem *item;
765         const GList *groups;
766         int i;
767         int ret;
768         
769         string = g_string_new ("<?xml version=\"1.0\"?>\n");
770         string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
771
772         i=0;
773         while (list) {
774                 gchar *uri;
775                 gchar *mime_type;
776                 gchar *escaped_uri;
777                 time_t timestamp;
778                 item = (EggRecentItem *)list->data;
779
780
781                 uri = egg_recent_item_get_uri_utf8 (item);
782                 escaped_uri = g_markup_escape_text (uri,
783                                                     strlen (uri));
784                 g_free (uri);
785
786                 mime_type = egg_recent_item_get_mime_type (item);
787                 timestamp = egg_recent_item_get_timestamp (item);
788                 
789                 string = g_string_append (string, "  <" TAG_RECENT_ITEM ">\n");
790
791                 g_string_append_printf (string,
792                                 "    <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
793
794                 if (mime_type)
795                         g_string_append_printf (string,
796                                 "    <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
797                 else
798                         g_string_append_printf (string,
799                                 "    <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
800
801                 
802                 g_string_append_printf (string,
803                                 "    <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
804
805                 if (egg_recent_item_get_private (item))
806                         string = g_string_append (string,
807                                         "    <" TAG_PRIVATE "/>\n");
808
809                 /* write the groups */
810                 string = g_string_append (string,
811                                 "    <" TAG_GROUPS ">\n");
812                 groups = egg_recent_item_get_groups (item);
813
814                 if (groups == NULL && egg_recent_item_get_private (item))
815                         g_warning ("Item with URI \"%s\" marked as private, but"
816                                    " does not belong to any groups.\n", uri);
817                 
818                 while (groups) {
819                         const gchar *group = (const gchar *)groups->data;
820                         gchar *escaped_group;
821
822                         escaped_group = g_markup_escape_text (group, strlen(group));
823
824                         g_string_append_printf (string,
825                                         "      <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
826                                         escaped_group);
827
828                         g_free (escaped_group);
829
830                         groups = groups->next;
831                 }
832                 
833                 string = g_string_append (string, "    </" TAG_GROUPS ">\n");
834
835                 string = g_string_append (string,
836                                 "  </" TAG_RECENT_ITEM ">\n");
837
838                 g_free (mime_type);
839                 g_free (escaped_uri);
840
841                 list = list->next;
842                 i++;
843         }
844
845         string = g_string_append (string, "</" TAG_RECENT_FILES ">");
846
847         data = g_string_free (string, FALSE);
848
849         ret = egg_recent_model_write_raw (model, file, data);
850
851         g_free (data);
852
853         return ret;
854 }
855
856 static FILE *
857 egg_recent_model_open_file (EggRecentModel *model)
858 {
859         FILE *file;
860         mode_t prev_umask;
861         
862         file = fopen (model->priv->path, "r+");
863         if (file == NULL) {
864                 /* be paranoid */
865                 prev_umask = umask (077);
866
867                 file = fopen (model->priv->path, "w+");
868
869                 umask (prev_umask);
870
871                 g_return_val_if_fail (file != NULL, NULL);
872         }
873
874         return file;
875 }
876
877 static gboolean
878 egg_recent_model_lock_file (FILE *file)
879 {
880         int fd;
881         gint    try = 5;
882
883         rewind (file);
884         fd = fileno (file);
885
886         /* Attempt to lock the file 5 times,
887          * waiting a random interval (< 1 second) 
888          * in between attempts.
889          * We should really be doing asynchronous
890          * locking, but requires substantially larger
891          * changes.
892          */
893         
894         while (try > 0)
895         {
896                 int rand_interval;
897
898                 if (lockf (fd, F_TLOCK, 0) == 0)
899                         return TRUE;
900
901                 rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
902                          
903                 g_usleep (100000 * rand_interval);
904
905                 --try;
906         }
907
908         return FALSE;
909 }
910
911 static gboolean
912 egg_recent_model_unlock_file (FILE *file)
913 {
914         int fd;
915
916         rewind (file);
917         fd = fileno (file);
918
919         return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
920 }
921
922 static void
923 egg_recent_model_finalize (GObject *object)
924 {
925         EggRecentModel *model = EGG_RECENT_MODEL (object);
926
927         egg_recent_model_monitor (model, FALSE);
928
929
930         g_slist_foreach (model->priv->mime_filter_values,
931                          (GFunc) g_pattern_spec_free, NULL);
932         g_slist_free (model->priv->mime_filter_values);
933         model->priv->mime_filter_values = NULL;
934
935         g_slist_foreach (model->priv->scheme_filter_values,
936                          (GFunc) g_pattern_spec_free, NULL);
937         g_slist_free (model->priv->scheme_filter_values);
938         model->priv->scheme_filter_values = NULL;
939
940         g_slist_foreach (model->priv->group_filter_values,
941                          (GFunc) g_free, NULL);
942         g_slist_free (model->priv->group_filter_values);
943         model->priv->group_filter_values = NULL;
944
945
946         if (model->priv->limit_change_notify_id)
947                 gconf_client_notify_remove (model->priv->client,
948                                             model->priv->limit_change_notify_id);
949         model->priv->expiration_change_notify_id = 0;
950
951         if (model->priv->expiration_change_notify_id)
952                 gconf_client_notify_remove (model->priv->client,
953                                             model->priv->expiration_change_notify_id);
954         model->priv->expiration_change_notify_id = 0;
955
956         g_object_unref (model->priv->client);
957         model->priv->client = NULL;
958
959
960         g_free (model->priv->path);
961         model->priv->path = NULL;
962         
963         g_hash_table_destroy (model->priv->monitors);
964         model->priv->monitors = NULL;
965
966
967         g_free (model->priv);
968 }
969
970 static void
971 egg_recent_model_set_property (GObject *object,
972                                guint prop_id,
973                                const GValue *value,
974                                GParamSpec *pspec)
975 {
976         EggRecentModel *model = EGG_RECENT_MODEL (object);
977
978         switch (prop_id)
979         {
980                 case PROP_MIME_FILTERS:
981                         model->priv->mime_filter_values =
982                                 (GSList *)g_value_get_pointer (value);
983                 break;
984
985                 case PROP_GROUP_FILTERS:
986                         model->priv->group_filter_values =
987                                 (GSList *)g_value_get_pointer (value);
988                 break;
989
990                 case PROP_SCHEME_FILTERS:
991                         model->priv->scheme_filter_values =
992                                 (GSList *)g_value_get_pointer (value);
993                 break;
994
995                 case PROP_SORT_TYPE:
996                         model->priv->sort_type = g_value_get_int (value);
997                 break;
998
999                 case PROP_LIMIT:
1000                         egg_recent_model_set_limit (model,
1001                                                 g_value_get_int (value));
1002                 break;
1003
1004                 default:
1005                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1006                 break;
1007         }
1008 }
1009
1010 static void
1011 egg_recent_model_get_property (GObject *object,
1012                                guint prop_id,
1013                                GValue *value,
1014                                GParamSpec *pspec)
1015 {
1016         EggRecentModel *model = EGG_RECENT_MODEL (object);
1017
1018         switch (prop_id)
1019         {
1020                 case PROP_MIME_FILTERS:
1021                         g_value_set_pointer (value, model->priv->mime_filter_values);
1022                 break;
1023
1024                 case PROP_GROUP_FILTERS:
1025                         g_value_set_pointer (value, model->priv->group_filter_values);
1026                 break;
1027
1028                 case PROP_SCHEME_FILTERS:
1029                         g_value_set_pointer (value, model->priv->scheme_filter_values);
1030                 break;
1031
1032                 case PROP_SORT_TYPE:
1033                         g_value_set_int (value, model->priv->sort_type);
1034                 break;
1035
1036                 case PROP_LIMIT:
1037                         g_value_set_int (value, model->priv->limit);
1038                 break;
1039
1040                 default:
1041                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1042                 break;
1043         }
1044 }
1045
1046 static void
1047 egg_recent_model_class_init (EggRecentModelClass * klass)
1048 {
1049         GObjectClass *object_class;
1050
1051         object_class = G_OBJECT_CLASS (klass);
1052         object_class->set_property = egg_recent_model_set_property;
1053         object_class->get_property = egg_recent_model_get_property;
1054         object_class->finalize     = egg_recent_model_finalize;
1055
1056         model_signals[CHANGED] = g_signal_new ("changed",
1057                         G_OBJECT_CLASS_TYPE (object_class),
1058                         G_SIGNAL_RUN_LAST,
1059                         G_STRUCT_OFFSET (EggRecentModelClass, changed),
1060                         NULL, NULL,
1061                         g_cclosure_marshal_VOID__POINTER,
1062                         G_TYPE_NONE, 1,
1063                         G_TYPE_POINTER);
1064
1065         
1066         g_object_class_install_property (object_class,
1067                                          PROP_MIME_FILTERS,
1068                                          g_param_spec_pointer ("mime-filters",
1069                                          "Mime Filters",
1070                                          "List of mime types to be allowed.",
1071                                          G_PARAM_READWRITE));
1072         
1073         g_object_class_install_property (object_class,
1074                                          PROP_GROUP_FILTERS,
1075                                          g_param_spec_pointer ("group-filters",
1076                                          "Group Filters",
1077                                          "List of groups to be allowed.",
1078                                          G_PARAM_READWRITE));
1079
1080         g_object_class_install_property (object_class,
1081                                          PROP_SCHEME_FILTERS,
1082                                          g_param_spec_pointer ("scheme-filters",
1083                                          "Scheme Filters",
1084                                          "List of URI schemes to be allowed.",
1085                                          G_PARAM_READWRITE));
1086
1087         g_object_class_install_property (object_class,
1088                                          PROP_SORT_TYPE,
1089                                          g_param_spec_int ("sort-type",
1090                                          "Sort Type",
1091                                          "Type of sorting to be done.",
1092                                          0, EGG_RECENT_MODEL_SORT_NONE,
1093                                          EGG_RECENT_MODEL_SORT_MRU,
1094                                          G_PARAM_READWRITE));
1095
1096         g_object_class_install_property (object_class,
1097                                          PROP_LIMIT,
1098                                          g_param_spec_int ("limit",
1099                                          "Limit",
1100                                          "Max number of items allowed.",
1101                                          -1, EGG_RECENT_MODEL_MAX_ITEMS,
1102                                          EGG_RECENT_MODEL_DEFAULT_LIMIT,
1103                                          G_PARAM_READWRITE));
1104
1105         klass->changed = NULL;
1106 }
1107
1108
1109
1110 static void
1111 egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
1112                                 GConfEntry *entry, gpointer user_data)
1113 {
1114         EggRecentModel *model;
1115         GConfValue *value;
1116
1117         model = EGG_RECENT_MODEL (user_data);
1118
1119         g_return_if_fail (model != NULL);
1120
1121         if (model->priv->use_default_limit == FALSE)
1122                 return; /* ignore this key */
1123
1124         /* the key was unset, and the schema has apparently failed */
1125         if (entry == NULL)
1126                 return;
1127
1128         value = gconf_entry_get_value (entry);
1129
1130         if (value->type != GCONF_VALUE_INT) {
1131                 g_warning ("Expected GConfValue of type integer, "
1132                            "got something else");
1133         }
1134
1135
1136         egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
1137 }
1138
1139 static void
1140 egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
1141                                      GConfEntry *entry, gpointer user_data)
1142 {
1143
1144 }
1145
1146 static void
1147 egg_recent_model_init (EggRecentModel * model)
1148 {
1149         if (!gnome_vfs_init ()) {
1150                 g_warning ("gnome-vfs initialization failed.");
1151                 return;
1152         }
1153         
1154
1155         model->priv = g_new0 (EggRecentModelPrivate, 1);
1156
1157         model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
1158                                              g_get_home_dir ());
1159
1160         model->priv->mime_filter_values   = NULL;
1161         model->priv->group_filter_values  = NULL;
1162         model->priv->scheme_filter_values = NULL;
1163         
1164         model->priv->client = gconf_client_get_default ();
1165         gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR,
1166                               GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
1167
1168         model->priv->limit_change_notify_id =
1169                         gconf_client_notify_add (model->priv->client,
1170                                                  EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY,
1171                                                  egg_recent_model_limit_changed,
1172                                                  model, NULL, NULL);
1173
1174         model->priv->expiration_change_notify_id =
1175                         gconf_client_notify_add (model->priv->client,
1176                                                  EGG_RECENT_MODEL_EXPIRE_KEY,
1177                                                  egg_recent_model_expiration_changed,
1178                                                  model, NULL, NULL);
1179
1180         model->priv->expire_days = gconf_client_get_int (
1181                                         model->priv->client,
1182                                         EGG_RECENT_MODEL_EXPIRE_KEY,
1183                                         NULL);
1184                                         
1185 #if 0
1186         /* keep this out, for now */
1187         model->priv->limit = gconf_client_get_int (
1188                                         model->priv->client,
1189                                         EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL);
1190         model->priv->use_default_limit = TRUE;
1191 #endif
1192         model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
1193         model->priv->use_default_limit = FALSE;
1194
1195         model->priv->monitors = g_hash_table_new_full (
1196                                         g_str_hash, g_str_equal,
1197                                         (GDestroyNotify) g_free,
1198                                         (GDestroyNotify) gnome_vfs_monitor_cancel);
1199
1200         model->priv->monitor = NULL;
1201         egg_recent_model_monitor (model, TRUE);
1202 }
1203
1204
1205 /**
1206  * egg_recent_model_new:
1207  * @sort:  the type of sorting to use
1208  * @limit:  maximum number of items in the list
1209  *
1210  * This creates a new EggRecentModel object.
1211  *
1212  * Returns: a EggRecentModel object
1213  */
1214 EggRecentModel *
1215 egg_recent_model_new (EggRecentModelSort sort)
1216 {
1217         EggRecentModel *model;
1218
1219         model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
1220                                   "sort-type", sort, NULL));
1221
1222         g_return_val_if_fail (model, NULL);
1223
1224         return model;
1225 }
1226
1227 /**
1228  * egg_recent_model_add_full:
1229  * @model:  A EggRecentModel object.
1230  * @item:  A EggRecentItem
1231  *
1232  * This function adds an item to the list of recently used URIs.
1233  *
1234  * Returns: gboolean
1235  */
1236 gboolean
1237 egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
1238 {
1239         FILE *file;
1240         GList *list = NULL;
1241         gboolean ret = FALSE;
1242         gboolean updated = FALSE;
1243         char *uri;
1244         time_t t;
1245         
1246         g_return_val_if_fail (model != NULL, FALSE);
1247         g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1248
1249         uri = egg_recent_item_get_uri (item);
1250         if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
1251                 g_free (uri);
1252                 return FALSE;
1253         } else {
1254                 g_free (uri);
1255         }
1256
1257         file = egg_recent_model_open_file (model);
1258         g_return_val_if_fail (file != NULL, FALSE);
1259
1260         time (&t);
1261         egg_recent_item_set_timestamp (item, t);
1262
1263         if (egg_recent_model_lock_file (file)) {
1264
1265                 /* read existing stuff */
1266                 list = egg_recent_model_read (model, file);
1267
1268                 /* if it's already there, we just update it */
1269                 updated = egg_recent_model_update_item (list, item);
1270
1271                 if (!updated) {
1272                         list = g_list_prepend (list, item);
1273
1274                         egg_recent_model_enforce_limit (list,
1275                                                 EGG_RECENT_MODEL_MAX_ITEMS);
1276                 }
1277
1278                 /* write new stuff */
1279                 if (!egg_recent_model_write (model, file, list))
1280                         g_warning ("Write failed: %s", strerror (errno));
1281
1282                 if (!updated)
1283                         list = g_list_remove (list, item);
1284
1285                 EGG_RECENT_ITEM_LIST_UNREF (list);
1286                 ret = TRUE;
1287         } else {
1288                 g_warning ("Failed to lock:  %s", strerror (errno));
1289                 return FALSE;
1290         }
1291
1292         if (!egg_recent_model_unlock_file (file))
1293                 g_warning ("Failed to unlock: %s", strerror (errno));
1294
1295         fclose (file);
1296
1297         if (model->priv->monitor == NULL) {
1298                 /* since monitoring isn't working, at least give a
1299                  * local notification
1300                  */
1301                 egg_recent_model_changed (model);
1302         }
1303
1304         return ret;
1305 }
1306
1307 /**
1308  * egg_recent_model_add:
1309  * @model:  A EggRecentModel object.
1310  * @uri:  A string URI
1311  *
1312  * This function adds an item to the list of recently used URIs.
1313  *
1314  * Returns: gboolean
1315  */
1316 gboolean
1317 egg_recent_model_add (EggRecentModel *model, const gchar *uri)
1318 {
1319         EggRecentItem *item;
1320         gboolean ret = FALSE;
1321
1322         g_return_val_if_fail (model != NULL, FALSE);
1323         g_return_val_if_fail (uri != NULL, FALSE);
1324
1325         item = egg_recent_item_new_from_uri (uri);
1326
1327         g_return_val_if_fail (item != NULL, FALSE);
1328
1329         ret = egg_recent_model_add_full (model, item);
1330
1331         egg_recent_item_unref (item);
1332
1333         return ret;
1334 }
1335
1336
1337
1338 /**
1339  * egg_recent_model_delete:
1340  * @model:  A EggRecentModel object.
1341  * @uri: The URI you want to delete.
1342  *
1343  * This function deletes a URI from the file of recently used URIs.
1344  *
1345  * Returns: gboolean
1346  */
1347 gboolean
1348 egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
1349 {
1350         FILE *file;
1351         GList *list;
1352         unsigned int length;
1353         gboolean ret = FALSE;
1354
1355         g_return_val_if_fail (model != NULL, FALSE);
1356         g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1357         g_return_val_if_fail (uri != NULL, FALSE);
1358
1359         file = egg_recent_model_open_file (model);
1360         g_return_val_if_fail (file != NULL, FALSE);
1361
1362         if (egg_recent_model_lock_file (file)) {
1363                 list = egg_recent_model_read (model, file);
1364
1365                 if (list == NULL)
1366                         goto out;
1367
1368                 length = g_list_length (list);
1369
1370                 list = egg_recent_model_delete_from_list (list, uri);
1371                 
1372                 if (length == g_list_length (list)) {
1373                         /* nothing was deleted */
1374                         EGG_RECENT_ITEM_LIST_UNREF (list);
1375                 } else {
1376                         egg_recent_model_write (model, file, list);
1377                         EGG_RECENT_ITEM_LIST_UNREF (list);
1378                         ret = TRUE;
1379
1380                 }
1381         } else {
1382                 g_warning ("Failed to lock:  %s", strerror (errno));
1383                 return FALSE;
1384         }
1385
1386 out:
1387                 
1388         if (!egg_recent_model_unlock_file (file))
1389                 g_warning ("Failed to unlock: %s", strerror (errno));
1390
1391         fclose (file);
1392
1393         g_hash_table_remove (model->priv->monitors, uri);
1394
1395         if (model->priv->monitor == NULL && ret) {
1396                 /* since monitoring isn't working, at least give a
1397                  * local notification
1398                  */
1399                 egg_recent_model_changed (model);
1400         }
1401
1402         return ret;
1403 }
1404
1405
1406 /**
1407  * egg_recent_model_get_list:
1408  * @model:  A EggRecentModel object.
1409  *
1410  * This function gets the current contents of the file
1411  *
1412  * Returns: a GList
1413  */
1414 GList *
1415 egg_recent_model_get_list (EggRecentModel *model)
1416 {
1417         FILE *file;
1418         GList *list=NULL;
1419
1420         file = egg_recent_model_open_file (model);
1421         g_return_val_if_fail (file != NULL, NULL);
1422         
1423         if (egg_recent_model_lock_file (file)) {
1424                 list = egg_recent_model_read (model, file);
1425                 
1426         } else {
1427                 g_warning ("Failed to lock:  %s", strerror (errno));
1428                 fclose (file);
1429                 return NULL;
1430         }
1431
1432         if (!egg_recent_model_unlock_file (file))
1433                 g_warning ("Failed to unlock: %s", strerror (errno));
1434
1435         if (list != NULL) {
1436                 list = egg_recent_model_filter (model, list);
1437                 list = egg_recent_model_sort (model, list);
1438
1439                 egg_recent_model_enforce_limit (list, model->priv->limit);
1440         }
1441
1442         fclose (file);
1443
1444         return list;
1445 }
1446
1447
1448
1449 /**
1450  * egg_recent_model_set_limit:
1451  * @model:  A EggRecentModel object.
1452  * @limit:  The maximum length of the list
1453  *
1454  * This function sets the maximum length of the list.  Note:  This only affects
1455  * the length of the list emitted in the "changed" signal, not the list stored
1456  * on disk.
1457  *
1458  * Returns:  void
1459  */
1460 void
1461 egg_recent_model_set_limit (EggRecentModel *model, int limit)
1462 {
1463         model->priv->use_default_limit = FALSE;
1464
1465         egg_recent_model_set_limit_internal (model, limit);
1466 }
1467
1468 /**
1469  * egg_recent_model_get_limit:
1470  * @model:  A EggRecentModel object.
1471  *
1472  * This function gets the maximum length of the list. 
1473  *
1474  * Returns:  int
1475  */
1476 int
1477 egg_recent_model_get_limit (EggRecentModel *model)
1478 {
1479         return model->priv->limit;
1480 }
1481
1482
1483 /**
1484  * egg_recent_model_clear:
1485  * @model:  A EggRecentModel object.
1486  *
1487  * This function clears the contents of the file
1488  *
1489  * Returns: void
1490  */
1491 void
1492 egg_recent_model_clear (EggRecentModel *model)
1493 {
1494         FILE *file;
1495         int fd;
1496
1497         file = egg_recent_model_open_file (model);
1498         g_return_if_fail (file != NULL);
1499
1500         fd = fileno (file);
1501
1502         if (egg_recent_model_lock_file (file)) {
1503                 ftruncate (fd, 0);
1504         } else {
1505                 g_warning ("Failed to lock:  %s", strerror (errno));
1506                 return;
1507         }
1508
1509         if (!egg_recent_model_unlock_file (file))
1510                 g_warning ("Failed to unlock: %s", strerror (errno));
1511
1512         fclose (file);
1513 }
1514
1515
1516 /**
1517  * egg_recent_model_set_filter_mime_types:
1518  * @model:  A EggRecentModel object.
1519  *
1520  * Sets which mime types are allowed in the list.
1521  *
1522  * Returns: void
1523  */
1524 void
1525 egg_recent_model_set_filter_mime_types (EggRecentModel *model,
1526                                ...)
1527 {
1528         va_list valist;
1529         GSList *list = NULL;
1530         gchar *str;
1531
1532         g_return_if_fail (model != NULL);
1533
1534         if (model->priv->mime_filter_values != NULL) {
1535                 g_slist_foreach (model->priv->mime_filter_values,
1536                                  (GFunc) g_pattern_spec_free, NULL);
1537                 g_slist_free (model->priv->mime_filter_values);
1538                 model->priv->mime_filter_values = NULL;
1539         }
1540
1541         va_start (valist, model);
1542
1543         str = va_arg (valist, gchar*);
1544
1545         while (str != NULL) {
1546                 list = g_slist_prepend (list, g_pattern_spec_new (str));
1547
1548                 str = va_arg (valist, gchar*);
1549         }
1550
1551         va_end (valist);
1552
1553         model->priv->mime_filter_values = list;
1554 }
1555
1556 /**
1557  * egg_recent_model_set_filter_groups:
1558  * @model:  A EggRecentModel object.
1559  *
1560  * Sets which groups are allowed in the list.
1561  *
1562  * Returns: void
1563  */
1564 void
1565 egg_recent_model_set_filter_groups (EggRecentModel *model,
1566                                ...)
1567 {
1568         va_list valist;
1569         GSList *list = NULL;
1570         gchar *str;
1571
1572         g_return_if_fail (model != NULL);
1573
1574         if (model->priv->group_filter_values != NULL) {
1575                 g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL);
1576                 g_slist_free (model->priv->group_filter_values);
1577                 model->priv->group_filter_values = NULL;
1578         }
1579
1580         va_start (valist, model);
1581
1582         str = va_arg (valist, gchar*);
1583
1584         while (str != NULL) {
1585                 list = g_slist_prepend (list, g_strdup (str));
1586
1587                 str = va_arg (valist, gchar*);
1588         }
1589
1590         va_end (valist);
1591
1592         model->priv->group_filter_values = list;
1593 }
1594
1595 /**
1596  * egg_recent_model_set_filter_uri_schemes:
1597  * @model:  A EggRecentModel object.
1598  *
1599  * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
1600  *
1601  * Returns: void
1602  */
1603 void
1604 egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
1605 {
1606         va_list valist;
1607         GSList *list = NULL;
1608         gchar *str;
1609
1610         g_return_if_fail (model != NULL);
1611
1612         if (model->priv->scheme_filter_values != NULL) {
1613                 g_slist_foreach (model->priv->scheme_filter_values,
1614                                 (GFunc) g_pattern_spec_free, NULL);
1615                 g_slist_free (model->priv->scheme_filter_values);
1616                 model->priv->scheme_filter_values = NULL;
1617         }
1618
1619         va_start (valist, model);
1620
1621         str = va_arg (valist, gchar*);
1622
1623         while (str != NULL) {
1624                 list = g_slist_prepend (list, g_pattern_spec_new (str));
1625
1626                 str = va_arg (valist, gchar*);
1627         }
1628
1629         va_end (valist);
1630
1631         model->priv->scheme_filter_values = list;
1632 }
1633
1634 /**
1635  * egg_recent_model_set_sort:
1636  * @model:  A EggRecentModel object.
1637  * @sort:  A EggRecentModelSort type
1638  *
1639  * Sets the type of sorting to be used.
1640  *
1641  * Returns: void
1642  */
1643 void
1644 egg_recent_model_set_sort (EggRecentModel *model,
1645                              EggRecentModelSort sort)
1646 {
1647         g_return_if_fail (model != NULL);
1648         
1649         model->priv->sort_type = sort;
1650 }
1651
1652 /**
1653  * egg_recent_model_changed:
1654  * @model:  A EggRecentModel object.
1655  *
1656  * This function causes a "changed" signal to be emitted.
1657  *
1658  * Returns: void
1659  */
1660 void
1661 egg_recent_model_changed (EggRecentModel *model)
1662 {
1663         GList *list = NULL;
1664
1665         if (model->priv->limit > 0) {
1666                 list = egg_recent_model_get_list (model);
1667                 /* egg_recent_model_monitor_list (model, list); */
1668         
1669                 g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
1670                                list);
1671         }
1672
1673         if (list)
1674                 EGG_RECENT_ITEM_LIST_UNREF (list);
1675 }
1676
1677 static void
1678 egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
1679 {
1680         time_t current_time;
1681         time_t day_seconds;
1682
1683         time (&current_time);
1684         day_seconds = model->priv->expire_days*24*60*60;
1685
1686         while (list != NULL) {
1687                 EggRecentItem *item = list->data;
1688                 time_t timestamp;
1689
1690                 timestamp = egg_recent_item_get_timestamp (item);
1691
1692                 if ((timestamp+day_seconds) < current_time) {
1693                         gchar *uri = egg_recent_item_get_uri (item);
1694                         egg_recent_model_delete (model, uri);
1695
1696                         g_strdup (uri);
1697                 }
1698
1699                 list = list->next;
1700         }
1701 }
1702
1703
1704 /**
1705  * egg_recent_model_remove_expired:
1706  * @model:  A EggRecentModel object.
1707  *
1708  * Goes through the entire list, and removes any items that are older than
1709  * the user-specified expiration period.
1710  *
1711  * Returns: void
1712  */
1713 void
1714 egg_recent_model_remove_expired (EggRecentModel *model)
1715 {
1716         FILE *file;
1717         GList *list=NULL;
1718
1719         g_return_if_fail (model != NULL);
1720
1721         file = egg_recent_model_open_file (model);
1722         g_return_if_fail (file != NULL);
1723         
1724         if (egg_recent_model_lock_file (file)) {
1725                 list = egg_recent_model_read (model, file);
1726                 
1727         } else {
1728                 g_warning ("Failed to lock:  %s", strerror (errno));
1729                 return;
1730         }
1731
1732         if (!egg_recent_model_unlock_file (file))
1733                 g_warning ("Failed to unlock: %s", strerror (errno));
1734
1735         if (list != NULL) {
1736                 egg_recent_model_remove_expired_list (model, list);
1737                 EGG_RECENT_ITEM_LIST_UNREF (list);
1738         }
1739
1740         fclose (file);
1741 }
1742
1743 /**
1744  * egg_recent_model_get_type:
1745  *
1746  * This returns a GType representing a EggRecentModel object.
1747  *
1748  * Returns: a GType
1749  */
1750 GType
1751 egg_recent_model_get_type (void)
1752 {
1753         static GType egg_recent_model_type = 0;
1754
1755         if(!egg_recent_model_type) {
1756                 static const GTypeInfo egg_recent_model_info = {
1757                         sizeof (EggRecentModelClass),
1758                         NULL, /* base init */
1759                         NULL, /* base finalize */
1760                         (GClassInitFunc)egg_recent_model_class_init, /* class init */
1761                         NULL, /* class finalize */
1762                         NULL, /* class data */
1763                         sizeof (EggRecentModel),
1764                         0,
1765                         (GInstanceInitFunc) egg_recent_model_init
1766                 };
1767
1768                 egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
1769                                                         "EggRecentModel",
1770                                                         &egg_recent_model_info, 0);
1771         }
1772
1773         return egg_recent_model_type;
1774 }
1775