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>
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"
41 #define EGG_RECENT_MODEL_FILE_PATH "/.recently-used"
42 #define EGG_RECENT_MODEL_BUFFER_SIZE 8192
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
49 /* needed for Darwin */
51 int lockf (int filedes, int function, off_t size);
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"
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 */
63 EggRecentModelSort sort_type; /* type of sorting to be done */
65 int limit; /* soft limit for length of the list */
66 int expire_days; /* number of days to hold an item */
68 char *path; /* path to the file we store stuff in */
72 GnomeVFSMonitorHandle *monitor;
75 gboolean use_default_limit;
77 guint limit_change_notify_id;
78 guint expiration_change_notify_id;
80 guint changed_timeout;
91 static GType model_signals[LAST_SIGNAL] = { 0 };
106 EggRecentItem *current_item;
122 EggRecentModel *model;
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"
135 static void start_element_handler (GMarkupParseContext *context,
136 const gchar *element_name,
137 const gchar **attribute_names,
138 const gchar **attribute_values,
142 static void end_element_handler (GMarkupParseContext *context,
143 const gchar *element_name,
147 static void text_handler (GMarkupParseContext *context,
153 static void error_handler (GMarkupParseContext *context,
157 static GMarkupParser parser = {start_element_handler, end_element_handler,
162 static GObjectClass *parent_class;
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);
168 static GObjectClass *parent_class;
171 egg_recent_model_string_match (const GSList *list, const gchar *str)
175 if (list == NULL || str == NULL)
181 if (g_pattern_match_string (tmp->data, str))
191 egg_recent_model_write_raw (EggRecentModel *model, FILE *file,
192 const gchar *content)
200 len = strlen (content);
203 if (fstat (fd, &sbuf) < 0)
204 g_warning ("Couldn't stat XML document.");
206 if ((off_t)len < sbuf.st_size) {
210 if (fputs (content, file) == EOF)
222 egg_recent_model_delete_from_list (GList *list,
233 EggRecentItem *item = tmp->data;
238 if (!strcmp (egg_recent_item_peek_uri (item), uri)) {
239 egg_recent_item_unref (item);
241 list = g_list_remove_link (list, tmp);
252 egg_recent_model_add_new_groups (EggRecentItem *item,
253 EggRecentItem *upd_item)
257 tmp = egg_recent_item_get_groups (upd_item);
260 char *group = tmp->data;
262 if (!egg_recent_item_in_group (item, group))
263 egg_recent_item_add_group (item, group);
270 egg_recent_model_update_item (GList *items, EggRecentItem *upd_item)
275 uri = egg_recent_item_peek_uri (upd_item);
280 EggRecentItem *item = tmp->data;
282 if (gnome_vfs_uris_match (egg_recent_item_peek_uri (item), uri)) {
283 egg_recent_item_set_timestamp (item, (time_t) -1);
285 egg_recent_model_add_new_groups (item, upd_item);
297 egg_recent_model_read_raw (EggRecentModel *model, FILE *file)
300 char buf[EGG_RECENT_MODEL_BUFFER_SIZE];
304 string = g_string_new (NULL);
305 while (fgets (buf, EGG_RECENT_MODEL_BUFFER_SIZE, file)) {
306 string = g_string_append (string, buf);
311 return g_string_free (string, FALSE);
317 parse_info_init (void)
321 retval = g_new0 (ParseInfo, 1);
322 retval->states = g_slist_prepend (NULL, STATE_START);
323 retval->items = NULL;
329 parse_info_free (ParseInfo *info)
331 g_slist_free (info->states);
336 push_state (ParseInfo *info,
339 info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
343 pop_state (ParseInfo *info)
345 g_return_if_fail (info->states != NULL);
347 info->states = g_slist_remove (info->states, info->states->data);
351 peek_state (ParseInfo *info)
353 g_return_val_if_fail (info->states != NULL, STATE_START);
355 return GPOINTER_TO_INT (info->states->data);
358 #define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
361 valid_element (ParseInfo *info,
362 int valid_parent_state,
363 const gchar *element_name,
364 const gchar *valid_element,
367 if (peek_state (info) != valid_parent_state) {
370 G_MARKUP_ERROR_INVALID_CONTENT,
371 "Unexpected tag '%s', tag '%s' expected",
372 element_name, valid_element);
380 start_element_handler (GMarkupParseContext *context,
381 const gchar *element_name,
382 const gchar **attribute_names,
383 const gchar **attribute_values,
387 ParseInfo *info = (ParseInfo *)user_data;
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);
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);
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);
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);
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);
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);
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);
432 list_compare_func_mru (gpointer a, gpointer b)
434 EggRecentItem *item_a = (EggRecentItem *)a;
435 EggRecentItem *item_b = (EggRecentItem *)b;
437 return item_a->timestamp < item_b->timestamp;
441 list_compare_func_lru (gpointer a, gpointer b)
443 EggRecentItem *item_a = (EggRecentItem *)a;
444 EggRecentItem *item_b = (EggRecentItem *)b;
446 return item_a->timestamp > item_b->timestamp;
452 end_element_handler (GMarkupParseContext *context,
453 const gchar *element_name,
457 ParseInfo *info = (ParseInfo *)user_data;
459 switch (peek_state (info)) {
460 case STATE_RECENT_ITEM:
461 if (!info->current_item) {
462 g_warning ("No recent item found\n");
466 if (!info->current_item->uri) {
467 g_warning ("Invalid item found\n");
471 info->items = g_list_prepend (info->items,
473 info->current_item = NULL;
483 text_handler (GMarkupParseContext *context,
489 ParseInfo *info = (ParseInfo *)user_data;
492 value = g_strndup (text, text_len);
494 switch (peek_state (info)) {
496 case STATE_RECENT_FILES:
497 case STATE_RECENT_ITEM:
502 egg_recent_item_set_uri (info->current_item, value);
504 case STATE_MIME_TYPE:
505 egg_recent_item_set_mime_type (info->current_item, value);
507 case STATE_TIMESTAMP:
508 egg_recent_item_set_timestamp (info->current_item,
509 (time_t)atoi (value));
512 egg_recent_item_add_group (info->current_item,
521 error_handler (GMarkupParseContext *context,
525 g_warning ("Error in parse: %s", error->message);
529 egg_recent_model_enforce_limit (GList *list, int limit)
534 /* limit < 0 means unlimited */
538 len = g_list_length (list);
543 end = g_list_nth (list, limit-1);
548 EGG_RECENT_ITEM_LIST_UNREF (next);
553 egg_recent_model_sort (EggRecentModel *model, GList *list)
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);
560 case EGG_RECENT_MODEL_SORT_LRU:
561 list = g_list_sort (list,
562 (GCompareFunc)list_compare_func_lru);
564 case EGG_RECENT_MODEL_SORT_NONE:
572 egg_recent_model_group_match (EggRecentItem *item, GSList *groups)
578 while (tmp != NULL) {
579 const gchar * group = (const gchar *)tmp->data;
581 if (egg_recent_item_in_group (item, group))
591 egg_recent_model_filter (EggRecentModel *model, GList *list)
593 GList *newlist = NULL;
598 g_return_val_if_fail (list != NULL, NULL);
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;
606 g_assert (item != NULL);
608 uri = egg_recent_item_get_uri (item);
610 /* filter by mime type */
611 if (model->priv->mime_filter_values != NULL) {
612 mime_type = egg_recent_item_get_mime_type (item);
614 if (egg_recent_model_string_match
615 (model->priv->mime_filter_values,
617 pass_mime_test = TRUE;
621 pass_mime_test = TRUE;
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;
631 pass_group_test = TRUE;
633 /* filter by URI scheme */
634 if (pass_mime_test && pass_group_test &&
635 model->priv->scheme_filter_values != NULL) {
638 scheme = gnome_vfs_get_uri_scheme (uri);
640 if (egg_recent_model_string_match
641 (model->priv->scheme_filter_values, scheme))
642 pass_scheme_test = TRUE;
646 pass_scheme_test = TRUE;
648 if (pass_mime_test && pass_group_test && pass_scheme_test)
649 newlist = g_list_prepend (newlist, item);
651 egg_recent_item_unref (item);
658 return g_list_reverse (newlist);
665 egg_recent_model_monitor_list_cb (GnomeVFSMonitorHandle *handle,
666 const gchar *monitor_uri,
667 const gchar *info_uri,
668 GnomeVFSMonitorEventType event_type,
671 EggRecentModel *model;
673 model = EGG_RECENT_MODEL (user_data);
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);
684 egg_recent_model_monitor_list (EggRecentModel *model, GList *list)
690 EggRecentItem *item = (EggRecentItem *)tmp->data;
691 GnomeVFSMonitorHandle *handle;
697 uri = egg_recent_item_get_uri (item);
698 if (g_hash_table_lookup (model->priv->monitors, uri)) {
699 /* already monitoring this one */
704 res = gnome_vfs_monitor_add (&handle, uri,
705 GNOME_VFS_MONITOR_FILE,
706 egg_recent_model_monitor_list_cb,
709 if (res == GNOME_VFS_OK)
710 g_hash_table_insert (model->priv->monitors, uri, handle);
719 egg_recent_model_changed_timeout (EggRecentModel *model)
721 model->priv->changed_timeout = 0;
723 egg_recent_model_changed (model);
729 egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
730 const gchar *monitor_uri,
731 const gchar *info_uri,
732 GnomeVFSMonitorEventType event_type,
735 EggRecentModel *model;
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);
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);
748 model->priv->changed_timeout = g_timeout_add (
749 EGG_RECENT_MODEL_TIMEOUT_LENGTH,
750 (GSourceFunc)egg_recent_model_changed_timeout,
756 egg_recent_model_poll_timeout (gpointer user_data)
758 EggRecentModel *model;
759 struct stat stat_buf;
762 model = EGG_RECENT_MODEL (user_data);
763 stat_res = stat (model->priv->path, &stat_buf);
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;
769 if (model->priv->changed_timeout > 0)
770 g_source_remove (model->priv->changed_timeout);
772 model->priv->changed_timeout = g_timeout_add (
773 EGG_RECENT_MODEL_TIMEOUT_LENGTH,
774 (GSourceFunc)egg_recent_model_changed_timeout,
781 egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
783 if (should_monitor && model->priv->monitor == NULL) {
785 GnomeVFSResult result;
787 uri = gnome_vfs_get_uri_from_local_path (model->priv->path);
789 result = gnome_vfs_monitor_add (&model->priv->monitor,
791 GNOME_VFS_MONITOR_FILE,
792 egg_recent_model_monitor_cb,
797 /* if the above fails, don't worry about it.
798 * local notifications will still happen
800 if (result == GNOME_VFS_ERROR_NOT_SUPPORTED) {
801 if (model->priv->poll_timeout > 0)
802 g_source_remove (model->priv->poll_timeout);
804 model->priv->poll_timeout = g_timeout_add (
805 EGG_RECENT_MODEL_POLL_TIME * 1000,
806 egg_recent_model_poll_timeout,
810 } else if (!should_monitor && model->priv->monitor != NULL) {
811 gnome_vfs_monitor_cancel (model->priv->monitor);
812 model->priv->monitor = NULL;
817 egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
819 model->priv->limit = limit;
822 egg_recent_model_monitor (model, FALSE);
824 egg_recent_model_monitor (model, TRUE);
825 egg_recent_model_changed (model);
830 egg_recent_model_read (EggRecentModel *model, FILE *file)
834 GMarkupParseContext *ctx;
838 content = egg_recent_model_read_raw (model, file);
840 if (strlen (content) <= 0) {
845 info = parse_info_init ();
847 ctx = g_markup_parse_context_new (&parser, 0, info, 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",
854 g_error_free (error);
855 parse_info_free (info);
861 if (!g_markup_parse_context_end_parse (ctx, &error)) {
862 g_warning ("Unable to complete parsing of the .recently-used file: %s\n",
865 g_error_free (error);
866 g_markup_parse_context_free (ctx);
867 parse_info_free (info);
872 list = g_list_reverse (info->items);
874 g_markup_parse_context_free (ctx);
875 parse_info_free (info);
883 egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
892 string = g_string_new ("<?xml version=\"1.0\"?>\n");
893 string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
901 item = (EggRecentItem *)list->data;
904 uri = egg_recent_item_get_uri_utf8 (item);
905 escaped_uri = g_markup_escape_text (uri,
909 mime_type = egg_recent_item_get_mime_type (item);
910 timestamp = egg_recent_item_get_timestamp (item);
912 string = g_string_append (string, " <" TAG_RECENT_ITEM ">\n");
914 g_string_append_printf (string,
915 " <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
918 g_string_append_printf (string,
919 " <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
921 g_string_append_printf (string,
922 " <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
925 g_string_append_printf (string,
926 " <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
928 if (egg_recent_item_get_private (item))
929 string = g_string_append (string,
930 " <" TAG_PRIVATE "/>\n");
932 /* write the groups */
933 string = g_string_append (string,
934 " <" TAG_GROUPS ">\n");
935 groups = egg_recent_item_get_groups (item);
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);
942 const gchar *group = (const gchar *)groups->data;
943 gchar *escaped_group;
945 escaped_group = g_markup_escape_text (group, strlen(group));
947 g_string_append_printf (string,
948 " <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
951 g_free (escaped_group);
953 groups = groups->next;
956 string = g_string_append (string, " </" TAG_GROUPS ">\n");
958 string = g_string_append (string,
959 " </" TAG_RECENT_ITEM ">\n");
962 g_free (escaped_uri);
968 string = g_string_append (string, "</" TAG_RECENT_FILES ">");
970 data = g_string_free (string, FALSE);
972 ret = egg_recent_model_write_raw (model, file, data);
980 egg_recent_model_open_file (EggRecentModel *model,
981 gboolean for_writing)
986 file = fopen (model->priv->path, "r+");
987 if (file == NULL && for_writing) {
989 prev_umask = umask (077);
991 file = fopen (model->priv->path, "w+");
995 g_return_val_if_fail (file != NULL, NULL);
1002 egg_recent_model_lock_file (FILE *file)
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
1023 if (lockf (fd, F_TLOCK, 0) == 0)
1026 rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
1028 g_usleep (100000 * rand_interval);
1036 #endif /* HAVE_LOCKF */
1040 egg_recent_model_unlock_file (FILE *file)
1048 return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
1051 #endif /* HAVE_LOCKF */
1055 egg_recent_model_finalize (GObject *object)
1057 EggRecentModel *model = EGG_RECENT_MODEL (object);
1059 if (model->priv->changed_timeout > 0) {
1060 g_source_remove (model->priv->changed_timeout);
1063 egg_recent_model_monitor (model, FALSE);
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;
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;
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;
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;
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;
1092 g_object_unref (model->priv->client);
1093 model->priv->client = NULL;
1096 g_free (model->priv->path);
1097 model->priv->path = NULL;
1099 g_hash_table_destroy (model->priv->monitors);
1100 model->priv->monitors = NULL;
1102 if (model->priv->poll_timeout > 0)
1103 g_source_remove (model->priv->poll_timeout);
1104 model->priv->poll_timeout =0;
1106 g_free (model->priv);
1108 parent_class->finalize (object);
1112 egg_recent_model_set_property (GObject *object,
1114 const GValue *value,
1117 EggRecentModel *model = EGG_RECENT_MODEL (object);
1121 case PROP_MIME_FILTERS:
1122 if (model->priv->mime_filter_values != NULL)
1123 egg_recent_model_clear_mime_filter (model);
1125 model->priv->mime_filter_values =
1126 (GSList *)g_value_get_pointer (value);
1129 case PROP_GROUP_FILTERS:
1130 if (model->priv->group_filter_values != NULL)
1131 egg_recent_model_clear_group_filter (model);
1133 model->priv->group_filter_values =
1134 (GSList *)g_value_get_pointer (value);
1137 case PROP_SCHEME_FILTERS:
1138 if (model->priv->scheme_filter_values != NULL)
1139 egg_recent_model_clear_scheme_filter (model);
1141 model->priv->scheme_filter_values =
1142 (GSList *)g_value_get_pointer (value);
1145 case PROP_SORT_TYPE:
1146 model->priv->sort_type = g_value_get_int (value);
1150 egg_recent_model_set_limit (model,
1151 g_value_get_int (value));
1155 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1161 egg_recent_model_get_property (GObject *object,
1166 EggRecentModel *model = EGG_RECENT_MODEL (object);
1170 case PROP_MIME_FILTERS:
1171 g_value_set_pointer (value, model->priv->mime_filter_values);
1174 case PROP_GROUP_FILTERS:
1175 g_value_set_pointer (value, model->priv->group_filter_values);
1178 case PROP_SCHEME_FILTERS:
1179 g_value_set_pointer (value, model->priv->scheme_filter_values);
1182 case PROP_SORT_TYPE:
1183 g_value_set_int (value, model->priv->sort_type);
1187 g_value_set_int (value, model->priv->limit);
1191 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1197 egg_recent_model_class_init (EggRecentModelClass * klass)
1199 GObjectClass *object_class;
1201 parent_class = g_type_class_peek_parent (klass);
1203 parent_class = g_type_class_peek_parent (klass);
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;
1210 model_signals[CHANGED] = g_signal_new ("changed",
1211 G_OBJECT_CLASS_TYPE (object_class),
1213 G_STRUCT_OFFSET (EggRecentModelClass, changed),
1215 g_cclosure_marshal_VOID__POINTER,
1220 g_object_class_install_property (object_class,
1222 g_param_spec_pointer ("mime-filters",
1224 "List of mime types to be allowed.",
1225 G_PARAM_READWRITE));
1227 g_object_class_install_property (object_class,
1229 g_param_spec_pointer ("group-filters",
1231 "List of groups to be allowed.",
1232 G_PARAM_READWRITE));
1234 g_object_class_install_property (object_class,
1235 PROP_SCHEME_FILTERS,
1236 g_param_spec_pointer ("scheme-filters",
1238 "List of URI schemes to be allowed.",
1239 G_PARAM_READWRITE));
1241 g_object_class_install_property (object_class,
1243 g_param_spec_int ("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));
1250 g_object_class_install_property (object_class,
1252 g_param_spec_int ("limit",
1254 "Max number of items allowed.",
1255 -1, EGG_RECENT_MODEL_MAX_ITEMS,
1256 EGG_RECENT_MODEL_DEFAULT_LIMIT,
1257 G_PARAM_READWRITE));
1259 klass->changed = NULL;
1265 egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
1266 GConfEntry *entry, gpointer user_data)
1268 EggRecentModel *model;
1271 model = EGG_RECENT_MODEL (user_data);
1273 g_return_if_fail (model != NULL);
1275 if (model->priv->use_default_limit == FALSE)
1276 return; /* ignore this key */
1278 /* the key was unset, and the schema has apparently failed */
1282 value = gconf_entry_get_value (entry);
1284 if (value->type != GCONF_VALUE_INT) {
1285 g_warning ("Expected GConfValue of type integer, "
1286 "got something else");
1290 egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
1294 egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
1295 GConfEntry *entry, gpointer user_data)
1301 egg_recent_model_init (EggRecentModel * model)
1303 if (!gnome_vfs_init ()) {
1304 g_warning ("gnome-vfs initialization failed.");
1309 model->priv = g_new0 (EggRecentModelPrivate, 1);
1311 model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
1314 model->priv->mime_filter_values = NULL;
1315 model->priv->group_filter_values = NULL;
1316 model->priv->scheme_filter_values = NULL;
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);
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,
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,
1334 model->priv->expire_days = gconf_client_get_int (
1335 model->priv->client,
1336 EGG_RECENT_MODEL_EXPIRE_KEY,
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;
1346 model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
1347 model->priv->use_default_limit = FALSE;
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);
1354 model->priv->monitor = NULL;
1355 model->priv->poll_timeout = 0;
1356 model->priv->last_mtime = 0;
1357 egg_recent_model_monitor (model, TRUE);
1362 * egg_recent_model_new:
1363 * @sort: the type of sorting to use
1364 * @limit: maximum number of items in the list
1366 * This creates a new EggRecentModel object.
1368 * Returns: a EggRecentModel object
1371 egg_recent_model_new (EggRecentModelSort sort)
1373 EggRecentModel *model;
1375 model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
1376 "sort-type", sort, NULL));
1378 g_return_val_if_fail (model, NULL);
1384 * egg_recent_model_add_full:
1385 * @model: A EggRecentModel object.
1386 * @item: A EggRecentItem
1388 * This function adds an item to the list of recently used URIs.
1393 egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
1397 gboolean ret = FALSE;
1398 gboolean updated = FALSE;
1402 g_return_val_if_fail (model != NULL, FALSE);
1403 g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1405 uri = egg_recent_item_get_uri (item);
1406 if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
1413 file = egg_recent_model_open_file (model, TRUE);
1414 g_return_val_if_fail (file != NULL, FALSE);
1417 egg_recent_item_set_timestamp (item, t);
1419 if (egg_recent_model_lock_file (file)) {
1421 /* read existing stuff */
1422 list = egg_recent_model_read (model, file);
1424 /* if it's already there, we just update it */
1425 updated = egg_recent_model_update_item (list, item);
1428 list = g_list_prepend (list, item);
1430 egg_recent_model_enforce_limit (list,
1431 EGG_RECENT_MODEL_MAX_ITEMS);
1434 /* write new stuff */
1435 if (!egg_recent_model_write (model, file, list))
1436 g_warning ("Write failed: %s", strerror (errno));
1439 list = g_list_remove (list, item);
1441 EGG_RECENT_ITEM_LIST_UNREF (list);
1444 g_warning ("Failed to lock: %s", strerror (errno));
1449 if (!egg_recent_model_unlock_file (file))
1450 g_warning ("Failed to unlock: %s", strerror (errno));
1454 if (model->priv->monitor == NULL) {
1455 /* since monitoring isn't working, at least give a
1456 * local notification
1458 egg_recent_model_changed (model);
1465 * egg_recent_model_add:
1466 * @model: A EggRecentModel object.
1467 * @uri: A string URI
1469 * This function adds an item to the list of recently used URIs.
1474 egg_recent_model_add (EggRecentModel *model, const gchar *uri)
1476 EggRecentItem *item;
1477 gboolean ret = FALSE;
1479 g_return_val_if_fail (model != NULL, FALSE);
1480 g_return_val_if_fail (uri != NULL, FALSE);
1482 item = egg_recent_item_new_from_uri (uri);
1484 g_return_val_if_fail (item != NULL, FALSE);
1486 ret = egg_recent_model_add_full (model, item);
1488 egg_recent_item_unref (item);
1496 * egg_recent_model_delete:
1497 * @model: A EggRecentModel object.
1498 * @uri: The URI you want to delete.
1500 * This function deletes a URI from the file of recently used URIs.
1505 egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
1509 unsigned int length;
1510 gboolean ret = FALSE;
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);
1516 file = egg_recent_model_open_file (model, TRUE);
1517 g_return_val_if_fail (file != NULL, FALSE);
1519 if (egg_recent_model_lock_file (file)) {
1520 list = egg_recent_model_read (model, file);
1525 length = g_list_length (list);
1527 list = egg_recent_model_delete_from_list (list, uri);
1529 if (length == g_list_length (list)) {
1530 /* nothing was deleted */
1531 EGG_RECENT_ITEM_LIST_UNREF (list);
1533 egg_recent_model_write (model, file, list);
1534 EGG_RECENT_ITEM_LIST_UNREF (list);
1539 g_warning ("Failed to lock: %s", strerror (errno));
1545 if (!egg_recent_model_unlock_file (file))
1546 g_warning ("Failed to unlock: %s", strerror (errno));
1550 g_hash_table_remove (model->priv->monitors, uri);
1552 if (model->priv->monitor == NULL && ret) {
1553 /* since monitoring isn't working, at least give a
1554 * local notification
1556 egg_recent_model_changed (model);
1564 * egg_recent_model_get_list:
1565 * @model: A EggRecentModel object.
1567 * This function gets the current contents of the file
1572 egg_recent_model_get_list (EggRecentModel *model)
1577 file = egg_recent_model_open_file (model, FALSE);
1581 if (egg_recent_model_lock_file (file))
1582 list = egg_recent_model_read (model, file);
1584 g_warning ("Failed to lock: %s", strerror (errno));
1589 if (!egg_recent_model_unlock_file (file))
1590 g_warning ("Failed to unlock: %s", strerror (errno));
1593 list = egg_recent_model_filter (model, list);
1594 list = egg_recent_model_sort (model, list);
1596 egg_recent_model_enforce_limit (list, model->priv->limit);
1607 * egg_recent_model_set_limit:
1608 * @model: A EggRecentModel object.
1609 * @limit: The maximum length of the list
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
1618 egg_recent_model_set_limit (EggRecentModel *model, int limit)
1620 model->priv->use_default_limit = FALSE;
1622 egg_recent_model_set_limit_internal (model, limit);
1626 * egg_recent_model_get_limit:
1627 * @model: A EggRecentModel object.
1629 * This function gets the maximum length of the list.
1634 egg_recent_model_get_limit (EggRecentModel *model)
1636 return model->priv->limit;
1641 * egg_recent_model_clear:
1642 * @model: A EggRecentModel object.
1644 * This function clears the contents of the file
1649 egg_recent_model_clear (EggRecentModel *model)
1654 file = egg_recent_model_open_file (model, TRUE);
1655 g_return_if_fail (file != NULL);
1659 if (egg_recent_model_lock_file (file)) {
1662 g_warning ("Failed to lock: %s", strerror (errno));
1666 if (!egg_recent_model_unlock_file (file))
1667 g_warning ("Failed to unlock: %s", strerror (errno));
1671 if (model->priv->monitor == NULL) {
1672 /* since monitoring isn't working, at least give a
1673 * local notification
1675 egg_recent_model_changed (model);
1680 egg_recent_model_clear_mime_filter (EggRecentModel *model)
1682 g_return_if_fail (model != NULL);
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;
1693 * egg_recent_model_set_filter_mime_types:
1694 * @model: A EggRecentModel object.
1696 * Sets which mime types are allowed in the list.
1701 egg_recent_model_set_filter_mime_types (EggRecentModel *model,
1705 GSList *list = NULL;
1708 g_return_if_fail (model != NULL);
1710 egg_recent_model_clear_mime_filter (model);
1712 va_start (valist, model);
1714 str = va_arg (valist, gchar*);
1716 while (str != NULL) {
1717 list = g_slist_prepend (list, g_pattern_spec_new (str));
1719 str = va_arg (valist, gchar*);
1724 model->priv->mime_filter_values = list;
1728 egg_recent_model_clear_group_filter (EggRecentModel *model)
1730 g_return_if_fail (model != NULL);
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;
1740 * egg_recent_model_set_filter_groups:
1741 * @model: A EggRecentModel object.
1743 * Sets which groups are allowed in the list.
1748 egg_recent_model_set_filter_groups (EggRecentModel *model,
1752 GSList *list = NULL;
1755 g_return_if_fail (model != NULL);
1757 egg_recent_model_clear_group_filter (model);
1759 va_start (valist, model);
1761 str = va_arg (valist, gchar*);
1763 while (str != NULL) {
1764 list = g_slist_prepend (list, g_strdup (str));
1766 str = va_arg (valist, gchar*);
1771 model->priv->group_filter_values = list;
1775 egg_recent_model_clear_scheme_filter (EggRecentModel *model)
1777 g_return_if_fail (model != NULL);
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;
1788 * egg_recent_model_set_filter_uri_schemes:
1789 * @model: A EggRecentModel object.
1791 * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
1796 egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
1799 GSList *list = NULL;
1802 g_return_if_fail (model != NULL);
1804 egg_recent_model_clear_scheme_filter (model);
1806 va_start (valist, model);
1808 str = va_arg (valist, gchar*);
1810 while (str != NULL) {
1811 list = g_slist_prepend (list, g_pattern_spec_new (str));
1813 str = va_arg (valist, gchar*);
1818 model->priv->scheme_filter_values = list;
1822 * egg_recent_model_set_sort:
1823 * @model: A EggRecentModel object.
1824 * @sort: A EggRecentModelSort type
1826 * Sets the type of sorting to be used.
1831 egg_recent_model_set_sort (EggRecentModel *model,
1832 EggRecentModelSort sort)
1834 g_return_if_fail (model != NULL);
1836 model->priv->sort_type = sort;
1840 * egg_recent_model_changed:
1841 * @model: A EggRecentModel object.
1843 * This function causes a "changed" signal to be emitted.
1848 egg_recent_model_changed (EggRecentModel *model)
1852 if (model->priv->limit > 0) {
1853 list = egg_recent_model_get_list (model);
1854 /* egg_recent_model_monitor_list (model, list); */
1856 g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
1861 EGG_RECENT_ITEM_LIST_UNREF (list);
1865 egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
1867 time_t current_time;
1870 time (¤t_time);
1871 day_seconds = model->priv->expire_days*24*60*60;
1873 while (list != NULL) {
1874 EggRecentItem *item = list->data;
1877 timestamp = egg_recent_item_get_timestamp (item);
1879 if ((timestamp+day_seconds) < current_time) {
1880 gchar *uri = egg_recent_item_get_uri (item);
1881 egg_recent_model_delete (model, uri);
1892 * egg_recent_model_remove_expired:
1893 * @model: A EggRecentModel object.
1895 * Goes through the entire list, and removes any items that are older than
1896 * the user-specified expiration period.
1901 egg_recent_model_remove_expired (EggRecentModel *model)
1906 g_return_if_fail (model != NULL);
1908 file = egg_recent_model_open_file (model, FALSE);
1912 if (egg_recent_model_lock_file (file)) {
1913 list = egg_recent_model_read (model, file);
1916 g_warning ("Failed to lock: %s", strerror (errno));
1920 if (!egg_recent_model_unlock_file (file))
1921 g_warning ("Failed to unlock: %s", strerror (errno));
1924 egg_recent_model_remove_expired_list (model, list);
1925 EGG_RECENT_ITEM_LIST_UNREF (list);
1932 * egg_recent_model_get_type:
1934 * This returns a GType representing a EggRecentModel object.
1939 egg_recent_model_get_type (void)
1941 static GType egg_recent_model_type = 0;
1943 if(!egg_recent_model_type) {
1944 static 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),
1953 (GInstanceInitFunc) egg_recent_model_init
1956 egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
1958 &egg_recent_model_info, 0);
1961 return egg_recent_model_type;