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