]> www.fi.muni.cz Git - evince.git/blob - egg-recent-model.c
8b3abe5eaaed964b487e2f34efdced0d89aac7d0
[evince.git] / 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         model->priv->changed_timeout = 0;
647
648         egg_recent_model_changed (model);
649
650         return FALSE;
651 }
652
653 static void
654 egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
655                                const gchar *monitor_uri,
656                                const gchar *info_uri,
657                                GnomeVFSMonitorEventType event_type,
658                                gpointer user_data)
659 {
660         EggRecentModel *model;
661
662         g_return_if_fail (user_data != NULL);
663         g_return_if_fail (EGG_IS_RECENT_MODEL (user_data));
664         model = EGG_RECENT_MODEL (user_data);
665
666         if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
667                 if (model->priv->changed_timeout > 0) {
668                         g_source_remove (model->priv->changed_timeout);
669                 }
670
671                 model->priv->changed_timeout = g_timeout_add (
672                         EGG_RECENT_MODEL_TIMEOUT_LENGTH,
673                         (GSourceFunc)egg_recent_model_changed_timeout,
674                         model);
675         }
676 }
677
678 static void
679 egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
680 {
681         if (should_monitor && model->priv->monitor == NULL) {
682                 char *uri;
683
684                 uri = gnome_vfs_get_uri_from_local_path (model->priv->path);
685
686                 gnome_vfs_monitor_add (&model->priv->monitor,
687                                        uri,
688                                        GNOME_VFS_MONITOR_FILE,
689                                        egg_recent_model_monitor_cb,
690                                        model);
691
692                 g_free (uri);
693
694                 /* if the above fails, don't worry about it.
695                  * local notifications will still happen
696                  */
697
698         } else if (!should_monitor && model->priv->monitor != NULL) {
699                 gnome_vfs_monitor_cancel (model->priv->monitor);
700                 model->priv->monitor = NULL;
701         }
702 }
703
704 static void
705 egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
706 {
707         model->priv->limit = limit;
708
709         if (limit <= 0)
710                 egg_recent_model_monitor (model, FALSE);
711         else {
712                 egg_recent_model_monitor (model, TRUE);
713                 egg_recent_model_changed (model);
714         }
715 }
716
717 static GList *
718 egg_recent_model_read (EggRecentModel *model, FILE *file)
719 {
720         GList *list=NULL;
721         gchar *content;
722         GMarkupParseContext *ctx;
723         ParseInfo info;
724         GError *error;
725
726         content = egg_recent_model_read_raw (model, file);
727
728         if (strlen (content) <= 0) {
729                 g_free (content);
730                 return NULL;
731         }
732
733         parse_info_init (&info);
734         
735         ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
736         
737         error = NULL;
738         if (!g_markup_parse_context_parse (ctx, content, strlen (content),
739                                            &error)) {
740                 g_warning (error->message);
741                 g_error_free (error);
742                 error = NULL;
743                 goto out;
744         }
745
746         error = NULL;
747         if (!g_markup_parse_context_end_parse (ctx, &error))
748                 goto out;
749         
750         g_markup_parse_context_free (ctx);
751 out:
752         list = info.items;
753
754         parse_info_free (&info);
755
756         g_free (content);
757
758         /*
759         g_print ("Total items: %d\n", g_list_length (list));
760         */
761
762         return list;
763 }
764
765
766 static gboolean
767 egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
768 {
769         GString *string;
770         gchar *data;
771         EggRecentItem *item;
772         const GList *groups;
773         int i;
774         int ret;
775         
776         string = g_string_new ("<?xml version=\"1.0\"?>\n");
777         string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
778
779         i=0;
780         while (list) {
781                 gchar *uri;
782                 gchar *mime_type;
783                 gchar *escaped_uri;
784                 time_t timestamp;
785                 item = (EggRecentItem *)list->data;
786
787
788                 uri = egg_recent_item_get_uri_utf8 (item);
789                 escaped_uri = g_markup_escape_text (uri,
790                                                     strlen (uri));
791                 g_free (uri);
792
793                 mime_type = egg_recent_item_get_mime_type (item);
794                 timestamp = egg_recent_item_get_timestamp (item);
795                 
796                 string = g_string_append (string, "  <" TAG_RECENT_ITEM ">\n");
797
798                 g_string_append_printf (string,
799                                 "    <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
800
801                 if (mime_type)
802                         g_string_append_printf (string,
803                                 "    <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
804                 else
805                         g_string_append_printf (string,
806                                 "    <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
807
808                 
809                 g_string_append_printf (string,
810                                 "    <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
811
812                 if (egg_recent_item_get_private (item))
813                         string = g_string_append (string,
814                                         "    <" TAG_PRIVATE "/>\n");
815
816                 /* write the groups */
817                 string = g_string_append (string,
818                                 "    <" TAG_GROUPS ">\n");
819                 groups = egg_recent_item_get_groups (item);
820
821                 if (groups == NULL && egg_recent_item_get_private (item))
822                         g_warning ("Item with URI \"%s\" marked as private, but"
823                                    " does not belong to any groups.\n", uri);
824                 
825                 while (groups) {
826                         const gchar *group = (const gchar *)groups->data;
827                         gchar *escaped_group;
828
829                         escaped_group = g_markup_escape_text (group, strlen(group));
830
831                         g_string_append_printf (string,
832                                         "      <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
833                                         escaped_group);
834
835                         g_free (escaped_group);
836
837                         groups = groups->next;
838                 }
839                 
840                 string = g_string_append (string, "    </" TAG_GROUPS ">\n");
841
842                 string = g_string_append (string,
843                                 "  </" TAG_RECENT_ITEM ">\n");
844
845                 g_free (mime_type);
846                 g_free (escaped_uri);
847
848                 list = list->next;
849                 i++;
850         }
851
852         string = g_string_append (string, "</" TAG_RECENT_FILES ">");
853
854         data = g_string_free (string, FALSE);
855
856         ret = egg_recent_model_write_raw (model, file, data);
857
858         g_free (data);
859
860         return ret;
861 }
862
863 static FILE *
864 egg_recent_model_open_file (EggRecentModel *model)
865 {
866         FILE *file;
867         mode_t prev_umask;
868         
869         file = fopen (model->priv->path, "r+");
870         if (file == NULL) {
871                 /* be paranoid */
872                 prev_umask = umask (077);
873
874                 file = fopen (model->priv->path, "w+");
875
876                 umask (prev_umask);
877
878                 g_return_val_if_fail (file != NULL, NULL);
879         }
880
881         return file;
882 }
883
884 static gboolean
885 egg_recent_model_lock_file (FILE *file)
886 {
887         int fd;
888         gint    try = 5;
889
890         rewind (file);
891         fd = fileno (file);
892
893         /* Attempt to lock the file 5 times,
894          * waiting a random interval (< 1 second) 
895          * in between attempts.
896          * We should really be doing asynchronous
897          * locking, but requires substantially larger
898          * changes.
899          */
900         
901         while (try > 0)
902         {
903                 int rand_interval;
904
905                 if (lockf (fd, F_TLOCK, 0) == 0)
906                         return TRUE;
907
908                 rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
909                          
910                 g_usleep (100000 * rand_interval);
911
912                 --try;
913         }
914
915         return FALSE;
916 }
917
918 static gboolean
919 egg_recent_model_unlock_file (FILE *file)
920 {
921         int fd;
922
923         rewind (file);
924         fd = fileno (file);
925
926         return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
927 }
928
929 static void
930 egg_recent_model_finalize (GObject *object)
931 {
932         EggRecentModel *model = EGG_RECENT_MODEL (object);
933
934         if (model->priv->changed_timeout > 0) {
935                 g_source_remove (model->priv->changed_timeout);
936         }
937
938         egg_recent_model_monitor (model, FALSE);
939
940
941         g_slist_foreach (model->priv->mime_filter_values,
942                          (GFunc) g_pattern_spec_free, NULL);
943         g_slist_free (model->priv->mime_filter_values);
944         model->priv->mime_filter_values = NULL;
945
946         g_slist_foreach (model->priv->scheme_filter_values,
947                          (GFunc) g_pattern_spec_free, NULL);
948         g_slist_free (model->priv->scheme_filter_values);
949         model->priv->scheme_filter_values = NULL;
950
951         g_slist_foreach (model->priv->group_filter_values,
952                          (GFunc) g_free, NULL);
953         g_slist_free (model->priv->group_filter_values);
954         model->priv->group_filter_values = NULL;
955
956
957         if (model->priv->limit_change_notify_id)
958                 gconf_client_notify_remove (model->priv->client,
959                                             model->priv->limit_change_notify_id);
960         model->priv->expiration_change_notify_id = 0;
961
962         if (model->priv->expiration_change_notify_id)
963                 gconf_client_notify_remove (model->priv->client,
964                                             model->priv->expiration_change_notify_id);
965         model->priv->expiration_change_notify_id = 0;
966
967         g_object_unref (model->priv->client);
968         model->priv->client = NULL;
969
970
971         g_free (model->priv->path);
972         model->priv->path = NULL;
973         
974         g_hash_table_destroy (model->priv->monitors);
975         model->priv->monitors = NULL;
976
977
978         g_free (model->priv);
979 }
980
981 static void
982 egg_recent_model_set_property (GObject *object,
983                                guint prop_id,
984                                const GValue *value,
985                                GParamSpec *pspec)
986 {
987         EggRecentModel *model = EGG_RECENT_MODEL (object);
988
989         switch (prop_id)
990         {
991                 case PROP_MIME_FILTERS:
992                         model->priv->mime_filter_values =
993                                 (GSList *)g_value_get_pointer (value);
994                 break;
995
996                 case PROP_GROUP_FILTERS:
997                         model->priv->group_filter_values =
998                                 (GSList *)g_value_get_pointer (value);
999                 break;
1000
1001                 case PROP_SCHEME_FILTERS:
1002                         model->priv->scheme_filter_values =
1003                                 (GSList *)g_value_get_pointer (value);
1004                 break;
1005
1006                 case PROP_SORT_TYPE:
1007                         model->priv->sort_type = g_value_get_int (value);
1008                 break;
1009
1010                 case PROP_LIMIT:
1011                         egg_recent_model_set_limit (model,
1012                                                 g_value_get_int (value));
1013                 break;
1014
1015                 default:
1016                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1017                 break;
1018         }
1019 }
1020
1021 static void
1022 egg_recent_model_get_property (GObject *object,
1023                                guint prop_id,
1024                                GValue *value,
1025                                GParamSpec *pspec)
1026 {
1027         EggRecentModel *model = EGG_RECENT_MODEL (object);
1028
1029         switch (prop_id)
1030         {
1031                 case PROP_MIME_FILTERS:
1032                         g_value_set_pointer (value, model->priv->mime_filter_values);
1033                 break;
1034
1035                 case PROP_GROUP_FILTERS:
1036                         g_value_set_pointer (value, model->priv->group_filter_values);
1037                 break;
1038
1039                 case PROP_SCHEME_FILTERS:
1040                         g_value_set_pointer (value, model->priv->scheme_filter_values);
1041                 break;
1042
1043                 case PROP_SORT_TYPE:
1044                         g_value_set_int (value, model->priv->sort_type);
1045                 break;
1046
1047                 case PROP_LIMIT:
1048                         g_value_set_int (value, model->priv->limit);
1049                 break;
1050
1051                 default:
1052                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1053                 break;
1054         }
1055 }
1056
1057 static void
1058 egg_recent_model_class_init (EggRecentModelClass * klass)
1059 {
1060         GObjectClass *object_class;
1061
1062         object_class = G_OBJECT_CLASS (klass);
1063         object_class->set_property = egg_recent_model_set_property;
1064         object_class->get_property = egg_recent_model_get_property;
1065         object_class->finalize     = egg_recent_model_finalize;
1066
1067         model_signals[CHANGED] = g_signal_new ("changed",
1068                         G_OBJECT_CLASS_TYPE (object_class),
1069                         G_SIGNAL_RUN_LAST,
1070                         G_STRUCT_OFFSET (EggRecentModelClass, changed),
1071                         NULL, NULL,
1072                         g_cclosure_marshal_VOID__POINTER,
1073                         G_TYPE_NONE, 1,
1074                         G_TYPE_POINTER);
1075
1076         
1077         g_object_class_install_property (object_class,
1078                                          PROP_MIME_FILTERS,
1079                                          g_param_spec_pointer ("mime-filters",
1080                                          "Mime Filters",
1081                                          "List of mime types to be allowed.",
1082                                          G_PARAM_READWRITE));
1083         
1084         g_object_class_install_property (object_class,
1085                                          PROP_GROUP_FILTERS,
1086                                          g_param_spec_pointer ("group-filters",
1087                                          "Group Filters",
1088                                          "List of groups to be allowed.",
1089                                          G_PARAM_READWRITE));
1090
1091         g_object_class_install_property (object_class,
1092                                          PROP_SCHEME_FILTERS,
1093                                          g_param_spec_pointer ("scheme-filters",
1094                                          "Scheme Filters",
1095                                          "List of URI schemes to be allowed.",
1096                                          G_PARAM_READWRITE));
1097
1098         g_object_class_install_property (object_class,
1099                                          PROP_SORT_TYPE,
1100                                          g_param_spec_int ("sort-type",
1101                                          "Sort Type",
1102                                          "Type of sorting to be done.",
1103                                          0, EGG_RECENT_MODEL_SORT_NONE,
1104                                          EGG_RECENT_MODEL_SORT_MRU,
1105                                          G_PARAM_READWRITE));
1106
1107         g_object_class_install_property (object_class,
1108                                          PROP_LIMIT,
1109                                          g_param_spec_int ("limit",
1110                                          "Limit",
1111                                          "Max number of items allowed.",
1112                                          -1, EGG_RECENT_MODEL_MAX_ITEMS,
1113                                          EGG_RECENT_MODEL_DEFAULT_LIMIT,
1114                                          G_PARAM_READWRITE));
1115
1116         klass->changed = NULL;
1117 }
1118
1119
1120
1121 static void
1122 egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
1123                                 GConfEntry *entry, gpointer user_data)
1124 {
1125         EggRecentModel *model;
1126         GConfValue *value;
1127
1128         model = EGG_RECENT_MODEL (user_data);
1129
1130         g_return_if_fail (model != NULL);
1131
1132         if (model->priv->use_default_limit == FALSE)
1133                 return; /* ignore this key */
1134
1135         /* the key was unset, and the schema has apparently failed */
1136         if (entry == NULL)
1137                 return;
1138
1139         value = gconf_entry_get_value (entry);
1140
1141         if (value->type != GCONF_VALUE_INT) {
1142                 g_warning ("Expected GConfValue of type integer, "
1143                            "got something else");
1144         }
1145
1146
1147         egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
1148 }
1149
1150 static void
1151 egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
1152                                      GConfEntry *entry, gpointer user_data)
1153 {
1154
1155 }
1156
1157 static void
1158 egg_recent_model_init (EggRecentModel * model)
1159 {
1160         if (!gnome_vfs_init ()) {
1161                 g_warning ("gnome-vfs initialization failed.");
1162                 return;
1163         }
1164         
1165
1166         model->priv = g_new0 (EggRecentModelPrivate, 1);
1167
1168         model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
1169                                              g_get_home_dir ());
1170
1171         model->priv->mime_filter_values   = NULL;
1172         model->priv->group_filter_values  = NULL;
1173         model->priv->scheme_filter_values = NULL;
1174         
1175         model->priv->client = gconf_client_get_default ();
1176         gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR,
1177                               GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
1178
1179         model->priv->limit_change_notify_id =
1180                         gconf_client_notify_add (model->priv->client,
1181                                                  EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY,
1182                                                  egg_recent_model_limit_changed,
1183                                                  model, NULL, NULL);
1184
1185         model->priv->expiration_change_notify_id =
1186                         gconf_client_notify_add (model->priv->client,
1187                                                  EGG_RECENT_MODEL_EXPIRE_KEY,
1188                                                  egg_recent_model_expiration_changed,
1189                                                  model, NULL, NULL);
1190
1191         model->priv->expire_days = gconf_client_get_int (
1192                                         model->priv->client,
1193                                         EGG_RECENT_MODEL_EXPIRE_KEY,
1194                                         NULL);
1195                                         
1196 #if 0
1197         /* keep this out, for now */
1198         model->priv->limit = gconf_client_get_int (
1199                                         model->priv->client,
1200                                         EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL);
1201         model->priv->use_default_limit = TRUE;
1202 #endif
1203         model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
1204         model->priv->use_default_limit = FALSE;
1205
1206         model->priv->monitors = g_hash_table_new_full (
1207                                         g_str_hash, g_str_equal,
1208                                         (GDestroyNotify) g_free,
1209                                         (GDestroyNotify) gnome_vfs_monitor_cancel);
1210
1211         model->priv->monitor = NULL;
1212         egg_recent_model_monitor (model, TRUE);
1213 }
1214
1215
1216 /**
1217  * egg_recent_model_new:
1218  * @sort:  the type of sorting to use
1219  * @limit:  maximum number of items in the list
1220  *
1221  * This creates a new EggRecentModel object.
1222  *
1223  * Returns: a EggRecentModel object
1224  */
1225 EggRecentModel *
1226 egg_recent_model_new (EggRecentModelSort sort)
1227 {
1228         EggRecentModel *model;
1229
1230         model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
1231                                   "sort-type", sort, NULL));
1232
1233         g_return_val_if_fail (model, NULL);
1234
1235         return model;
1236 }
1237
1238 /**
1239  * egg_recent_model_add_full:
1240  * @model:  A EggRecentModel object.
1241  * @item:  A EggRecentItem
1242  *
1243  * This function adds an item to the list of recently used URIs.
1244  *
1245  * Returns: gboolean
1246  */
1247 gboolean
1248 egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
1249 {
1250         FILE *file;
1251         GList *list = NULL;
1252         gboolean ret = FALSE;
1253         gboolean updated = FALSE;
1254         char *uri;
1255         time_t t;
1256         
1257         g_return_val_if_fail (model != NULL, FALSE);
1258         g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1259
1260         uri = egg_recent_item_get_uri (item);
1261         if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
1262                 g_free (uri);
1263                 return FALSE;
1264         } else {
1265                 g_free (uri);
1266         }
1267
1268         file = egg_recent_model_open_file (model);
1269         g_return_val_if_fail (file != NULL, FALSE);
1270
1271         time (&t);
1272         egg_recent_item_set_timestamp (item, t);
1273
1274         if (egg_recent_model_lock_file (file)) {
1275
1276                 /* read existing stuff */
1277                 list = egg_recent_model_read (model, file);
1278
1279                 /* if it's already there, we just update it */
1280                 updated = egg_recent_model_update_item (list, item);
1281
1282                 if (!updated) {
1283                         list = g_list_prepend (list, item);
1284
1285                         egg_recent_model_enforce_limit (list,
1286                                                 EGG_RECENT_MODEL_MAX_ITEMS);
1287                 }
1288
1289                 /* write new stuff */
1290                 if (!egg_recent_model_write (model, file, list))
1291                         g_warning ("Write failed: %s", strerror (errno));
1292
1293                 if (!updated)
1294                         list = g_list_remove (list, item);
1295
1296                 EGG_RECENT_ITEM_LIST_UNREF (list);
1297                 ret = TRUE;
1298         } else {
1299                 g_warning ("Failed to lock:  %s", strerror (errno));
1300                 fclose (file);
1301                 return FALSE;
1302         }
1303
1304         if (!egg_recent_model_unlock_file (file))
1305                 g_warning ("Failed to unlock: %s", strerror (errno));
1306
1307         fclose (file);
1308
1309         if (model->priv->monitor == NULL) {
1310                 /* since monitoring isn't working, at least give a
1311                  * local notification
1312                  */
1313                 egg_recent_model_changed (model);
1314         }
1315
1316         return ret;
1317 }
1318
1319 /**
1320  * egg_recent_model_add:
1321  * @model:  A EggRecentModel object.
1322  * @uri:  A string URI
1323  *
1324  * This function adds an item to the list of recently used URIs.
1325  *
1326  * Returns: gboolean
1327  */
1328 gboolean
1329 egg_recent_model_add (EggRecentModel *model, const gchar *uri)
1330 {
1331         EggRecentItem *item;
1332         gboolean ret = FALSE;
1333
1334         g_return_val_if_fail (model != NULL, FALSE);
1335         g_return_val_if_fail (uri != NULL, FALSE);
1336
1337         item = egg_recent_item_new_from_uri (uri);
1338
1339         g_return_val_if_fail (item != NULL, FALSE);
1340
1341         ret = egg_recent_model_add_full (model, item);
1342
1343         egg_recent_item_unref (item);
1344
1345         return ret;
1346 }
1347
1348
1349
1350 /**
1351  * egg_recent_model_delete:
1352  * @model:  A EggRecentModel object.
1353  * @uri: The URI you want to delete.
1354  *
1355  * This function deletes a URI from the file of recently used URIs.
1356  *
1357  * Returns: gboolean
1358  */
1359 gboolean
1360 egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
1361 {
1362         FILE *file;
1363         GList *list;
1364         unsigned int length;
1365         gboolean ret = FALSE;
1366
1367         g_return_val_if_fail (model != NULL, FALSE);
1368         g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1369         g_return_val_if_fail (uri != NULL, FALSE);
1370
1371         file = egg_recent_model_open_file (model);
1372         g_return_val_if_fail (file != NULL, FALSE);
1373
1374         if (egg_recent_model_lock_file (file)) {
1375                 list = egg_recent_model_read (model, file);
1376
1377                 if (list == NULL)
1378                         goto out;
1379
1380                 length = g_list_length (list);
1381
1382                 list = egg_recent_model_delete_from_list (list, uri);
1383                 
1384                 if (length == g_list_length (list)) {
1385                         /* nothing was deleted */
1386                         EGG_RECENT_ITEM_LIST_UNREF (list);
1387                 } else {
1388                         egg_recent_model_write (model, file, list);
1389                         EGG_RECENT_ITEM_LIST_UNREF (list);
1390                         ret = TRUE;
1391
1392                 }
1393         } else {
1394                 g_warning ("Failed to lock:  %s", strerror (errno));
1395                 return FALSE;
1396         }
1397
1398 out:
1399                 
1400         if (!egg_recent_model_unlock_file (file))
1401                 g_warning ("Failed to unlock: %s", strerror (errno));
1402
1403         fclose (file);
1404
1405         g_hash_table_remove (model->priv->monitors, uri);
1406
1407         if (model->priv->monitor == NULL && ret) {
1408                 /* since monitoring isn't working, at least give a
1409                  * local notification
1410                  */
1411                 egg_recent_model_changed (model);
1412         }
1413
1414         return ret;
1415 }
1416
1417
1418 /**
1419  * egg_recent_model_get_list:
1420  * @model:  A EggRecentModel object.
1421  *
1422  * This function gets the current contents of the file
1423  *
1424  * Returns: a GList
1425  */
1426 GList *
1427 egg_recent_model_get_list (EggRecentModel *model)
1428 {
1429         FILE *file;
1430         GList *list=NULL;
1431
1432         file = egg_recent_model_open_file (model);
1433         g_return_val_if_fail (file != NULL, NULL);
1434         
1435         if (egg_recent_model_lock_file (file)) {
1436                 list = egg_recent_model_read (model, file);
1437                 
1438         } else {
1439                 g_warning ("Failed to lock:  %s", strerror (errno));
1440                 fclose (file);
1441                 return NULL;
1442         }
1443
1444         if (!egg_recent_model_unlock_file (file))
1445                 g_warning ("Failed to unlock: %s", strerror (errno));
1446
1447         if (list != NULL) {
1448                 list = egg_recent_model_filter (model, list);
1449                 list = egg_recent_model_sort (model, list);
1450
1451                 egg_recent_model_enforce_limit (list, model->priv->limit);
1452         }
1453
1454         fclose (file);
1455
1456         return list;
1457 }
1458
1459
1460
1461 /**
1462  * egg_recent_model_set_limit:
1463  * @model:  A EggRecentModel object.
1464  * @limit:  The maximum length of the list
1465  *
1466  * This function sets the maximum length of the list.  Note:  This only affects
1467  * the length of the list emitted in the "changed" signal, not the list stored
1468  * on disk.
1469  *
1470  * Returns:  void
1471  */
1472 void
1473 egg_recent_model_set_limit (EggRecentModel *model, int limit)
1474 {
1475         model->priv->use_default_limit = FALSE;
1476
1477         egg_recent_model_set_limit_internal (model, limit);
1478 }
1479
1480 /**
1481  * egg_recent_model_get_limit:
1482  * @model:  A EggRecentModel object.
1483  *
1484  * This function gets the maximum length of the list. 
1485  *
1486  * Returns:  int
1487  */
1488 int
1489 egg_recent_model_get_limit (EggRecentModel *model)
1490 {
1491         return model->priv->limit;
1492 }
1493
1494
1495 /**
1496  * egg_recent_model_clear:
1497  * @model:  A EggRecentModel object.
1498  *
1499  * This function clears the contents of the file
1500  *
1501  * Returns: void
1502  */
1503 void
1504 egg_recent_model_clear (EggRecentModel *model)
1505 {
1506         FILE *file;
1507         int fd;
1508
1509         file = egg_recent_model_open_file (model);
1510         g_return_if_fail (file != NULL);
1511
1512         fd = fileno (file);
1513
1514         if (egg_recent_model_lock_file (file)) {
1515                 ftruncate (fd, 0);
1516         } else {
1517                 g_warning ("Failed to lock:  %s", strerror (errno));
1518                 return;
1519         }
1520
1521         if (!egg_recent_model_unlock_file (file))
1522                 g_warning ("Failed to unlock: %s", strerror (errno));
1523
1524         fclose (file);
1525 }
1526
1527
1528 /**
1529  * egg_recent_model_set_filter_mime_types:
1530  * @model:  A EggRecentModel object.
1531  *
1532  * Sets which mime types are allowed in the list.
1533  *
1534  * Returns: void
1535  */
1536 void
1537 egg_recent_model_set_filter_mime_types (EggRecentModel *model,
1538                                ...)
1539 {
1540         va_list valist;
1541         GSList *list = NULL;
1542         gchar *str;
1543
1544         g_return_if_fail (model != NULL);
1545
1546         if (model->priv->mime_filter_values != NULL) {
1547                 g_slist_foreach (model->priv->mime_filter_values,
1548                                  (GFunc) g_pattern_spec_free, NULL);
1549                 g_slist_free (model->priv->mime_filter_values);
1550                 model->priv->mime_filter_values = NULL;
1551         }
1552
1553         va_start (valist, model);
1554
1555         str = va_arg (valist, gchar*);
1556
1557         while (str != NULL) {
1558                 list = g_slist_prepend (list, g_pattern_spec_new (str));
1559
1560                 str = va_arg (valist, gchar*);
1561         }
1562
1563         va_end (valist);
1564
1565         model->priv->mime_filter_values = list;
1566 }
1567
1568 /**
1569  * egg_recent_model_set_filter_groups:
1570  * @model:  A EggRecentModel object.
1571  *
1572  * Sets which groups are allowed in the list.
1573  *
1574  * Returns: void
1575  */
1576 void
1577 egg_recent_model_set_filter_groups (EggRecentModel *model,
1578                                ...)
1579 {
1580         va_list valist;
1581         GSList *list = NULL;
1582         gchar *str;
1583
1584         g_return_if_fail (model != NULL);
1585
1586         if (model->priv->group_filter_values != NULL) {
1587                 g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL);
1588                 g_slist_free (model->priv->group_filter_values);
1589                 model->priv->group_filter_values = NULL;
1590         }
1591
1592         va_start (valist, model);
1593
1594         str = va_arg (valist, gchar*);
1595
1596         while (str != NULL) {
1597                 list = g_slist_prepend (list, g_strdup (str));
1598
1599                 str = va_arg (valist, gchar*);
1600         }
1601
1602         va_end (valist);
1603
1604         model->priv->group_filter_values = list;
1605 }
1606
1607 /**
1608  * egg_recent_model_set_filter_uri_schemes:
1609  * @model:  A EggRecentModel object.
1610  *
1611  * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
1612  *
1613  * Returns: void
1614  */
1615 void
1616 egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
1617 {
1618         va_list valist;
1619         GSList *list = NULL;
1620         gchar *str;
1621
1622         g_return_if_fail (model != NULL);
1623
1624         if (model->priv->scheme_filter_values != NULL) {
1625                 g_slist_foreach (model->priv->scheme_filter_values,
1626                                 (GFunc) g_pattern_spec_free, NULL);
1627                 g_slist_free (model->priv->scheme_filter_values);
1628                 model->priv->scheme_filter_values = NULL;
1629         }
1630
1631         va_start (valist, model);
1632
1633         str = va_arg (valist, gchar*);
1634
1635         while (str != NULL) {
1636                 list = g_slist_prepend (list, g_pattern_spec_new (str));
1637
1638                 str = va_arg (valist, gchar*);
1639         }
1640
1641         va_end (valist);
1642
1643         model->priv->scheme_filter_values = list;
1644 }
1645
1646 /**
1647  * egg_recent_model_set_sort:
1648  * @model:  A EggRecentModel object.
1649  * @sort:  A EggRecentModelSort type
1650  *
1651  * Sets the type of sorting to be used.
1652  *
1653  * Returns: void
1654  */
1655 void
1656 egg_recent_model_set_sort (EggRecentModel *model,
1657                              EggRecentModelSort sort)
1658 {
1659         g_return_if_fail (model != NULL);
1660         
1661         model->priv->sort_type = sort;
1662 }
1663
1664 /**
1665  * egg_recent_model_changed:
1666  * @model:  A EggRecentModel object.
1667  *
1668  * This function causes a "changed" signal to be emitted.
1669  *
1670  * Returns: void
1671  */
1672 void
1673 egg_recent_model_changed (EggRecentModel *model)
1674 {
1675         GList *list = NULL;
1676
1677         if (model->priv->limit > 0) {
1678                 list = egg_recent_model_get_list (model);
1679                 /* egg_recent_model_monitor_list (model, list); */
1680         
1681                 g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
1682                                list);
1683         }
1684
1685         if (list)
1686                 EGG_RECENT_ITEM_LIST_UNREF (list);
1687 }
1688
1689 static void
1690 egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
1691 {
1692         time_t current_time;
1693         time_t day_seconds;
1694
1695         time (&current_time);
1696         day_seconds = model->priv->expire_days*24*60*60;
1697
1698         while (list != NULL) {
1699                 EggRecentItem *item = list->data;
1700                 time_t timestamp;
1701
1702                 timestamp = egg_recent_item_get_timestamp (item);
1703
1704                 if ((timestamp+day_seconds) < current_time) {
1705                         gchar *uri = egg_recent_item_get_uri (item);
1706                         egg_recent_model_delete (model, uri);
1707
1708                         g_strdup (uri);
1709                 }
1710
1711                 list = list->next;
1712         }
1713 }
1714
1715
1716 /**
1717  * egg_recent_model_remove_expired:
1718  * @model:  A EggRecentModel object.
1719  *
1720  * Goes through the entire list, and removes any items that are older than
1721  * the user-specified expiration period.
1722  *
1723  * Returns: void
1724  */
1725 void
1726 egg_recent_model_remove_expired (EggRecentModel *model)
1727 {
1728         FILE *file;
1729         GList *list=NULL;
1730
1731         g_return_if_fail (model != NULL);
1732
1733         file = egg_recent_model_open_file (model);
1734         g_return_if_fail (file != NULL);
1735         
1736         if (egg_recent_model_lock_file (file)) {
1737                 list = egg_recent_model_read (model, file);
1738                 
1739         } else {
1740                 g_warning ("Failed to lock:  %s", strerror (errno));
1741                 return;
1742         }
1743
1744         if (!egg_recent_model_unlock_file (file))
1745                 g_warning ("Failed to unlock: %s", strerror (errno));
1746
1747         if (list != NULL) {
1748                 egg_recent_model_remove_expired_list (model, list);
1749                 EGG_RECENT_ITEM_LIST_UNREF (list);
1750         }
1751
1752         fclose (file);
1753 }
1754
1755 /**
1756  * egg_recent_model_get_type:
1757  *
1758  * This returns a GType representing a EggRecentModel object.
1759  *
1760  * Returns: a GType
1761  */
1762 GType
1763 egg_recent_model_get_type (void)
1764 {
1765         static GType egg_recent_model_type = 0;
1766
1767         if(!egg_recent_model_type) {
1768                 static const GTypeInfo egg_recent_model_info = {
1769                         sizeof (EggRecentModelClass),
1770                         NULL, /* base init */
1771                         NULL, /* base finalize */
1772                         (GClassInitFunc)egg_recent_model_class_init, /* class init */
1773                         NULL, /* class finalize */
1774                         NULL, /* class data */
1775                         sizeof (EggRecentModel),
1776                         0,
1777                         (GInstanceInitFunc) egg_recent_model_init
1778                 };
1779
1780                 egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
1781                                                         "EggRecentModel",
1782                                                         &egg_recent_model_info, 0);
1783         }
1784
1785         return egg_recent_model_type;
1786 }
1787