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