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