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 model->priv->changed_timeout = 0;
648 egg_recent_model_changed (model);
654 egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
655 const gchar *monitor_uri,
656 const gchar *info_uri,
657 GnomeVFSMonitorEventType event_type,
660 EggRecentModel *model;
662 g_return_if_fail (user_data != NULL);
663 g_return_if_fail (EGG_IS_RECENT_MODEL (user_data));
664 model = EGG_RECENT_MODEL (user_data);
666 if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
667 if (model->priv->changed_timeout > 0) {
668 g_source_remove (model->priv->changed_timeout);
671 model->priv->changed_timeout = g_timeout_add (
672 EGG_RECENT_MODEL_TIMEOUT_LENGTH,
673 (GSourceFunc)egg_recent_model_changed_timeout,
679 egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
681 if (should_monitor && model->priv->monitor == NULL) {
684 uri = gnome_vfs_get_uri_from_local_path (model->priv->path);
686 gnome_vfs_monitor_add (&model->priv->monitor,
688 GNOME_VFS_MONITOR_FILE,
689 egg_recent_model_monitor_cb,
694 /* if the above fails, don't worry about it.
695 * local notifications will still happen
698 } else if (!should_monitor && model->priv->monitor != NULL) {
699 gnome_vfs_monitor_cancel (model->priv->monitor);
700 model->priv->monitor = NULL;
705 egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
707 model->priv->limit = limit;
710 egg_recent_model_monitor (model, FALSE);
712 egg_recent_model_monitor (model, TRUE);
713 egg_recent_model_changed (model);
718 egg_recent_model_read (EggRecentModel *model, FILE *file)
722 GMarkupParseContext *ctx;
726 content = egg_recent_model_read_raw (model, file);
728 if (strlen (content) <= 0) {
733 parse_info_init (&info);
735 ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
738 if (!g_markup_parse_context_parse (ctx, content, strlen (content),
740 g_warning (error->message);
741 g_error_free (error);
747 if (!g_markup_parse_context_end_parse (ctx, &error))
750 g_markup_parse_context_free (ctx);
754 parse_info_free (&info);
759 g_print ("Total items: %d\n", g_list_length (list));
767 egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
776 string = g_string_new ("<?xml version=\"1.0\"?>\n");
777 string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
785 item = (EggRecentItem *)list->data;
788 uri = egg_recent_item_get_uri_utf8 (item);
789 escaped_uri = g_markup_escape_text (uri,
793 mime_type = egg_recent_item_get_mime_type (item);
794 timestamp = egg_recent_item_get_timestamp (item);
796 string = g_string_append (string, " <" TAG_RECENT_ITEM ">\n");
798 g_string_append_printf (string,
799 " <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
802 g_string_append_printf (string,
803 " <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
805 g_string_append_printf (string,
806 " <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
809 g_string_append_printf (string,
810 " <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
812 if (egg_recent_item_get_private (item))
813 string = g_string_append (string,
814 " <" TAG_PRIVATE "/>\n");
816 /* write the groups */
817 string = g_string_append (string,
818 " <" TAG_GROUPS ">\n");
819 groups = egg_recent_item_get_groups (item);
821 if (groups == NULL && egg_recent_item_get_private (item))
822 g_warning ("Item with URI \"%s\" marked as private, but"
823 " does not belong to any groups.\n", uri);
826 const gchar *group = (const gchar *)groups->data;
827 gchar *escaped_group;
829 escaped_group = g_markup_escape_text (group, strlen(group));
831 g_string_append_printf (string,
832 " <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
835 g_free (escaped_group);
837 groups = groups->next;
840 string = g_string_append (string, " </" TAG_GROUPS ">\n");
842 string = g_string_append (string,
843 " </" TAG_RECENT_ITEM ">\n");
846 g_free (escaped_uri);
852 string = g_string_append (string, "</" TAG_RECENT_FILES ">");
854 data = g_string_free (string, FALSE);
856 ret = egg_recent_model_write_raw (model, file, data);
864 egg_recent_model_open_file (EggRecentModel *model)
869 file = fopen (model->priv->path, "r+");
872 prev_umask = umask (077);
874 file = fopen (model->priv->path, "w+");
878 g_return_val_if_fail (file != NULL, NULL);
885 egg_recent_model_lock_file (FILE *file)
893 /* Attempt to lock the file 5 times,
894 * waiting a random interval (< 1 second)
895 * in between attempts.
896 * We should really be doing asynchronous
897 * locking, but requires substantially larger
905 if (lockf (fd, F_TLOCK, 0) == 0)
908 rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
910 g_usleep (100000 * rand_interval);
919 egg_recent_model_unlock_file (FILE *file)
926 return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
930 egg_recent_model_finalize (GObject *object)
932 EggRecentModel *model = EGG_RECENT_MODEL (object);
934 if (model->priv->changed_timeout > 0) {
935 g_source_remove (model->priv->changed_timeout);
938 egg_recent_model_monitor (model, FALSE);
941 g_slist_foreach (model->priv->mime_filter_values,
942 (GFunc) g_pattern_spec_free, NULL);
943 g_slist_free (model->priv->mime_filter_values);
944 model->priv->mime_filter_values = NULL;
946 g_slist_foreach (model->priv->scheme_filter_values,
947 (GFunc) g_pattern_spec_free, NULL);
948 g_slist_free (model->priv->scheme_filter_values);
949 model->priv->scheme_filter_values = NULL;
951 g_slist_foreach (model->priv->group_filter_values,
952 (GFunc) g_free, NULL);
953 g_slist_free (model->priv->group_filter_values);
954 model->priv->group_filter_values = NULL;
957 if (model->priv->limit_change_notify_id)
958 gconf_client_notify_remove (model->priv->client,
959 model->priv->limit_change_notify_id);
960 model->priv->expiration_change_notify_id = 0;
962 if (model->priv->expiration_change_notify_id)
963 gconf_client_notify_remove (model->priv->client,
964 model->priv->expiration_change_notify_id);
965 model->priv->expiration_change_notify_id = 0;
967 g_object_unref (model->priv->client);
968 model->priv->client = NULL;
971 g_free (model->priv->path);
972 model->priv->path = NULL;
974 g_hash_table_destroy (model->priv->monitors);
975 model->priv->monitors = NULL;
978 g_free (model->priv);
982 egg_recent_model_set_property (GObject *object,
987 EggRecentModel *model = EGG_RECENT_MODEL (object);
991 case PROP_MIME_FILTERS:
992 model->priv->mime_filter_values =
993 (GSList *)g_value_get_pointer (value);
996 case PROP_GROUP_FILTERS:
997 model->priv->group_filter_values =
998 (GSList *)g_value_get_pointer (value);
1001 case PROP_SCHEME_FILTERS:
1002 model->priv->scheme_filter_values =
1003 (GSList *)g_value_get_pointer (value);
1006 case PROP_SORT_TYPE:
1007 model->priv->sort_type = g_value_get_int (value);
1011 egg_recent_model_set_limit (model,
1012 g_value_get_int (value));
1016 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1022 egg_recent_model_get_property (GObject *object,
1027 EggRecentModel *model = EGG_RECENT_MODEL (object);
1031 case PROP_MIME_FILTERS:
1032 g_value_set_pointer (value, model->priv->mime_filter_values);
1035 case PROP_GROUP_FILTERS:
1036 g_value_set_pointer (value, model->priv->group_filter_values);
1039 case PROP_SCHEME_FILTERS:
1040 g_value_set_pointer (value, model->priv->scheme_filter_values);
1043 case PROP_SORT_TYPE:
1044 g_value_set_int (value, model->priv->sort_type);
1048 g_value_set_int (value, model->priv->limit);
1052 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1058 egg_recent_model_class_init (EggRecentModelClass * klass)
1060 GObjectClass *object_class;
1062 object_class = G_OBJECT_CLASS (klass);
1063 object_class->set_property = egg_recent_model_set_property;
1064 object_class->get_property = egg_recent_model_get_property;
1065 object_class->finalize = egg_recent_model_finalize;
1067 model_signals[CHANGED] = g_signal_new ("changed",
1068 G_OBJECT_CLASS_TYPE (object_class),
1070 G_STRUCT_OFFSET (EggRecentModelClass, changed),
1072 g_cclosure_marshal_VOID__POINTER,
1077 g_object_class_install_property (object_class,
1079 g_param_spec_pointer ("mime-filters",
1081 "List of mime types to be allowed.",
1082 G_PARAM_READWRITE));
1084 g_object_class_install_property (object_class,
1086 g_param_spec_pointer ("group-filters",
1088 "List of groups to be allowed.",
1089 G_PARAM_READWRITE));
1091 g_object_class_install_property (object_class,
1092 PROP_SCHEME_FILTERS,
1093 g_param_spec_pointer ("scheme-filters",
1095 "List of URI schemes to be allowed.",
1096 G_PARAM_READWRITE));
1098 g_object_class_install_property (object_class,
1100 g_param_spec_int ("sort-type",
1102 "Type of sorting to be done.",
1103 0, EGG_RECENT_MODEL_SORT_NONE,
1104 EGG_RECENT_MODEL_SORT_MRU,
1105 G_PARAM_READWRITE));
1107 g_object_class_install_property (object_class,
1109 g_param_spec_int ("limit",
1111 "Max number of items allowed.",
1112 -1, EGG_RECENT_MODEL_MAX_ITEMS,
1113 EGG_RECENT_MODEL_DEFAULT_LIMIT,
1114 G_PARAM_READWRITE));
1116 klass->changed = NULL;
1122 egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
1123 GConfEntry *entry, gpointer user_data)
1125 EggRecentModel *model;
1128 model = EGG_RECENT_MODEL (user_data);
1130 g_return_if_fail (model != NULL);
1132 if (model->priv->use_default_limit == FALSE)
1133 return; /* ignore this key */
1135 /* the key was unset, and the schema has apparently failed */
1139 value = gconf_entry_get_value (entry);
1141 if (value->type != GCONF_VALUE_INT) {
1142 g_warning ("Expected GConfValue of type integer, "
1143 "got something else");
1147 egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
1151 egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
1152 GConfEntry *entry, gpointer user_data)
1158 egg_recent_model_init (EggRecentModel * model)
1160 if (!gnome_vfs_init ()) {
1161 g_warning ("gnome-vfs initialization failed.");
1166 model->priv = g_new0 (EggRecentModelPrivate, 1);
1168 model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
1171 model->priv->mime_filter_values = NULL;
1172 model->priv->group_filter_values = NULL;
1173 model->priv->scheme_filter_values = NULL;
1175 model->priv->client = gconf_client_get_default ();
1176 gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR,
1177 GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
1179 model->priv->limit_change_notify_id =
1180 gconf_client_notify_add (model->priv->client,
1181 EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY,
1182 egg_recent_model_limit_changed,
1185 model->priv->expiration_change_notify_id =
1186 gconf_client_notify_add (model->priv->client,
1187 EGG_RECENT_MODEL_EXPIRE_KEY,
1188 egg_recent_model_expiration_changed,
1191 model->priv->expire_days = gconf_client_get_int (
1192 model->priv->client,
1193 EGG_RECENT_MODEL_EXPIRE_KEY,
1197 /* keep this out, for now */
1198 model->priv->limit = gconf_client_get_int (
1199 model->priv->client,
1200 EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL);
1201 model->priv->use_default_limit = TRUE;
1203 model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
1204 model->priv->use_default_limit = FALSE;
1206 model->priv->monitors = g_hash_table_new_full (
1207 g_str_hash, g_str_equal,
1208 (GDestroyNotify) g_free,
1209 (GDestroyNotify) gnome_vfs_monitor_cancel);
1211 model->priv->monitor = NULL;
1212 egg_recent_model_monitor (model, TRUE);
1217 * egg_recent_model_new:
1218 * @sort: the type of sorting to use
1219 * @limit: maximum number of items in the list
1221 * This creates a new EggRecentModel object.
1223 * Returns: a EggRecentModel object
1226 egg_recent_model_new (EggRecentModelSort sort)
1228 EggRecentModel *model;
1230 model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
1231 "sort-type", sort, NULL));
1233 g_return_val_if_fail (model, NULL);
1239 * egg_recent_model_add_full:
1240 * @model: A EggRecentModel object.
1241 * @item: A EggRecentItem
1243 * This function adds an item to the list of recently used URIs.
1248 egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
1252 gboolean ret = FALSE;
1253 gboolean updated = FALSE;
1257 g_return_val_if_fail (model != NULL, FALSE);
1258 g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1260 uri = egg_recent_item_get_uri (item);
1261 if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
1268 file = egg_recent_model_open_file (model);
1269 g_return_val_if_fail (file != NULL, FALSE);
1272 egg_recent_item_set_timestamp (item, t);
1274 if (egg_recent_model_lock_file (file)) {
1276 /* read existing stuff */
1277 list = egg_recent_model_read (model, file);
1279 /* if it's already there, we just update it */
1280 updated = egg_recent_model_update_item (list, item);
1283 list = g_list_prepend (list, item);
1285 egg_recent_model_enforce_limit (list,
1286 EGG_RECENT_MODEL_MAX_ITEMS);
1289 /* write new stuff */
1290 if (!egg_recent_model_write (model, file, list))
1291 g_warning ("Write failed: %s", strerror (errno));
1294 list = g_list_remove (list, item);
1296 EGG_RECENT_ITEM_LIST_UNREF (list);
1299 g_warning ("Failed to lock: %s", strerror (errno));
1304 if (!egg_recent_model_unlock_file (file))
1305 g_warning ("Failed to unlock: %s", strerror (errno));
1309 if (model->priv->monitor == NULL) {
1310 /* since monitoring isn't working, at least give a
1311 * local notification
1313 egg_recent_model_changed (model);
1320 * egg_recent_model_add:
1321 * @model: A EggRecentModel object.
1322 * @uri: A string URI
1324 * This function adds an item to the list of recently used URIs.
1329 egg_recent_model_add (EggRecentModel *model, const gchar *uri)
1331 EggRecentItem *item;
1332 gboolean ret = FALSE;
1334 g_return_val_if_fail (model != NULL, FALSE);
1335 g_return_val_if_fail (uri != NULL, FALSE);
1337 item = egg_recent_item_new_from_uri (uri);
1339 g_return_val_if_fail (item != NULL, FALSE);
1341 ret = egg_recent_model_add_full (model, item);
1343 egg_recent_item_unref (item);
1351 * egg_recent_model_delete:
1352 * @model: A EggRecentModel object.
1353 * @uri: The URI you want to delete.
1355 * This function deletes a URI from the file of recently used URIs.
1360 egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
1364 unsigned int length;
1365 gboolean ret = FALSE;
1367 g_return_val_if_fail (model != NULL, FALSE);
1368 g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1369 g_return_val_if_fail (uri != NULL, FALSE);
1371 file = egg_recent_model_open_file (model);
1372 g_return_val_if_fail (file != NULL, FALSE);
1374 if (egg_recent_model_lock_file (file)) {
1375 list = egg_recent_model_read (model, file);
1380 length = g_list_length (list);
1382 list = egg_recent_model_delete_from_list (list, uri);
1384 if (length == g_list_length (list)) {
1385 /* nothing was deleted */
1386 EGG_RECENT_ITEM_LIST_UNREF (list);
1388 egg_recent_model_write (model, file, list);
1389 EGG_RECENT_ITEM_LIST_UNREF (list);
1394 g_warning ("Failed to lock: %s", strerror (errno));
1400 if (!egg_recent_model_unlock_file (file))
1401 g_warning ("Failed to unlock: %s", strerror (errno));
1405 g_hash_table_remove (model->priv->monitors, uri);
1407 if (model->priv->monitor == NULL && ret) {
1408 /* since monitoring isn't working, at least give a
1409 * local notification
1411 egg_recent_model_changed (model);
1419 * egg_recent_model_get_list:
1420 * @model: A EggRecentModel object.
1422 * This function gets the current contents of the file
1427 egg_recent_model_get_list (EggRecentModel *model)
1432 file = egg_recent_model_open_file (model);
1433 g_return_val_if_fail (file != NULL, NULL);
1435 if (egg_recent_model_lock_file (file)) {
1436 list = egg_recent_model_read (model, file);
1439 g_warning ("Failed to lock: %s", strerror (errno));
1444 if (!egg_recent_model_unlock_file (file))
1445 g_warning ("Failed to unlock: %s", strerror (errno));
1448 list = egg_recent_model_filter (model, list);
1449 list = egg_recent_model_sort (model, list);
1451 egg_recent_model_enforce_limit (list, model->priv->limit);
1462 * egg_recent_model_set_limit:
1463 * @model: A EggRecentModel object.
1464 * @limit: The maximum length of the list
1466 * This function sets the maximum length of the list. Note: This only affects
1467 * the length of the list emitted in the "changed" signal, not the list stored
1473 egg_recent_model_set_limit (EggRecentModel *model, int limit)
1475 model->priv->use_default_limit = FALSE;
1477 egg_recent_model_set_limit_internal (model, limit);
1481 * egg_recent_model_get_limit:
1482 * @model: A EggRecentModel object.
1484 * This function gets the maximum length of the list.
1489 egg_recent_model_get_limit (EggRecentModel *model)
1491 return model->priv->limit;
1496 * egg_recent_model_clear:
1497 * @model: A EggRecentModel object.
1499 * This function clears the contents of the file
1504 egg_recent_model_clear (EggRecentModel *model)
1509 file = egg_recent_model_open_file (model);
1510 g_return_if_fail (file != NULL);
1514 if (egg_recent_model_lock_file (file)) {
1517 g_warning ("Failed to lock: %s", strerror (errno));
1521 if (!egg_recent_model_unlock_file (file))
1522 g_warning ("Failed to unlock: %s", strerror (errno));
1529 * egg_recent_model_set_filter_mime_types:
1530 * @model: A EggRecentModel object.
1532 * Sets which mime types are allowed in the list.
1537 egg_recent_model_set_filter_mime_types (EggRecentModel *model,
1541 GSList *list = NULL;
1544 g_return_if_fail (model != NULL);
1546 if (model->priv->mime_filter_values != NULL) {
1547 g_slist_foreach (model->priv->mime_filter_values,
1548 (GFunc) g_pattern_spec_free, NULL);
1549 g_slist_free (model->priv->mime_filter_values);
1550 model->priv->mime_filter_values = NULL;
1553 va_start (valist, model);
1555 str = va_arg (valist, gchar*);
1557 while (str != NULL) {
1558 list = g_slist_prepend (list, g_pattern_spec_new (str));
1560 str = va_arg (valist, gchar*);
1565 model->priv->mime_filter_values = list;
1569 * egg_recent_model_set_filter_groups:
1570 * @model: A EggRecentModel object.
1572 * Sets which groups are allowed in the list.
1577 egg_recent_model_set_filter_groups (EggRecentModel *model,
1581 GSList *list = NULL;
1584 g_return_if_fail (model != NULL);
1586 if (model->priv->group_filter_values != NULL) {
1587 g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL);
1588 g_slist_free (model->priv->group_filter_values);
1589 model->priv->group_filter_values = NULL;
1592 va_start (valist, model);
1594 str = va_arg (valist, gchar*);
1596 while (str != NULL) {
1597 list = g_slist_prepend (list, g_strdup (str));
1599 str = va_arg (valist, gchar*);
1604 model->priv->group_filter_values = list;
1608 * egg_recent_model_set_filter_uri_schemes:
1609 * @model: A EggRecentModel object.
1611 * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
1616 egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
1619 GSList *list = NULL;
1622 g_return_if_fail (model != NULL);
1624 if (model->priv->scheme_filter_values != NULL) {
1625 g_slist_foreach (model->priv->scheme_filter_values,
1626 (GFunc) g_pattern_spec_free, NULL);
1627 g_slist_free (model->priv->scheme_filter_values);
1628 model->priv->scheme_filter_values = NULL;
1631 va_start (valist, model);
1633 str = va_arg (valist, gchar*);
1635 while (str != NULL) {
1636 list = g_slist_prepend (list, g_pattern_spec_new (str));
1638 str = va_arg (valist, gchar*);
1643 model->priv->scheme_filter_values = list;
1647 * egg_recent_model_set_sort:
1648 * @model: A EggRecentModel object.
1649 * @sort: A EggRecentModelSort type
1651 * Sets the type of sorting to be used.
1656 egg_recent_model_set_sort (EggRecentModel *model,
1657 EggRecentModelSort sort)
1659 g_return_if_fail (model != NULL);
1661 model->priv->sort_type = sort;
1665 * egg_recent_model_changed:
1666 * @model: A EggRecentModel object.
1668 * This function causes a "changed" signal to be emitted.
1673 egg_recent_model_changed (EggRecentModel *model)
1677 if (model->priv->limit > 0) {
1678 list = egg_recent_model_get_list (model);
1679 /* egg_recent_model_monitor_list (model, list); */
1681 g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
1686 EGG_RECENT_ITEM_LIST_UNREF (list);
1690 egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
1692 time_t current_time;
1695 time (¤t_time);
1696 day_seconds = model->priv->expire_days*24*60*60;
1698 while (list != NULL) {
1699 EggRecentItem *item = list->data;
1702 timestamp = egg_recent_item_get_timestamp (item);
1704 if ((timestamp+day_seconds) < current_time) {
1705 gchar *uri = egg_recent_item_get_uri (item);
1706 egg_recent_model_delete (model, uri);
1717 * egg_recent_model_remove_expired:
1718 * @model: A EggRecentModel object.
1720 * Goes through the entire list, and removes any items that are older than
1721 * the user-specified expiration period.
1726 egg_recent_model_remove_expired (EggRecentModel *model)
1731 g_return_if_fail (model != NULL);
1733 file = egg_recent_model_open_file (model);
1734 g_return_if_fail (file != NULL);
1736 if (egg_recent_model_lock_file (file)) {
1737 list = egg_recent_model_read (model, file);
1740 g_warning ("Failed to lock: %s", strerror (errno));
1744 if (!egg_recent_model_unlock_file (file))
1745 g_warning ("Failed to unlock: %s", strerror (errno));
1748 egg_recent_model_remove_expired_list (model, list);
1749 EGG_RECENT_ITEM_LIST_UNREF (list);
1756 * egg_recent_model_get_type:
1758 * This returns a GType representing a EggRecentModel object.
1763 egg_recent_model_get_type (void)
1765 static GType egg_recent_model_type = 0;
1767 if(!egg_recent_model_type) {
1768 static const GTypeInfo egg_recent_model_info = {
1769 sizeof (EggRecentModelClass),
1770 NULL, /* base init */
1771 NULL, /* base finalize */
1772 (GClassInitFunc)egg_recent_model_class_init, /* class init */
1773 NULL, /* class finalize */
1774 NULL, /* class data */
1775 sizeof (EggRecentModel),
1777 (GInstanceInitFunc) egg_recent_model_init
1780 egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
1782 &egg_recent_model_info, 0);
1785 return egg_recent_model_type;