1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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.
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.
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.
18 * James Willcox <jwillcox@cs.indiana.edu>
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"
40 #define EGG_RECENT_MODEL_FILE_PATH "/.recently-used"
41 #define EGG_RECENT_MODEL_BUFFER_SIZE 8192
43 #define EGG_RECENT_MODEL_MAX_ITEMS 500
44 #define EGG_RECENT_MODEL_DEFAULT_LIMIT 10
45 #define EGG_RECENT_MODEL_TIMEOUT_LENGTH 200
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"
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 */
56 EggRecentModelSort sort_type; /* type of sorting to be done */
58 int limit; /* soft limit for length of the list */
59 int expire_days; /* number of days to hold an item */
61 char *path; /* path to the file we store stuff in */
65 GnomeVFSMonitorHandle *monitor;
68 gboolean use_default_limit;
70 guint limit_change_notify_id;
71 guint expiration_change_notify_id;
73 guint changed_timeout;
82 static GType model_signals[LAST_SIGNAL] = { 0 };
97 EggRecentItem *current_item;
112 typedef struct _ChangedData {
113 EggRecentModel *model;
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"
126 static void start_element_handler (GMarkupParseContext *context,
127 const gchar *element_name,
128 const gchar **attribute_names,
129 const gchar **attribute_values,
133 static void end_element_handler (GMarkupParseContext *context,
134 const gchar *element_name,
138 static void text_handler (GMarkupParseContext *context,
144 static void error_handler (GMarkupParseContext *context,
148 static GMarkupParser parser = {start_element_handler, end_element_handler,
154 egg_recent_model_string_match (const GSList *list, const gchar *str)
158 if (list == NULL || str == NULL)
164 if (g_pattern_match_string (tmp->data, str))
174 egg_recent_model_write_raw (EggRecentModel *model, FILE *file,
175 const gchar *content)
183 len = strlen (content);
186 if (fstat (fd, &sbuf) < 0)
187 g_warning ("Couldn't stat XML document.");
189 if ((off_t)len < sbuf.st_size) {
193 if (fputs (content, file) == EOF)
203 egg_recent_model_delete_from_list (GList *list,
214 EggRecentItem *item = tmp->data;
219 if (!strcmp (egg_recent_item_peek_uri (item), uri)) {
220 egg_recent_item_unref (item);
222 list = g_list_remove_link (list, tmp);
233 egg_recent_model_add_new_groups (EggRecentItem *item,
234 EggRecentItem *upd_item)
238 tmp = egg_recent_item_get_groups (upd_item);
241 char *group = tmp->data;
243 if (!egg_recent_item_in_group (item, group))
244 egg_recent_item_add_group (item, group);
251 egg_recent_model_update_item (GList *items, EggRecentItem *upd_item)
256 uri = egg_recent_item_peek_uri (upd_item);
261 EggRecentItem *item = tmp->data;
263 if (gnome_vfs_uris_match (egg_recent_item_peek_uri (item), uri)) {
264 egg_recent_item_set_timestamp (item, (time_t) -1);
266 egg_recent_model_add_new_groups (item, upd_item);
278 egg_recent_model_read_raw (EggRecentModel *model, FILE *file)
281 char buf[EGG_RECENT_MODEL_BUFFER_SIZE];
285 string = g_string_new (NULL);
286 while (fgets (buf, EGG_RECENT_MODEL_BUFFER_SIZE, file)) {
287 string = g_string_append (string, buf);
292 return g_string_free (string, FALSE);
298 parse_info_init (ParseInfo *info)
300 info->states = g_slist_prepend (NULL, STATE_START);
305 parse_info_free (ParseInfo *info)
307 g_slist_free (info->states);
311 push_state (ParseInfo *info,
314 info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
318 pop_state (ParseInfo *info)
320 g_return_if_fail (info->states != NULL);
322 info->states = g_slist_remove (info->states, info->states->data);
326 peek_state (ParseInfo *info)
328 g_return_val_if_fail (info->states != NULL, STATE_START);
330 return GPOINTER_TO_INT (info->states->data);
333 #define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
336 start_element_handler (GMarkupParseContext *context,
337 const gchar *element_name,
338 const gchar **attribute_names,
339 const gchar **attribute_values,
343 ParseInfo *info = (ParseInfo *)user_data;
345 if (ELEMENT_IS (TAG_RECENT_FILES))
346 push_state (info, STATE_RECENT_FILES);
347 else if (ELEMENT_IS (TAG_RECENT_ITEM)) {
348 info->current_item = egg_recent_item_new ();
349 push_state (info, STATE_RECENT_ITEM);
350 } else if (ELEMENT_IS (TAG_URI))
351 push_state (info, STATE_URI);
352 else if (ELEMENT_IS (TAG_MIME_TYPE))
353 push_state (info, STATE_MIME_TYPE);
354 else if (ELEMENT_IS (TAG_TIMESTAMP))
355 push_state (info, STATE_TIMESTAMP);
356 else if (ELEMENT_IS (TAG_PRIVATE)) {
357 push_state (info, STATE_PRIVATE);
358 egg_recent_item_set_private (info->current_item, TRUE);
359 } else if (ELEMENT_IS (TAG_GROUPS))
360 push_state (info, STATE_GROUPS);
361 else if (ELEMENT_IS (TAG_GROUP))
362 push_state (info, STATE_GROUP);
366 list_compare_func_mru (gpointer a, gpointer b)
368 EggRecentItem *item_a = (EggRecentItem *)a;
369 EggRecentItem *item_b = (EggRecentItem *)b;
371 return item_a->timestamp < item_b->timestamp;
375 list_compare_func_lru (gpointer a, gpointer b)
377 EggRecentItem *item_a = (EggRecentItem *)a;
378 EggRecentItem *item_b = (EggRecentItem *)b;
380 return item_a->timestamp > item_b->timestamp;
386 end_element_handler (GMarkupParseContext *context,
387 const gchar *element_name,
391 ParseInfo *info = (ParseInfo *)user_data;
393 switch (peek_state (info)) {
394 case STATE_RECENT_ITEM:
395 info->items = g_list_append (info->items,
397 if (info->current_item->uri == NULL ||
398 strlen (info->current_item->uri) == 0)
399 g_warning ("URI NOT LOADED");
409 text_handler (GMarkupParseContext *context,
415 ParseInfo *info = (ParseInfo *)user_data;
417 switch (peek_state (info)) {
419 case STATE_RECENT_FILES:
420 case STATE_RECENT_ITEM:
425 egg_recent_item_set_uri (info->current_item, text);
427 case STATE_MIME_TYPE:
428 egg_recent_item_set_mime_type (info->current_item,
431 case STATE_TIMESTAMP:
432 egg_recent_item_set_timestamp (info->current_item,
433 (time_t)atoi (text));
436 egg_recent_item_add_group (info->current_item,
444 error_handler (GMarkupParseContext *context,
448 g_warning ("Error in parse: %s", error->message);
452 egg_recent_model_enforce_limit (GList *list, int limit)
457 /* limit < 0 means unlimited */
461 len = g_list_length (list);
466 end = g_list_nth (list, limit-1);
471 EGG_RECENT_ITEM_LIST_UNREF (next);
476 egg_recent_model_sort (EggRecentModel *model, GList *list)
478 switch (model->priv->sort_type) {
479 case EGG_RECENT_MODEL_SORT_MRU:
480 list = g_list_sort (list,
481 (GCompareFunc)list_compare_func_mru);
483 case EGG_RECENT_MODEL_SORT_LRU:
484 list = g_list_sort (list,
485 (GCompareFunc)list_compare_func_lru);
487 case EGG_RECENT_MODEL_SORT_NONE:
495 egg_recent_model_group_match (EggRecentItem *item, GSList *groups)
501 while (tmp != NULL) {
502 const gchar * group = (const gchar *)tmp->data;
504 if (egg_recent_item_in_group (item, group))
514 egg_recent_model_filter (EggRecentModel *model,
518 GList *newlist = NULL;
522 g_return_val_if_fail (list != NULL, NULL);
525 gboolean pass_mime_test = FALSE;
526 gboolean pass_group_test = FALSE;
527 gboolean pass_scheme_test = FALSE;
528 item = (EggRecentItem *)list->data;
531 uri = egg_recent_item_get_uri (item);
533 /* filter by mime type */
534 if (model->priv->mime_filter_values != NULL) {
535 mime_type = egg_recent_item_get_mime_type (item);
537 if (egg_recent_model_string_match
538 (model->priv->mime_filter_values,
540 pass_mime_test = TRUE;
544 pass_mime_test = TRUE;
546 /* filter by group */
547 if (pass_mime_test && model->priv->group_filter_values != NULL) {
548 if (egg_recent_model_group_match
549 (item, model->priv->group_filter_values))
550 pass_group_test = TRUE;
551 } else if (egg_recent_item_get_private (item)) {
552 pass_group_test = FALSE;
554 pass_group_test = TRUE;
556 /* filter by URI scheme */
557 if (pass_mime_test && pass_group_test &&
558 model->priv->scheme_filter_values != NULL) {
561 scheme = gnome_vfs_get_uri_scheme (uri);
563 if (egg_recent_model_string_match
564 (model->priv->scheme_filter_values, scheme))
565 pass_scheme_test = TRUE;
569 pass_scheme_test = TRUE;
571 if (pass_mime_test && pass_group_test && pass_scheme_test)
572 newlist = g_list_prepend (newlist, item);
578 newlist = g_list_reverse (newlist);
590 egg_recent_model_monitor_list_cb (GnomeVFSMonitorHandle *handle,
591 const gchar *monitor_uri,
592 const gchar *info_uri,
593 GnomeVFSMonitorEventType event_type,
596 EggRecentModel *model;
598 model = EGG_RECENT_MODEL (user_data);
600 if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED) {
601 egg_recent_model_delete (model, monitor_uri);
602 g_hash_table_remove (model->priv->monitors, monitor_uri);
609 egg_recent_model_monitor_list (EggRecentModel *model, GList *list)
615 EggRecentItem *item = (EggRecentItem *)tmp->data;
616 GnomeVFSMonitorHandle *handle;
622 uri = egg_recent_item_get_uri (item);
623 if (g_hash_table_lookup (model->priv->monitors, uri)) {
624 /* already monitoring this one */
629 res = gnome_vfs_monitor_add (&handle, uri,
630 GNOME_VFS_MONITOR_FILE,
631 egg_recent_model_monitor_list_cb,
634 if (res == GNOME_VFS_OK)
635 g_hash_table_insert (model->priv->monitors, uri, handle);
644 egg_recent_model_changed_timeout (EggRecentModel *model)
646 egg_recent_model_changed (model);
652 egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
653 const gchar *monitor_uri,
654 const gchar *info_uri,
655 GnomeVFSMonitorEventType event_type,
658 EggRecentModel *model;
660 g_return_if_fail (user_data != NULL);
661 g_return_if_fail (EGG_IS_RECENT_MODEL (user_data));
662 model = EGG_RECENT_MODEL (user_data);
664 if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
665 if (model->priv->changed_timeout > 0) {
666 g_source_remove (model->priv->changed_timeout);
669 model->priv->changed_timeout = g_timeout_add (
670 EGG_RECENT_MODEL_TIMEOUT_LENGTH,
671 (GSourceFunc)egg_recent_model_changed_timeout,
677 egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
679 if (should_monitor && model->priv->monitor == NULL) {
681 gnome_vfs_monitor_add (&model->priv->monitor,
683 GNOME_VFS_MONITOR_FILE,
684 egg_recent_model_monitor_cb,
687 /* if the above fails, don't worry about it.
688 * local notifications will still happen
691 } else if (!should_monitor && model->priv->monitor != NULL) {
692 gnome_vfs_monitor_cancel (model->priv->monitor);
693 model->priv->monitor = NULL;
698 egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
700 model->priv->limit = limit;
703 egg_recent_model_monitor (model, FALSE);
705 egg_recent_model_monitor (model, TRUE);
706 egg_recent_model_changed (model);
711 egg_recent_model_read (EggRecentModel *model, FILE *file)
715 GMarkupParseContext *ctx;
719 content = egg_recent_model_read_raw (model, file);
721 if (strlen (content) <= 0) {
726 parse_info_init (&info);
728 ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
731 if (!g_markup_parse_context_parse (ctx, content, strlen (content),
733 g_warning (error->message);
734 g_error_free (error);
740 if (!g_markup_parse_context_end_parse (ctx, &error))
743 g_markup_parse_context_free (ctx);
747 parse_info_free (&info);
752 g_print ("Total items: %d\n", g_list_length (list));
760 egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
769 string = g_string_new ("<?xml version=\"1.0\"?>\n");
770 string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
778 item = (EggRecentItem *)list->data;
781 uri = egg_recent_item_get_uri_utf8 (item);
782 escaped_uri = g_markup_escape_text (uri,
786 mime_type = egg_recent_item_get_mime_type (item);
787 timestamp = egg_recent_item_get_timestamp (item);
789 string = g_string_append (string, " <" TAG_RECENT_ITEM ">\n");
791 g_string_append_printf (string,
792 " <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
795 g_string_append_printf (string,
796 " <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
798 g_string_append_printf (string,
799 " <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
802 g_string_append_printf (string,
803 " <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
805 if (egg_recent_item_get_private (item))
806 string = g_string_append (string,
807 " <" TAG_PRIVATE "/>\n");
809 /* write the groups */
810 string = g_string_append (string,
811 " <" TAG_GROUPS ">\n");
812 groups = egg_recent_item_get_groups (item);
814 if (groups == NULL && egg_recent_item_get_private (item))
815 g_warning ("Item with URI \"%s\" marked as private, but"
816 " does not belong to any groups.\n", uri);
819 const gchar *group = (const gchar *)groups->data;
820 gchar *escaped_group;
822 escaped_group = g_markup_escape_text (group, strlen(group));
824 g_string_append_printf (string,
825 " <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
828 g_free (escaped_group);
830 groups = groups->next;
833 string = g_string_append (string, " </" TAG_GROUPS ">\n");
835 string = g_string_append (string,
836 " </" TAG_RECENT_ITEM ">\n");
839 g_free (escaped_uri);
845 string = g_string_append (string, "</" TAG_RECENT_FILES ">");
847 data = g_string_free (string, FALSE);
849 ret = egg_recent_model_write_raw (model, file, data);
857 egg_recent_model_open_file (EggRecentModel *model)
862 file = fopen (model->priv->path, "r+");
865 prev_umask = umask (077);
867 file = fopen (model->priv->path, "w+");
871 g_return_val_if_fail (file != NULL, NULL);
878 egg_recent_model_lock_file (FILE *file)
886 /* Attempt to lock the file 5 times,
887 * waiting a random interval (< 1 second)
888 * in between attempts.
889 * We should really be doing asynchronous
890 * locking, but requires substantially larger
898 if (lockf (fd, F_TLOCK, 0) == 0)
901 rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
903 g_usleep (100000 * rand_interval);
912 egg_recent_model_unlock_file (FILE *file)
919 return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
923 egg_recent_model_finalize (GObject *object)
925 EggRecentModel *model = EGG_RECENT_MODEL (object);
927 egg_recent_model_monitor (model, FALSE);
930 g_slist_foreach (model->priv->mime_filter_values,
931 (GFunc) g_pattern_spec_free, NULL);
932 g_slist_free (model->priv->mime_filter_values);
933 model->priv->mime_filter_values = NULL;
935 g_slist_foreach (model->priv->scheme_filter_values,
936 (GFunc) g_pattern_spec_free, NULL);
937 g_slist_free (model->priv->scheme_filter_values);
938 model->priv->scheme_filter_values = NULL;
940 g_slist_foreach (model->priv->group_filter_values,
941 (GFunc) g_free, NULL);
942 g_slist_free (model->priv->group_filter_values);
943 model->priv->group_filter_values = NULL;
946 if (model->priv->limit_change_notify_id)
947 gconf_client_notify_remove (model->priv->client,
948 model->priv->limit_change_notify_id);
949 model->priv->expiration_change_notify_id = 0;
951 if (model->priv->expiration_change_notify_id)
952 gconf_client_notify_remove (model->priv->client,
953 model->priv->expiration_change_notify_id);
954 model->priv->expiration_change_notify_id = 0;
956 g_object_unref (model->priv->client);
957 model->priv->client = NULL;
960 g_free (model->priv->path);
961 model->priv->path = NULL;
963 g_hash_table_destroy (model->priv->monitors);
964 model->priv->monitors = NULL;
967 g_free (model->priv);
971 egg_recent_model_set_property (GObject *object,
976 EggRecentModel *model = EGG_RECENT_MODEL (object);
980 case PROP_MIME_FILTERS:
981 model->priv->mime_filter_values =
982 (GSList *)g_value_get_pointer (value);
985 case PROP_GROUP_FILTERS:
986 model->priv->group_filter_values =
987 (GSList *)g_value_get_pointer (value);
990 case PROP_SCHEME_FILTERS:
991 model->priv->scheme_filter_values =
992 (GSList *)g_value_get_pointer (value);
996 model->priv->sort_type = g_value_get_int (value);
1000 egg_recent_model_set_limit (model,
1001 g_value_get_int (value));
1005 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1011 egg_recent_model_get_property (GObject *object,
1016 EggRecentModel *model = EGG_RECENT_MODEL (object);
1020 case PROP_MIME_FILTERS:
1021 g_value_set_pointer (value, model->priv->mime_filter_values);
1024 case PROP_GROUP_FILTERS:
1025 g_value_set_pointer (value, model->priv->group_filter_values);
1028 case PROP_SCHEME_FILTERS:
1029 g_value_set_pointer (value, model->priv->scheme_filter_values);
1032 case PROP_SORT_TYPE:
1033 g_value_set_int (value, model->priv->sort_type);
1037 g_value_set_int (value, model->priv->limit);
1041 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1047 egg_recent_model_class_init (EggRecentModelClass * klass)
1049 GObjectClass *object_class;
1051 object_class = G_OBJECT_CLASS (klass);
1052 object_class->set_property = egg_recent_model_set_property;
1053 object_class->get_property = egg_recent_model_get_property;
1054 object_class->finalize = egg_recent_model_finalize;
1056 model_signals[CHANGED] = g_signal_new ("changed",
1057 G_OBJECT_CLASS_TYPE (object_class),
1059 G_STRUCT_OFFSET (EggRecentModelClass, changed),
1061 g_cclosure_marshal_VOID__POINTER,
1066 g_object_class_install_property (object_class,
1068 g_param_spec_pointer ("mime-filters",
1070 "List of mime types to be allowed.",
1071 G_PARAM_READWRITE));
1073 g_object_class_install_property (object_class,
1075 g_param_spec_pointer ("group-filters",
1077 "List of groups to be allowed.",
1078 G_PARAM_READWRITE));
1080 g_object_class_install_property (object_class,
1081 PROP_SCHEME_FILTERS,
1082 g_param_spec_pointer ("scheme-filters",
1084 "List of URI schemes to be allowed.",
1085 G_PARAM_READWRITE));
1087 g_object_class_install_property (object_class,
1089 g_param_spec_int ("sort-type",
1091 "Type of sorting to be done.",
1092 0, EGG_RECENT_MODEL_SORT_NONE,
1093 EGG_RECENT_MODEL_SORT_MRU,
1094 G_PARAM_READWRITE));
1096 g_object_class_install_property (object_class,
1098 g_param_spec_int ("limit",
1100 "Max number of items allowed.",
1101 -1, EGG_RECENT_MODEL_MAX_ITEMS,
1102 EGG_RECENT_MODEL_DEFAULT_LIMIT,
1103 G_PARAM_READWRITE));
1105 klass->changed = NULL;
1111 egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
1112 GConfEntry *entry, gpointer user_data)
1114 EggRecentModel *model;
1117 model = EGG_RECENT_MODEL (user_data);
1119 g_return_if_fail (model != NULL);
1121 if (model->priv->use_default_limit == FALSE)
1122 return; /* ignore this key */
1124 /* the key was unset, and the schema has apparently failed */
1128 value = gconf_entry_get_value (entry);
1130 if (value->type != GCONF_VALUE_INT) {
1131 g_warning ("Expected GConfValue of type integer, "
1132 "got something else");
1136 egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
1140 egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
1141 GConfEntry *entry, gpointer user_data)
1147 egg_recent_model_init (EggRecentModel * model)
1149 if (!gnome_vfs_init ()) {
1150 g_warning ("gnome-vfs initialization failed.");
1155 model->priv = g_new0 (EggRecentModelPrivate, 1);
1157 model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
1160 model->priv->mime_filter_values = NULL;
1161 model->priv->group_filter_values = NULL;
1162 model->priv->scheme_filter_values = NULL;
1164 model->priv->client = gconf_client_get_default ();
1165 gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR,
1166 GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
1168 model->priv->limit_change_notify_id =
1169 gconf_client_notify_add (model->priv->client,
1170 EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY,
1171 egg_recent_model_limit_changed,
1174 model->priv->expiration_change_notify_id =
1175 gconf_client_notify_add (model->priv->client,
1176 EGG_RECENT_MODEL_EXPIRE_KEY,
1177 egg_recent_model_expiration_changed,
1180 model->priv->expire_days = gconf_client_get_int (
1181 model->priv->client,
1182 EGG_RECENT_MODEL_EXPIRE_KEY,
1186 /* keep this out, for now */
1187 model->priv->limit = gconf_client_get_int (
1188 model->priv->client,
1189 EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL);
1190 model->priv->use_default_limit = TRUE;
1192 model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
1193 model->priv->use_default_limit = FALSE;
1195 model->priv->monitors = g_hash_table_new_full (
1196 g_str_hash, g_str_equal,
1197 (GDestroyNotify) g_free,
1198 (GDestroyNotify) gnome_vfs_monitor_cancel);
1200 model->priv->monitor = NULL;
1201 egg_recent_model_monitor (model, TRUE);
1206 * egg_recent_model_new:
1207 * @sort: the type of sorting to use
1208 * @limit: maximum number of items in the list
1210 * This creates a new EggRecentModel object.
1212 * Returns: a EggRecentModel object
1215 egg_recent_model_new (EggRecentModelSort sort)
1217 EggRecentModel *model;
1219 model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
1220 "sort-type", sort, NULL));
1222 g_return_val_if_fail (model, NULL);
1228 * egg_recent_model_add_full:
1229 * @model: A EggRecentModel object.
1230 * @item: A EggRecentItem
1232 * This function adds an item to the list of recently used URIs.
1237 egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
1241 gboolean ret = FALSE;
1242 gboolean updated = FALSE;
1246 g_return_val_if_fail (model != NULL, FALSE);
1247 g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1249 uri = egg_recent_item_get_uri (item);
1250 if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
1257 file = egg_recent_model_open_file (model);
1258 g_return_val_if_fail (file != NULL, FALSE);
1261 egg_recent_item_set_timestamp (item, t);
1263 if (egg_recent_model_lock_file (file)) {
1265 /* read existing stuff */
1266 list = egg_recent_model_read (model, file);
1268 /* if it's already there, we just update it */
1269 updated = egg_recent_model_update_item (list, item);
1272 list = g_list_prepend (list, item);
1274 egg_recent_model_enforce_limit (list,
1275 EGG_RECENT_MODEL_MAX_ITEMS);
1278 /* write new stuff */
1279 if (!egg_recent_model_write (model, file, list))
1280 g_warning ("Write failed: %s", strerror (errno));
1283 list = g_list_remove (list, item);
1285 EGG_RECENT_ITEM_LIST_UNREF (list);
1288 g_warning ("Failed to lock: %s", strerror (errno));
1292 if (!egg_recent_model_unlock_file (file))
1293 g_warning ("Failed to unlock: %s", strerror (errno));
1297 if (model->priv->monitor == NULL) {
1298 /* since monitoring isn't working, at least give a
1299 * local notification
1301 egg_recent_model_changed (model);
1308 * egg_recent_model_add:
1309 * @model: A EggRecentModel object.
1310 * @uri: A string URI
1312 * This function adds an item to the list of recently used URIs.
1317 egg_recent_model_add (EggRecentModel *model, const gchar *uri)
1319 EggRecentItem *item;
1320 gboolean ret = FALSE;
1322 g_return_val_if_fail (model != NULL, FALSE);
1323 g_return_val_if_fail (uri != NULL, FALSE);
1325 item = egg_recent_item_new_from_uri (uri);
1327 g_return_val_if_fail (item != NULL, FALSE);
1329 ret = egg_recent_model_add_full (model, item);
1331 egg_recent_item_unref (item);
1339 * egg_recent_model_delete:
1340 * @model: A EggRecentModel object.
1341 * @uri: The URI you want to delete.
1343 * This function deletes a URI from the file of recently used URIs.
1348 egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
1352 unsigned int length;
1353 gboolean ret = FALSE;
1355 g_return_val_if_fail (model != NULL, FALSE);
1356 g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1357 g_return_val_if_fail (uri != NULL, FALSE);
1359 file = egg_recent_model_open_file (model);
1360 g_return_val_if_fail (file != NULL, FALSE);
1362 if (egg_recent_model_lock_file (file)) {
1363 list = egg_recent_model_read (model, file);
1368 length = g_list_length (list);
1370 list = egg_recent_model_delete_from_list (list, uri);
1372 if (length == g_list_length (list)) {
1373 /* nothing was deleted */
1374 EGG_RECENT_ITEM_LIST_UNREF (list);
1376 egg_recent_model_write (model, file, list);
1377 EGG_RECENT_ITEM_LIST_UNREF (list);
1382 g_warning ("Failed to lock: %s", strerror (errno));
1388 if (!egg_recent_model_unlock_file (file))
1389 g_warning ("Failed to unlock: %s", strerror (errno));
1393 g_hash_table_remove (model->priv->monitors, uri);
1395 if (model->priv->monitor == NULL && ret) {
1396 /* since monitoring isn't working, at least give a
1397 * local notification
1399 egg_recent_model_changed (model);
1407 * egg_recent_model_get_list:
1408 * @model: A EggRecentModel object.
1410 * This function gets the current contents of the file
1415 egg_recent_model_get_list (EggRecentModel *model)
1420 file = egg_recent_model_open_file (model);
1421 g_return_val_if_fail (file != NULL, NULL);
1423 if (egg_recent_model_lock_file (file)) {
1424 list = egg_recent_model_read (model, file);
1427 g_warning ("Failed to lock: %s", strerror (errno));
1432 if (!egg_recent_model_unlock_file (file))
1433 g_warning ("Failed to unlock: %s", strerror (errno));
1436 list = egg_recent_model_filter (model, list);
1437 list = egg_recent_model_sort (model, list);
1439 egg_recent_model_enforce_limit (list, model->priv->limit);
1450 * egg_recent_model_set_limit:
1451 * @model: A EggRecentModel object.
1452 * @limit: The maximum length of the list
1454 * This function sets the maximum length of the list. Note: This only affects
1455 * the length of the list emitted in the "changed" signal, not the list stored
1461 egg_recent_model_set_limit (EggRecentModel *model, int limit)
1463 model->priv->use_default_limit = FALSE;
1465 egg_recent_model_set_limit_internal (model, limit);
1469 * egg_recent_model_get_limit:
1470 * @model: A EggRecentModel object.
1472 * This function gets the maximum length of the list.
1477 egg_recent_model_get_limit (EggRecentModel *model)
1479 return model->priv->limit;
1484 * egg_recent_model_clear:
1485 * @model: A EggRecentModel object.
1487 * This function clears the contents of the file
1492 egg_recent_model_clear (EggRecentModel *model)
1497 file = egg_recent_model_open_file (model);
1498 g_return_if_fail (file != NULL);
1502 if (egg_recent_model_lock_file (file)) {
1505 g_warning ("Failed to lock: %s", strerror (errno));
1509 if (!egg_recent_model_unlock_file (file))
1510 g_warning ("Failed to unlock: %s", strerror (errno));
1517 * egg_recent_model_set_filter_mime_types:
1518 * @model: A EggRecentModel object.
1520 * Sets which mime types are allowed in the list.
1525 egg_recent_model_set_filter_mime_types (EggRecentModel *model,
1529 GSList *list = NULL;
1532 g_return_if_fail (model != NULL);
1534 if (model->priv->mime_filter_values != NULL) {
1535 g_slist_foreach (model->priv->mime_filter_values,
1536 (GFunc) g_pattern_spec_free, NULL);
1537 g_slist_free (model->priv->mime_filter_values);
1538 model->priv->mime_filter_values = NULL;
1541 va_start (valist, model);
1543 str = va_arg (valist, gchar*);
1545 while (str != NULL) {
1546 list = g_slist_prepend (list, g_pattern_spec_new (str));
1548 str = va_arg (valist, gchar*);
1553 model->priv->mime_filter_values = list;
1557 * egg_recent_model_set_filter_groups:
1558 * @model: A EggRecentModel object.
1560 * Sets which groups are allowed in the list.
1565 egg_recent_model_set_filter_groups (EggRecentModel *model,
1569 GSList *list = NULL;
1572 g_return_if_fail (model != NULL);
1574 if (model->priv->group_filter_values != NULL) {
1575 g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL);
1576 g_slist_free (model->priv->group_filter_values);
1577 model->priv->group_filter_values = NULL;
1580 va_start (valist, model);
1582 str = va_arg (valist, gchar*);
1584 while (str != NULL) {
1585 list = g_slist_prepend (list, g_strdup (str));
1587 str = va_arg (valist, gchar*);
1592 model->priv->group_filter_values = list;
1596 * egg_recent_model_set_filter_uri_schemes:
1597 * @model: A EggRecentModel object.
1599 * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
1604 egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
1607 GSList *list = NULL;
1610 g_return_if_fail (model != NULL);
1612 if (model->priv->scheme_filter_values != NULL) {
1613 g_slist_foreach (model->priv->scheme_filter_values,
1614 (GFunc) g_pattern_spec_free, NULL);
1615 g_slist_free (model->priv->scheme_filter_values);
1616 model->priv->scheme_filter_values = NULL;
1619 va_start (valist, model);
1621 str = va_arg (valist, gchar*);
1623 while (str != NULL) {
1624 list = g_slist_prepend (list, g_pattern_spec_new (str));
1626 str = va_arg (valist, gchar*);
1631 model->priv->scheme_filter_values = list;
1635 * egg_recent_model_set_sort:
1636 * @model: A EggRecentModel object.
1637 * @sort: A EggRecentModelSort type
1639 * Sets the type of sorting to be used.
1644 egg_recent_model_set_sort (EggRecentModel *model,
1645 EggRecentModelSort sort)
1647 g_return_if_fail (model != NULL);
1649 model->priv->sort_type = sort;
1653 * egg_recent_model_changed:
1654 * @model: A EggRecentModel object.
1656 * This function causes a "changed" signal to be emitted.
1661 egg_recent_model_changed (EggRecentModel *model)
1665 if (model->priv->limit > 0) {
1666 list = egg_recent_model_get_list (model);
1667 /* egg_recent_model_monitor_list (model, list); */
1669 g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
1674 EGG_RECENT_ITEM_LIST_UNREF (list);
1678 egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
1680 time_t current_time;
1683 time (¤t_time);
1684 day_seconds = model->priv->expire_days*24*60*60;
1686 while (list != NULL) {
1687 EggRecentItem *item = list->data;
1690 timestamp = egg_recent_item_get_timestamp (item);
1692 if ((timestamp+day_seconds) < current_time) {
1693 gchar *uri = egg_recent_item_get_uri (item);
1694 egg_recent_model_delete (model, uri);
1705 * egg_recent_model_remove_expired:
1706 * @model: A EggRecentModel object.
1708 * Goes through the entire list, and removes any items that are older than
1709 * the user-specified expiration period.
1714 egg_recent_model_remove_expired (EggRecentModel *model)
1719 g_return_if_fail (model != NULL);
1721 file = egg_recent_model_open_file (model);
1722 g_return_if_fail (file != NULL);
1724 if (egg_recent_model_lock_file (file)) {
1725 list = egg_recent_model_read (model, file);
1728 g_warning ("Failed to lock: %s", strerror (errno));
1732 if (!egg_recent_model_unlock_file (file))
1733 g_warning ("Failed to unlock: %s", strerror (errno));
1736 egg_recent_model_remove_expired_list (model, list);
1737 EGG_RECENT_ITEM_LIST_UNREF (list);
1744 * egg_recent_model_get_type:
1746 * This returns a GType representing a EggRecentModel object.
1751 egg_recent_model_get_type (void)
1753 static GType egg_recent_model_type = 0;
1755 if(!egg_recent_model_type) {
1756 static const GTypeInfo egg_recent_model_info = {
1757 sizeof (EggRecentModelClass),
1758 NULL, /* base init */
1759 NULL, /* base finalize */
1760 (GClassInitFunc)egg_recent_model_class_init, /* class init */
1761 NULL, /* class finalize */
1762 NULL, /* class data */
1763 sizeof (EggRecentModel),
1765 (GInstanceInitFunc) egg_recent_model_init
1768 egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
1770 &egg_recent_model_info, 0);
1773 return egg_recent_model_type;