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)
205 egg_recent_model_delete_from_list (GList *list,
216 EggRecentItem *item = tmp->data;
221 if (!strcmp (egg_recent_item_peek_uri (item), uri)) {
222 egg_recent_item_unref (item);
224 list = g_list_remove_link (list, tmp);
235 egg_recent_model_add_new_groups (EggRecentItem *item,
236 EggRecentItem *upd_item)
240 tmp = egg_recent_item_get_groups (upd_item);
243 char *group = tmp->data;
245 if (!egg_recent_item_in_group (item, group))
246 egg_recent_item_add_group (item, group);
253 egg_recent_model_update_item (GList *items, EggRecentItem *upd_item)
258 uri = egg_recent_item_peek_uri (upd_item);
263 EggRecentItem *item = tmp->data;
265 if (gnome_vfs_uris_match (egg_recent_item_peek_uri (item), uri)) {
266 egg_recent_item_set_timestamp (item, (time_t) -1);
268 egg_recent_model_add_new_groups (item, upd_item);
280 egg_recent_model_read_raw (EggRecentModel *model, FILE *file)
283 char buf[EGG_RECENT_MODEL_BUFFER_SIZE];
287 string = g_string_new (NULL);
288 while (fgets (buf, EGG_RECENT_MODEL_BUFFER_SIZE, file)) {
289 string = g_string_append (string, buf);
294 return g_string_free (string, FALSE);
300 parse_info_init (ParseInfo *info)
302 info->states = g_slist_prepend (NULL, STATE_START);
307 parse_info_free (ParseInfo *info)
309 g_slist_free (info->states);
313 push_state (ParseInfo *info,
316 info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
320 pop_state (ParseInfo *info)
322 g_return_if_fail (info->states != NULL);
324 info->states = g_slist_remove (info->states, info->states->data);
328 peek_state (ParseInfo *info)
330 g_return_val_if_fail (info->states != NULL, STATE_START);
332 return GPOINTER_TO_INT (info->states->data);
335 #define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
338 start_element_handler (GMarkupParseContext *context,
339 const gchar *element_name,
340 const gchar **attribute_names,
341 const gchar **attribute_values,
345 ParseInfo *info = (ParseInfo *)user_data;
347 if (ELEMENT_IS (TAG_RECENT_FILES))
348 push_state (info, STATE_RECENT_FILES);
349 else if (ELEMENT_IS (TAG_RECENT_ITEM)) {
350 info->current_item = egg_recent_item_new ();
351 push_state (info, STATE_RECENT_ITEM);
352 } else if (ELEMENT_IS (TAG_URI))
353 push_state (info, STATE_URI);
354 else if (ELEMENT_IS (TAG_MIME_TYPE))
355 push_state (info, STATE_MIME_TYPE);
356 else if (ELEMENT_IS (TAG_TIMESTAMP))
357 push_state (info, STATE_TIMESTAMP);
358 else if (ELEMENT_IS (TAG_PRIVATE)) {
359 push_state (info, STATE_PRIVATE);
360 egg_recent_item_set_private (info->current_item, TRUE);
361 } else if (ELEMENT_IS (TAG_GROUPS))
362 push_state (info, STATE_GROUPS);
363 else if (ELEMENT_IS (TAG_GROUP))
364 push_state (info, STATE_GROUP);
368 list_compare_func_mru (gpointer a, gpointer b)
370 EggRecentItem *item_a = (EggRecentItem *)a;
371 EggRecentItem *item_b = (EggRecentItem *)b;
373 return item_a->timestamp < item_b->timestamp;
377 list_compare_func_lru (gpointer a, gpointer b)
379 EggRecentItem *item_a = (EggRecentItem *)a;
380 EggRecentItem *item_b = (EggRecentItem *)b;
382 return item_a->timestamp > item_b->timestamp;
388 end_element_handler (GMarkupParseContext *context,
389 const gchar *element_name,
393 ParseInfo *info = (ParseInfo *)user_data;
395 switch (peek_state (info)) {
396 case STATE_RECENT_ITEM:
397 info->items = g_list_append (info->items,
399 if (info->current_item->uri == NULL ||
400 strlen (info->current_item->uri) == 0)
401 g_warning ("URI NOT LOADED");
411 text_handler (GMarkupParseContext *context,
417 ParseInfo *info = (ParseInfo *)user_data;
419 switch (peek_state (info)) {
421 case STATE_RECENT_FILES:
422 case STATE_RECENT_ITEM:
427 egg_recent_item_set_uri (info->current_item, text);
429 case STATE_MIME_TYPE:
430 egg_recent_item_set_mime_type (info->current_item,
433 case STATE_TIMESTAMP:
434 egg_recent_item_set_timestamp (info->current_item,
435 (time_t)atoi (text));
438 egg_recent_item_add_group (info->current_item,
446 error_handler (GMarkupParseContext *context,
450 g_warning ("Error in parse: %s", error->message);
454 egg_recent_model_enforce_limit (GList *list, int limit)
459 /* limit < 0 means unlimited */
463 len = g_list_length (list);
468 end = g_list_nth (list, limit-1);
473 EGG_RECENT_ITEM_LIST_UNREF (next);
478 egg_recent_model_sort (EggRecentModel *model, GList *list)
480 switch (model->priv->sort_type) {
481 case EGG_RECENT_MODEL_SORT_MRU:
482 list = g_list_sort (list,
483 (GCompareFunc)list_compare_func_mru);
485 case EGG_RECENT_MODEL_SORT_LRU:
486 list = g_list_sort (list,
487 (GCompareFunc)list_compare_func_lru);
489 case EGG_RECENT_MODEL_SORT_NONE:
497 egg_recent_model_group_match (EggRecentItem *item, GSList *groups)
503 while (tmp != NULL) {
504 const gchar * group = (const gchar *)tmp->data;
506 if (egg_recent_item_in_group (item, group))
516 egg_recent_model_filter (EggRecentModel *model,
520 GList *newlist = NULL;
524 g_return_val_if_fail (list != NULL, NULL);
527 gboolean pass_mime_test = FALSE;
528 gboolean pass_group_test = FALSE;
529 gboolean pass_scheme_test = FALSE;
530 item = (EggRecentItem *)list->data;
533 uri = egg_recent_item_get_uri (item);
535 /* filter by mime type */
536 if (model->priv->mime_filter_values != NULL) {
537 mime_type = egg_recent_item_get_mime_type (item);
539 if (egg_recent_model_string_match
540 (model->priv->mime_filter_values,
542 pass_mime_test = TRUE;
546 pass_mime_test = TRUE;
548 /* filter by group */
549 if (pass_mime_test && model->priv->group_filter_values != NULL) {
550 if (egg_recent_model_group_match
551 (item, model->priv->group_filter_values))
552 pass_group_test = TRUE;
553 } else if (egg_recent_item_get_private (item)) {
554 pass_group_test = FALSE;
556 pass_group_test = TRUE;
558 /* filter by URI scheme */
559 if (pass_mime_test && pass_group_test &&
560 model->priv->scheme_filter_values != NULL) {
563 scheme = gnome_vfs_get_uri_scheme (uri);
565 if (egg_recent_model_string_match
566 (model->priv->scheme_filter_values, scheme))
567 pass_scheme_test = TRUE;
571 pass_scheme_test = TRUE;
573 if (pass_mime_test && pass_group_test && pass_scheme_test)
574 newlist = g_list_prepend (newlist, item);
580 newlist = g_list_reverse (newlist);
592 egg_recent_model_monitor_list_cb (GnomeVFSMonitorHandle *handle,
593 const gchar *monitor_uri,
594 const gchar *info_uri,
595 GnomeVFSMonitorEventType event_type,
598 EggRecentModel *model;
600 model = EGG_RECENT_MODEL (user_data);
602 if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED) {
603 egg_recent_model_delete (model, monitor_uri);
604 g_hash_table_remove (model->priv->monitors, monitor_uri);
611 egg_recent_model_monitor_list (EggRecentModel *model, GList *list)
617 EggRecentItem *item = (EggRecentItem *)tmp->data;
618 GnomeVFSMonitorHandle *handle;
624 uri = egg_recent_item_get_uri (item);
625 if (g_hash_table_lookup (model->priv->monitors, uri)) {
626 /* already monitoring this one */
631 res = gnome_vfs_monitor_add (&handle, uri,
632 GNOME_VFS_MONITOR_FILE,
633 egg_recent_model_monitor_list_cb,
636 if (res == GNOME_VFS_OK)
637 g_hash_table_insert (model->priv->monitors, uri, handle);
646 egg_recent_model_changed_timeout (EggRecentModel *model)
648 model->priv->changed_timeout = 0;
650 egg_recent_model_changed (model);
656 egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
657 const gchar *monitor_uri,
658 const gchar *info_uri,
659 GnomeVFSMonitorEventType event_type,
662 EggRecentModel *model;
664 g_return_if_fail (user_data != NULL);
665 g_return_if_fail (EGG_IS_RECENT_MODEL (user_data));
666 model = EGG_RECENT_MODEL (user_data);
668 if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
669 if (model->priv->changed_timeout > 0) {
670 g_source_remove (model->priv->changed_timeout);
673 model->priv->changed_timeout = g_timeout_add (
674 EGG_RECENT_MODEL_TIMEOUT_LENGTH,
675 (GSourceFunc)egg_recent_model_changed_timeout,
681 egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
683 if (should_monitor && model->priv->monitor == NULL) {
686 uri = gnome_vfs_get_uri_from_local_path (model->priv->path);
688 gnome_vfs_monitor_add (&model->priv->monitor,
690 GNOME_VFS_MONITOR_FILE,
691 egg_recent_model_monitor_cb,
696 /* if the above fails, don't worry about it.
697 * local notifications will still happen
700 } else if (!should_monitor && model->priv->monitor != NULL) {
701 gnome_vfs_monitor_cancel (model->priv->monitor);
702 model->priv->monitor = NULL;
707 egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
709 model->priv->limit = limit;
712 egg_recent_model_monitor (model, FALSE);
714 egg_recent_model_monitor (model, TRUE);
715 egg_recent_model_changed (model);
720 egg_recent_model_read (EggRecentModel *model, FILE *file)
724 GMarkupParseContext *ctx;
728 content = egg_recent_model_read_raw (model, file);
730 if (strlen (content) <= 0) {
735 parse_info_init (&info);
737 ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
740 if (!g_markup_parse_context_parse (ctx, content, strlen (content),
742 g_warning (error->message);
743 g_error_free (error);
749 if (!g_markup_parse_context_end_parse (ctx, &error))
752 g_markup_parse_context_free (ctx);
756 parse_info_free (&info);
761 g_print ("Total items: %d\n", g_list_length (list));
769 egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
778 string = g_string_new ("<?xml version=\"1.0\"?>\n");
779 string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
787 item = (EggRecentItem *)list->data;
790 uri = egg_recent_item_get_uri_utf8 (item);
791 escaped_uri = g_markup_escape_text (uri,
795 mime_type = egg_recent_item_get_mime_type (item);
796 timestamp = egg_recent_item_get_timestamp (item);
798 string = g_string_append (string, " <" TAG_RECENT_ITEM ">\n");
800 g_string_append_printf (string,
801 " <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
804 g_string_append_printf (string,
805 " <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
807 g_string_append_printf (string,
808 " <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
811 g_string_append_printf (string,
812 " <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
814 if (egg_recent_item_get_private (item))
815 string = g_string_append (string,
816 " <" TAG_PRIVATE "/>\n");
818 /* write the groups */
819 string = g_string_append (string,
820 " <" TAG_GROUPS ">\n");
821 groups = egg_recent_item_get_groups (item);
823 if (groups == NULL && egg_recent_item_get_private (item))
824 g_warning ("Item with URI \"%s\" marked as private, but"
825 " does not belong to any groups.\n", uri);
828 const gchar *group = (const gchar *)groups->data;
829 gchar *escaped_group;
831 escaped_group = g_markup_escape_text (group, strlen(group));
833 g_string_append_printf (string,
834 " <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
837 g_free (escaped_group);
839 groups = groups->next;
842 string = g_string_append (string, " </" TAG_GROUPS ">\n");
844 string = g_string_append (string,
845 " </" TAG_RECENT_ITEM ">\n");
848 g_free (escaped_uri);
854 string = g_string_append (string, "</" TAG_RECENT_FILES ">");
856 data = g_string_free (string, FALSE);
858 ret = egg_recent_model_write_raw (model, file, data);
866 egg_recent_model_open_file (EggRecentModel *model)
871 file = fopen (model->priv->path, "r+");
874 prev_umask = umask (077);
876 file = fopen (model->priv->path, "w+");
880 g_return_val_if_fail (file != NULL, NULL);
887 egg_recent_model_lock_file (FILE *file)
896 /* Attempt to lock the file 5 times,
897 * waiting a random interval (< 1 second)
898 * in between attempts.
899 * We should really be doing asynchronous
900 * locking, but requires substantially larger
908 if (lockf (fd, F_TLOCK, 0) == 0)
911 rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
913 g_usleep (100000 * rand_interval);
925 egg_recent_model_unlock_file (FILE *file)
933 return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
940 egg_recent_model_finalize (GObject *object)
942 EggRecentModel *model = EGG_RECENT_MODEL (object);
944 if (model->priv->changed_timeout > 0) {
945 g_source_remove (model->priv->changed_timeout);
948 egg_recent_model_monitor (model, FALSE);
951 g_slist_foreach (model->priv->mime_filter_values,
952 (GFunc) g_pattern_spec_free, NULL);
953 g_slist_free (model->priv->mime_filter_values);
954 model->priv->mime_filter_values = NULL;
956 g_slist_foreach (model->priv->scheme_filter_values,
957 (GFunc) g_pattern_spec_free, NULL);
958 g_slist_free (model->priv->scheme_filter_values);
959 model->priv->scheme_filter_values = NULL;
961 g_slist_foreach (model->priv->group_filter_values,
962 (GFunc) g_free, NULL);
963 g_slist_free (model->priv->group_filter_values);
964 model->priv->group_filter_values = NULL;
967 if (model->priv->limit_change_notify_id)
968 gconf_client_notify_remove (model->priv->client,
969 model->priv->limit_change_notify_id);
970 model->priv->expiration_change_notify_id = 0;
972 if (model->priv->expiration_change_notify_id)
973 gconf_client_notify_remove (model->priv->client,
974 model->priv->expiration_change_notify_id);
975 model->priv->expiration_change_notify_id = 0;
977 g_object_unref (model->priv->client);
978 model->priv->client = NULL;
981 g_free (model->priv->path);
982 model->priv->path = NULL;
984 g_hash_table_destroy (model->priv->monitors);
985 model->priv->monitors = NULL;
988 g_free (model->priv);
992 egg_recent_model_set_property (GObject *object,
997 EggRecentModel *model = EGG_RECENT_MODEL (object);
1001 case PROP_MIME_FILTERS:
1002 model->priv->mime_filter_values =
1003 (GSList *)g_value_get_pointer (value);
1006 case PROP_GROUP_FILTERS:
1007 model->priv->group_filter_values =
1008 (GSList *)g_value_get_pointer (value);
1011 case PROP_SCHEME_FILTERS:
1012 model->priv->scheme_filter_values =
1013 (GSList *)g_value_get_pointer (value);
1016 case PROP_SORT_TYPE:
1017 model->priv->sort_type = g_value_get_int (value);
1021 egg_recent_model_set_limit (model,
1022 g_value_get_int (value));
1026 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1032 egg_recent_model_get_property (GObject *object,
1037 EggRecentModel *model = EGG_RECENT_MODEL (object);
1041 case PROP_MIME_FILTERS:
1042 g_value_set_pointer (value, model->priv->mime_filter_values);
1045 case PROP_GROUP_FILTERS:
1046 g_value_set_pointer (value, model->priv->group_filter_values);
1049 case PROP_SCHEME_FILTERS:
1050 g_value_set_pointer (value, model->priv->scheme_filter_values);
1053 case PROP_SORT_TYPE:
1054 g_value_set_int (value, model->priv->sort_type);
1058 g_value_set_int (value, model->priv->limit);
1062 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1068 egg_recent_model_class_init (EggRecentModelClass * klass)
1070 GObjectClass *object_class;
1072 object_class = G_OBJECT_CLASS (klass);
1073 object_class->set_property = egg_recent_model_set_property;
1074 object_class->get_property = egg_recent_model_get_property;
1075 object_class->finalize = egg_recent_model_finalize;
1077 model_signals[CHANGED] = g_signal_new ("changed",
1078 G_OBJECT_CLASS_TYPE (object_class),
1080 G_STRUCT_OFFSET (EggRecentModelClass, changed),
1082 g_cclosure_marshal_VOID__POINTER,
1087 g_object_class_install_property (object_class,
1089 g_param_spec_pointer ("mime-filters",
1091 "List of mime types to be allowed.",
1092 G_PARAM_READWRITE));
1094 g_object_class_install_property (object_class,
1096 g_param_spec_pointer ("group-filters",
1098 "List of groups to be allowed.",
1099 G_PARAM_READWRITE));
1101 g_object_class_install_property (object_class,
1102 PROP_SCHEME_FILTERS,
1103 g_param_spec_pointer ("scheme-filters",
1105 "List of URI schemes to be allowed.",
1106 G_PARAM_READWRITE));
1108 g_object_class_install_property (object_class,
1110 g_param_spec_int ("sort-type",
1112 "Type of sorting to be done.",
1113 0, EGG_RECENT_MODEL_SORT_NONE,
1114 EGG_RECENT_MODEL_SORT_MRU,
1115 G_PARAM_READWRITE));
1117 g_object_class_install_property (object_class,
1119 g_param_spec_int ("limit",
1121 "Max number of items allowed.",
1122 -1, EGG_RECENT_MODEL_MAX_ITEMS,
1123 EGG_RECENT_MODEL_DEFAULT_LIMIT,
1124 G_PARAM_READWRITE));
1126 klass->changed = NULL;
1132 egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
1133 GConfEntry *entry, gpointer user_data)
1135 EggRecentModel *model;
1138 model = EGG_RECENT_MODEL (user_data);
1140 g_return_if_fail (model != NULL);
1142 if (model->priv->use_default_limit == FALSE)
1143 return; /* ignore this key */
1145 /* the key was unset, and the schema has apparently failed */
1149 value = gconf_entry_get_value (entry);
1151 if (value->type != GCONF_VALUE_INT) {
1152 g_warning ("Expected GConfValue of type integer, "
1153 "got something else");
1157 egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
1161 egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
1162 GConfEntry *entry, gpointer user_data)
1168 egg_recent_model_init (EggRecentModel * model)
1170 if (!gnome_vfs_init ()) {
1171 g_warning ("gnome-vfs initialization failed.");
1176 model->priv = g_new0 (EggRecentModelPrivate, 1);
1178 model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
1181 model->priv->mime_filter_values = NULL;
1182 model->priv->group_filter_values = NULL;
1183 model->priv->scheme_filter_values = NULL;
1185 model->priv->client = gconf_client_get_default ();
1186 gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR,
1187 GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
1189 model->priv->limit_change_notify_id =
1190 gconf_client_notify_add (model->priv->client,
1191 EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY,
1192 egg_recent_model_limit_changed,
1195 model->priv->expiration_change_notify_id =
1196 gconf_client_notify_add (model->priv->client,
1197 EGG_RECENT_MODEL_EXPIRE_KEY,
1198 egg_recent_model_expiration_changed,
1201 model->priv->expire_days = gconf_client_get_int (
1202 model->priv->client,
1203 EGG_RECENT_MODEL_EXPIRE_KEY,
1207 /* keep this out, for now */
1208 model->priv->limit = gconf_client_get_int (
1209 model->priv->client,
1210 EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL);
1211 model->priv->use_default_limit = TRUE;
1213 model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
1214 model->priv->use_default_limit = FALSE;
1216 model->priv->monitors = g_hash_table_new_full (
1217 g_str_hash, g_str_equal,
1218 (GDestroyNotify) g_free,
1219 (GDestroyNotify) gnome_vfs_monitor_cancel);
1221 model->priv->monitor = NULL;
1222 egg_recent_model_monitor (model, TRUE);
1227 * egg_recent_model_new:
1228 * @sort: the type of sorting to use
1229 * @limit: maximum number of items in the list
1231 * This creates a new EggRecentModel object.
1233 * Returns: a EggRecentModel object
1236 egg_recent_model_new (EggRecentModelSort sort)
1238 EggRecentModel *model;
1240 model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
1241 "sort-type", sort, NULL));
1243 g_return_val_if_fail (model, NULL);
1249 * egg_recent_model_add_full:
1250 * @model: A EggRecentModel object.
1251 * @item: A EggRecentItem
1253 * This function adds an item to the list of recently used URIs.
1258 egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
1262 gboolean ret = FALSE;
1263 gboolean updated = FALSE;
1267 g_return_val_if_fail (model != NULL, FALSE);
1268 g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1270 uri = egg_recent_item_get_uri (item);
1271 if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
1278 file = egg_recent_model_open_file (model);
1279 g_return_val_if_fail (file != NULL, FALSE);
1282 egg_recent_item_set_timestamp (item, t);
1284 if (egg_recent_model_lock_file (file)) {
1286 /* read existing stuff */
1287 list = egg_recent_model_read (model, file);
1289 /* if it's already there, we just update it */
1290 updated = egg_recent_model_update_item (list, item);
1293 list = g_list_prepend (list, item);
1295 egg_recent_model_enforce_limit (list,
1296 EGG_RECENT_MODEL_MAX_ITEMS);
1299 /* write new stuff */
1300 if (!egg_recent_model_write (model, file, list))
1301 g_warning ("Write failed: %s", strerror (errno));
1304 list = g_list_remove (list, item);
1306 EGG_RECENT_ITEM_LIST_UNREF (list);
1309 g_warning ("Failed to lock: %s", strerror (errno));
1314 if (!egg_recent_model_unlock_file (file))
1315 g_warning ("Failed to unlock: %s", strerror (errno));
1319 if (model->priv->monitor == NULL) {
1320 /* since monitoring isn't working, at least give a
1321 * local notification
1323 egg_recent_model_changed (model);
1330 * egg_recent_model_add:
1331 * @model: A EggRecentModel object.
1332 * @uri: A string URI
1334 * This function adds an item to the list of recently used URIs.
1339 egg_recent_model_add (EggRecentModel *model, const gchar *uri)
1341 EggRecentItem *item;
1342 gboolean ret = FALSE;
1344 g_return_val_if_fail (model != NULL, FALSE);
1345 g_return_val_if_fail (uri != NULL, FALSE);
1347 item = egg_recent_item_new_from_uri (uri);
1349 g_return_val_if_fail (item != NULL, FALSE);
1351 ret = egg_recent_model_add_full (model, item);
1353 egg_recent_item_unref (item);
1361 * egg_recent_model_delete:
1362 * @model: A EggRecentModel object.
1363 * @uri: The URI you want to delete.
1365 * This function deletes a URI from the file of recently used URIs.
1370 egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
1374 unsigned int length;
1375 gboolean ret = FALSE;
1377 g_return_val_if_fail (model != NULL, FALSE);
1378 g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1379 g_return_val_if_fail (uri != NULL, FALSE);
1381 file = egg_recent_model_open_file (model);
1382 g_return_val_if_fail (file != NULL, FALSE);
1384 if (egg_recent_model_lock_file (file)) {
1385 list = egg_recent_model_read (model, file);
1390 length = g_list_length (list);
1392 list = egg_recent_model_delete_from_list (list, uri);
1394 if (length == g_list_length (list)) {
1395 /* nothing was deleted */
1396 EGG_RECENT_ITEM_LIST_UNREF (list);
1398 egg_recent_model_write (model, file, list);
1399 EGG_RECENT_ITEM_LIST_UNREF (list);
1404 g_warning ("Failed to lock: %s", strerror (errno));
1410 if (!egg_recent_model_unlock_file (file))
1411 g_warning ("Failed to unlock: %s", strerror (errno));
1415 g_hash_table_remove (model->priv->monitors, uri);
1417 if (model->priv->monitor == NULL && ret) {
1418 /* since monitoring isn't working, at least give a
1419 * local notification
1421 egg_recent_model_changed (model);
1429 * egg_recent_model_get_list:
1430 * @model: A EggRecentModel object.
1432 * This function gets the current contents of the file
1437 egg_recent_model_get_list (EggRecentModel *model)
1442 file = egg_recent_model_open_file (model);
1443 g_return_val_if_fail (file != NULL, NULL);
1445 if (egg_recent_model_lock_file (file)) {
1446 list = egg_recent_model_read (model, file);
1449 g_warning ("Failed to lock: %s", strerror (errno));
1454 if (!egg_recent_model_unlock_file (file))
1455 g_warning ("Failed to unlock: %s", strerror (errno));
1458 list = egg_recent_model_filter (model, list);
1459 list = egg_recent_model_sort (model, list);
1461 egg_recent_model_enforce_limit (list, model->priv->limit);
1472 * egg_recent_model_set_limit:
1473 * @model: A EggRecentModel object.
1474 * @limit: The maximum length of the list
1476 * This function sets the maximum length of the list. Note: This only affects
1477 * the length of the list emitted in the "changed" signal, not the list stored
1483 egg_recent_model_set_limit (EggRecentModel *model, int limit)
1485 model->priv->use_default_limit = FALSE;
1487 egg_recent_model_set_limit_internal (model, limit);
1491 * egg_recent_model_get_limit:
1492 * @model: A EggRecentModel object.
1494 * This function gets the maximum length of the list.
1499 egg_recent_model_get_limit (EggRecentModel *model)
1501 return model->priv->limit;
1506 * egg_recent_model_clear:
1507 * @model: A EggRecentModel object.
1509 * This function clears the contents of the file
1514 egg_recent_model_clear (EggRecentModel *model)
1519 file = egg_recent_model_open_file (model);
1520 g_return_if_fail (file != NULL);
1524 if (egg_recent_model_lock_file (file)) {
1527 g_warning ("Failed to lock: %s", strerror (errno));
1531 if (!egg_recent_model_unlock_file (file))
1532 g_warning ("Failed to unlock: %s", strerror (errno));
1539 * egg_recent_model_set_filter_mime_types:
1540 * @model: A EggRecentModel object.
1542 * Sets which mime types are allowed in the list.
1547 egg_recent_model_set_filter_mime_types (EggRecentModel *model,
1551 GSList *list = NULL;
1554 g_return_if_fail (model != NULL);
1556 if (model->priv->mime_filter_values != NULL) {
1557 g_slist_foreach (model->priv->mime_filter_values,
1558 (GFunc) g_pattern_spec_free, NULL);
1559 g_slist_free (model->priv->mime_filter_values);
1560 model->priv->mime_filter_values = NULL;
1563 va_start (valist, model);
1565 str = va_arg (valist, gchar*);
1567 while (str != NULL) {
1568 list = g_slist_prepend (list, g_pattern_spec_new (str));
1570 str = va_arg (valist, gchar*);
1575 model->priv->mime_filter_values = list;
1579 * egg_recent_model_set_filter_groups:
1580 * @model: A EggRecentModel object.
1582 * Sets which groups are allowed in the list.
1587 egg_recent_model_set_filter_groups (EggRecentModel *model,
1591 GSList *list = NULL;
1594 g_return_if_fail (model != NULL);
1596 if (model->priv->group_filter_values != NULL) {
1597 g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL);
1598 g_slist_free (model->priv->group_filter_values);
1599 model->priv->group_filter_values = NULL;
1602 va_start (valist, model);
1604 str = va_arg (valist, gchar*);
1606 while (str != NULL) {
1607 list = g_slist_prepend (list, g_strdup (str));
1609 str = va_arg (valist, gchar*);
1614 model->priv->group_filter_values = list;
1618 * egg_recent_model_set_filter_uri_schemes:
1619 * @model: A EggRecentModel object.
1621 * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
1626 egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
1629 GSList *list = NULL;
1632 g_return_if_fail (model != NULL);
1634 if (model->priv->scheme_filter_values != NULL) {
1635 g_slist_foreach (model->priv->scheme_filter_values,
1636 (GFunc) g_pattern_spec_free, NULL);
1637 g_slist_free (model->priv->scheme_filter_values);
1638 model->priv->scheme_filter_values = NULL;
1641 va_start (valist, model);
1643 str = va_arg (valist, gchar*);
1645 while (str != NULL) {
1646 list = g_slist_prepend (list, g_pattern_spec_new (str));
1648 str = va_arg (valist, gchar*);
1653 model->priv->scheme_filter_values = list;
1657 * egg_recent_model_set_sort:
1658 * @model: A EggRecentModel object.
1659 * @sort: A EggRecentModelSort type
1661 * Sets the type of sorting to be used.
1666 egg_recent_model_set_sort (EggRecentModel *model,
1667 EggRecentModelSort sort)
1669 g_return_if_fail (model != NULL);
1671 model->priv->sort_type = sort;
1675 * egg_recent_model_changed:
1676 * @model: A EggRecentModel object.
1678 * This function causes a "changed" signal to be emitted.
1683 egg_recent_model_changed (EggRecentModel *model)
1687 if (model->priv->limit > 0) {
1688 list = egg_recent_model_get_list (model);
1689 /* egg_recent_model_monitor_list (model, list); */
1691 g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
1696 EGG_RECENT_ITEM_LIST_UNREF (list);
1700 egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
1702 time_t current_time;
1705 time (¤t_time);
1706 day_seconds = model->priv->expire_days*24*60*60;
1708 while (list != NULL) {
1709 EggRecentItem *item = list->data;
1712 timestamp = egg_recent_item_get_timestamp (item);
1714 if ((timestamp+day_seconds) < current_time) {
1715 gchar *uri = egg_recent_item_get_uri (item);
1716 egg_recent_model_delete (model, uri);
1727 * egg_recent_model_remove_expired:
1728 * @model: A EggRecentModel object.
1730 * Goes through the entire list, and removes any items that are older than
1731 * the user-specified expiration period.
1736 egg_recent_model_remove_expired (EggRecentModel *model)
1741 g_return_if_fail (model != NULL);
1743 file = egg_recent_model_open_file (model);
1744 g_return_if_fail (file != NULL);
1746 if (egg_recent_model_lock_file (file)) {
1747 list = egg_recent_model_read (model, file);
1750 g_warning ("Failed to lock: %s", strerror (errno));
1754 if (!egg_recent_model_unlock_file (file))
1755 g_warning ("Failed to unlock: %s", strerror (errno));
1758 egg_recent_model_remove_expired_list (model, list);
1759 EGG_RECENT_ITEM_LIST_UNREF (list);
1766 * egg_recent_model_get_type:
1768 * This returns a GType representing a EggRecentModel object.
1773 egg_recent_model_get_type (void)
1775 static GType egg_recent_model_type = 0;
1777 if(!egg_recent_model_type) {
1778 static const GTypeInfo egg_recent_model_info = {
1779 sizeof (EggRecentModelClass),
1780 NULL, /* base init */
1781 NULL, /* base finalize */
1782 (GClassInitFunc)egg_recent_model_class_init, /* class init */
1783 NULL, /* class finalize */
1784 NULL, /* class data */
1785 sizeof (EggRecentModel),
1787 (GInstanceInitFunc) egg_recent_model_init
1790 egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
1792 &egg_recent_model_info, 0);
1795 return egg_recent_model_type;