1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of the
6 * License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
18 * James Willcox <jwillcox@cs.indiana.edu>
34 #include <libgnomevfs/gnome-vfs.h>
35 #include <libgnomevfs/gnome-vfs-mime-utils.h>
36 #include <gconf/gconf-client.h>
37 #include "egg-recent-model.h"
38 #include "egg-recent-item.h"
40 #define EGG_RECENT_MODEL_FILE_PATH "/.recently-used"
41 #define EGG_RECENT_MODEL_BUFFER_SIZE 8192
43 #define EGG_RECENT_MODEL_MAX_ITEMS 500
44 #define EGG_RECENT_MODEL_DEFAULT_LIMIT 10
45 #define EGG_RECENT_MODEL_TIMEOUT_LENGTH 200
47 #define EGG_RECENT_MODEL_KEY_DIR "/desktop/gnome/recent_files"
48 #define EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY EGG_RECENT_MODEL_KEY_DIR "/default_limit"
49 #define EGG_RECENT_MODEL_EXPIRE_KEY EGG_RECENT_MODEL_KEY_DIR "/expire"
51 struct _EggRecentModelPrivate {
52 GSList *mime_filter_values; /* list of mime types we allow */
53 GSList *group_filter_values; /* list of groups we allow */
54 GSList *scheme_filter_values; /* list of URI schemes we allow */
56 EggRecentModelSort sort_type; /* type of sorting to be done */
58 int limit; /* soft limit for length of the list */
59 int expire_days; /* number of days to hold an item */
61 char *path; /* path to the file we store stuff in */
65 GnomeVFSMonitorHandle *monitor;
68 gboolean use_default_limit;
70 guint limit_change_notify_id;
71 guint expiration_change_notify_id;
73 guint changed_timeout;
82 static GType model_signals[LAST_SIGNAL] = { 0 };
97 EggRecentItem *current_item;
112 typedef struct _ChangedData {
113 EggRecentModel *model;
117 #define TAG_RECENT_FILES "RecentFiles"
118 #define TAG_RECENT_ITEM "RecentItem"
119 #define TAG_URI "URI"
120 #define TAG_MIME_TYPE "Mime-Type"
121 #define TAG_TIMESTAMP "Timestamp"
122 #define TAG_PRIVATE "Private"
123 #define TAG_GROUPS "Groups"
124 #define TAG_GROUP "Group"
126 static void start_element_handler (GMarkupParseContext *context,
127 const gchar *element_name,
128 const gchar **attribute_names,
129 const gchar **attribute_values,
133 static void end_element_handler (GMarkupParseContext *context,
134 const gchar *element_name,
138 static void text_handler (GMarkupParseContext *context,
144 static void error_handler (GMarkupParseContext *context,
148 static GMarkupParser parser = {start_element_handler, end_element_handler,
154 egg_recent_model_string_match (const GSList *list, const gchar *str)
158 if (list == NULL || str == NULL)
164 if (g_pattern_match_string (tmp->data, str))
174 egg_recent_model_write_raw (EggRecentModel *model, FILE *file,
175 const gchar *content)
183 len = strlen (content);
186 if (fstat (fd, &sbuf) < 0)
187 g_warning ("Couldn't stat XML document.");
189 if ((off_t)len < sbuf.st_size) {
193 if (fputs (content, file) == EOF)
203 egg_recent_model_delete_from_list (GList *list,
214 EggRecentItem *item = tmp->data;
219 if (!strcmp (egg_recent_item_peek_uri (item), uri)) {
220 egg_recent_item_unref (item);
222 list = g_list_remove_link (list, tmp);
233 egg_recent_model_add_new_groups (EggRecentItem *item,
234 EggRecentItem *upd_item)
238 tmp = egg_recent_item_get_groups (upd_item);
241 char *group = tmp->data;
243 if (!egg_recent_item_in_group (item, group))
244 egg_recent_item_add_group (item, group);
251 egg_recent_model_update_item (GList *items, EggRecentItem *upd_item)
256 uri = egg_recent_item_peek_uri (upd_item);
261 EggRecentItem *item = tmp->data;
263 if (gnome_vfs_uris_match (egg_recent_item_peek_uri (item), uri)) {
264 egg_recent_item_set_timestamp (item, (time_t) -1);
266 egg_recent_model_add_new_groups (item, upd_item);
278 egg_recent_model_read_raw (EggRecentModel *model, FILE *file)
281 char buf[EGG_RECENT_MODEL_BUFFER_SIZE];
285 string = g_string_new (NULL);
286 while (fgets (buf, EGG_RECENT_MODEL_BUFFER_SIZE, file)) {
287 string = g_string_append (string, buf);
292 return g_string_free (string, FALSE);
298 parse_info_init (ParseInfo *info)
300 info->states = g_slist_prepend (NULL, STATE_START);
305 parse_info_free (ParseInfo *info)
307 g_slist_free (info->states);
311 push_state (ParseInfo *info,
314 info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
318 pop_state (ParseInfo *info)
320 g_return_if_fail (info->states != NULL);
322 info->states = g_slist_remove (info->states, info->states->data);
326 peek_state (ParseInfo *info)
328 g_return_val_if_fail (info->states != NULL, STATE_START);
330 return GPOINTER_TO_INT (info->states->data);
333 #define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
336 start_element_handler (GMarkupParseContext *context,
337 const gchar *element_name,
338 const gchar **attribute_names,
339 const gchar **attribute_values,
343 ParseInfo *info = (ParseInfo *)user_data;
345 if (ELEMENT_IS (TAG_RECENT_FILES))
346 push_state (info, STATE_RECENT_FILES);
347 else if (ELEMENT_IS (TAG_RECENT_ITEM)) {
348 info->current_item = egg_recent_item_new ();
349 push_state (info, STATE_RECENT_ITEM);
350 } else if (ELEMENT_IS (TAG_URI))
351 push_state (info, STATE_URI);
352 else if (ELEMENT_IS (TAG_MIME_TYPE))
353 push_state (info, STATE_MIME_TYPE);
354 else if (ELEMENT_IS (TAG_TIMESTAMP))
355 push_state (info, STATE_TIMESTAMP);
356 else if (ELEMENT_IS (TAG_PRIVATE)) {
357 push_state (info, STATE_PRIVATE);
358 egg_recent_item_set_private (info->current_item, TRUE);
359 } else if (ELEMENT_IS (TAG_GROUPS))
360 push_state (info, STATE_GROUPS);
361 else if (ELEMENT_IS (TAG_GROUP))
362 push_state (info, STATE_GROUP);
366 list_compare_func_mru (gpointer a, gpointer b)
368 EggRecentItem *item_a = (EggRecentItem *)a;
369 EggRecentItem *item_b = (EggRecentItem *)b;
371 return item_a->timestamp < item_b->timestamp;
375 list_compare_func_lru (gpointer a, gpointer b)
377 EggRecentItem *item_a = (EggRecentItem *)a;
378 EggRecentItem *item_b = (EggRecentItem *)b;
380 return item_a->timestamp > item_b->timestamp;
386 end_element_handler (GMarkupParseContext *context,
387 const gchar *element_name,
391 ParseInfo *info = (ParseInfo *)user_data;
393 switch (peek_state (info)) {
394 case STATE_RECENT_ITEM:
395 info->items = g_list_append (info->items,
397 if (info->current_item->uri == NULL ||
398 strlen (info->current_item->uri) == 0)
399 g_warning ("URI NOT LOADED");
409 text_handler (GMarkupParseContext *context,
415 ParseInfo *info = (ParseInfo *)user_data;
417 switch (peek_state (info)) {
419 case STATE_RECENT_FILES:
420 case STATE_RECENT_ITEM:
425 egg_recent_item_set_uri (info->current_item, text);
427 case STATE_MIME_TYPE:
428 egg_recent_item_set_mime_type (info->current_item,
431 case STATE_TIMESTAMP:
432 egg_recent_item_set_timestamp (info->current_item,
433 (time_t)atoi (text));
436 egg_recent_item_add_group (info->current_item,
444 error_handler (GMarkupParseContext *context,
448 g_warning ("Error in parse: %s", error->message);
452 egg_recent_model_enforce_limit (GList *list, int limit)
457 /* limit < 0 means unlimited */
461 len = g_list_length (list);
466 end = g_list_nth (list, limit-1);
471 EGG_RECENT_ITEM_LIST_UNREF (next);
476 egg_recent_model_sort (EggRecentModel *model, GList *list)
478 switch (model->priv->sort_type) {
479 case EGG_RECENT_MODEL_SORT_MRU:
480 list = g_list_sort (list,
481 (GCompareFunc)list_compare_func_mru);
483 case EGG_RECENT_MODEL_SORT_LRU:
484 list = g_list_sort (list,
485 (GCompareFunc)list_compare_func_lru);
487 case EGG_RECENT_MODEL_SORT_NONE:
495 egg_recent_model_group_match (EggRecentItem *item, GSList *groups)
501 while (tmp != NULL) {
502 const gchar * group = (const gchar *)tmp->data;
504 if (egg_recent_item_in_group (item, group))
514 egg_recent_model_filter (EggRecentModel *model,
518 GList *newlist = NULL;
522 g_return_val_if_fail (list != NULL, NULL);
525 gboolean pass_mime_test = FALSE;
526 gboolean pass_group_test = FALSE;
527 gboolean pass_scheme_test = FALSE;
528 item = (EggRecentItem *)list->data;
531 uri = egg_recent_item_get_uri (item);
533 /* filter by mime type */
534 if (model->priv->mime_filter_values != NULL) {
535 mime_type = egg_recent_item_get_mime_type (item);
537 if (egg_recent_model_string_match
538 (model->priv->mime_filter_values,
540 pass_mime_test = TRUE;
544 pass_mime_test = TRUE;
546 /* filter by group */
547 if (pass_mime_test && model->priv->group_filter_values != NULL) {
548 if (egg_recent_model_group_match
549 (item, model->priv->group_filter_values))
550 pass_group_test = TRUE;
551 } else if (egg_recent_item_get_private (item)) {
552 pass_group_test = FALSE;
554 pass_group_test = TRUE;
556 /* filter by URI scheme */
557 if (pass_mime_test && pass_group_test &&
558 model->priv->scheme_filter_values != NULL) {
561 scheme = gnome_vfs_get_uri_scheme (uri);
563 if (egg_recent_model_string_match
564 (model->priv->scheme_filter_values, scheme))
565 pass_scheme_test = TRUE;
569 pass_scheme_test = TRUE;
571 if (pass_mime_test && pass_group_test && pass_scheme_test)
572 newlist = g_list_prepend (newlist, item);
578 newlist = g_list_reverse (newlist);
590 egg_recent_model_monitor_list_cb (GnomeVFSMonitorHandle *handle,
591 const gchar *monitor_uri,
592 const gchar *info_uri,
593 GnomeVFSMonitorEventType event_type,
596 EggRecentModel *model;
598 model = EGG_RECENT_MODEL (user_data);
600 if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED) {
601 egg_recent_model_delete (model, monitor_uri);
602 g_hash_table_remove (model->priv->monitors, monitor_uri);
609 egg_recent_model_monitor_list (EggRecentModel *model, GList *list)
615 EggRecentItem *item = (EggRecentItem *)tmp->data;
616 GnomeVFSMonitorHandle *handle;
622 uri = egg_recent_item_get_uri (item);
623 if (g_hash_table_lookup (model->priv->monitors, uri)) {
624 /* already monitoring this one */
629 res = gnome_vfs_monitor_add (&handle, uri,
630 GNOME_VFS_MONITOR_FILE,
631 egg_recent_model_monitor_list_cb,
634 if (res == GNOME_VFS_OK)
635 g_hash_table_insert (model->priv->monitors, uri, handle);
644 egg_recent_model_changed_timeout (EggRecentModel *model)
646 egg_recent_model_changed (model);
652 egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
653 const gchar *monitor_uri,
654 const gchar *info_uri,
655 GnomeVFSMonitorEventType event_type,
658 EggRecentModel *model;
660 g_return_if_fail (user_data != NULL);
661 g_return_if_fail (EGG_IS_RECENT_MODEL (user_data));
662 model = EGG_RECENT_MODEL (user_data);
664 if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
665 if (model->priv->changed_timeout > 0) {
666 g_source_remove (model->priv->changed_timeout);
669 model->priv->changed_timeout = g_timeout_add (
670 EGG_RECENT_MODEL_TIMEOUT_LENGTH,
671 (GSourceFunc)egg_recent_model_changed_timeout,
677 egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
679 if (should_monitor && model->priv->monitor == NULL) {
682 uri = gnome_vfs_get_uri_from_local_path (model->priv->path);
684 gnome_vfs_monitor_add (&model->priv->monitor,
686 GNOME_VFS_MONITOR_FILE,
687 egg_recent_model_monitor_cb,
692 /* if the above fails, don't worry about it.
693 * local notifications will still happen
696 } else if (!should_monitor && model->priv->monitor != NULL) {
697 gnome_vfs_monitor_cancel (model->priv->monitor);
698 model->priv->monitor = NULL;
703 egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
705 model->priv->limit = limit;
708 egg_recent_model_monitor (model, FALSE);
710 egg_recent_model_monitor (model, TRUE);
711 egg_recent_model_changed (model);
716 egg_recent_model_read (EggRecentModel *model, FILE *file)
720 GMarkupParseContext *ctx;
724 content = egg_recent_model_read_raw (model, file);
726 if (strlen (content) <= 0) {
731 parse_info_init (&info);
733 ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
736 if (!g_markup_parse_context_parse (ctx, content, strlen (content),
738 g_warning (error->message);
739 g_error_free (error);
745 if (!g_markup_parse_context_end_parse (ctx, &error))
748 g_markup_parse_context_free (ctx);
752 parse_info_free (&info);
757 g_print ("Total items: %d\n", g_list_length (list));
765 egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
774 string = g_string_new ("<?xml version=\"1.0\"?>\n");
775 string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
783 item = (EggRecentItem *)list->data;
786 uri = egg_recent_item_get_uri_utf8 (item);
787 escaped_uri = g_markup_escape_text (uri,
791 mime_type = egg_recent_item_get_mime_type (item);
792 timestamp = egg_recent_item_get_timestamp (item);
794 string = g_string_append (string, " <" TAG_RECENT_ITEM ">\n");
796 g_string_append_printf (string,
797 " <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
800 g_string_append_printf (string,
801 " <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
803 g_string_append_printf (string,
804 " <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
807 g_string_append_printf (string,
808 " <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
810 if (egg_recent_item_get_private (item))
811 string = g_string_append (string,
812 " <" TAG_PRIVATE "/>\n");
814 /* write the groups */
815 string = g_string_append (string,
816 " <" TAG_GROUPS ">\n");
817 groups = egg_recent_item_get_groups (item);
819 if (groups == NULL && egg_recent_item_get_private (item))
820 g_warning ("Item with URI \"%s\" marked as private, but"
821 " does not belong to any groups.\n", uri);
824 const gchar *group = (const gchar *)groups->data;
825 gchar *escaped_group;
827 escaped_group = g_markup_escape_text (group, strlen(group));
829 g_string_append_printf (string,
830 " <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
833 g_free (escaped_group);
835 groups = groups->next;
838 string = g_string_append (string, " </" TAG_GROUPS ">\n");
840 string = g_string_append (string,
841 " </" TAG_RECENT_ITEM ">\n");
844 g_free (escaped_uri);
850 string = g_string_append (string, "</" TAG_RECENT_FILES ">");
852 data = g_string_free (string, FALSE);
854 ret = egg_recent_model_write_raw (model, file, data);
862 egg_recent_model_open_file (EggRecentModel *model)
867 file = fopen (model->priv->path, "r+");
870 prev_umask = umask (077);
872 file = fopen (model->priv->path, "w+");
876 g_return_val_if_fail (file != NULL, NULL);
883 egg_recent_model_lock_file (FILE *file)
891 /* Attempt to lock the file 5 times,
892 * waiting a random interval (< 1 second)
893 * in between attempts.
894 * We should really be doing asynchronous
895 * locking, but requires substantially larger
903 if (lockf (fd, F_TLOCK, 0) == 0)
906 rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
908 g_usleep (100000 * rand_interval);
917 egg_recent_model_unlock_file (FILE *file)
924 return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
928 egg_recent_model_finalize (GObject *object)
930 EggRecentModel *model = EGG_RECENT_MODEL (object);
932 egg_recent_model_monitor (model, FALSE);
935 g_slist_foreach (model->priv->mime_filter_values,
936 (GFunc) g_pattern_spec_free, NULL);
937 g_slist_free (model->priv->mime_filter_values);
938 model->priv->mime_filter_values = NULL;
940 g_slist_foreach (model->priv->scheme_filter_values,
941 (GFunc) g_pattern_spec_free, NULL);
942 g_slist_free (model->priv->scheme_filter_values);
943 model->priv->scheme_filter_values = NULL;
945 g_slist_foreach (model->priv->group_filter_values,
946 (GFunc) g_free, NULL);
947 g_slist_free (model->priv->group_filter_values);
948 model->priv->group_filter_values = NULL;
951 if (model->priv->limit_change_notify_id)
952 gconf_client_notify_remove (model->priv->client,
953 model->priv->limit_change_notify_id);
954 model->priv->expiration_change_notify_id = 0;
956 if (model->priv->expiration_change_notify_id)
957 gconf_client_notify_remove (model->priv->client,
958 model->priv->expiration_change_notify_id);
959 model->priv->expiration_change_notify_id = 0;
961 g_object_unref (model->priv->client);
962 model->priv->client = NULL;
965 g_free (model->priv->path);
966 model->priv->path = NULL;
968 g_hash_table_destroy (model->priv->monitors);
969 model->priv->monitors = NULL;
972 g_free (model->priv);
976 egg_recent_model_set_property (GObject *object,
981 EggRecentModel *model = EGG_RECENT_MODEL (object);
985 case PROP_MIME_FILTERS:
986 model->priv->mime_filter_values =
987 (GSList *)g_value_get_pointer (value);
990 case PROP_GROUP_FILTERS:
991 model->priv->group_filter_values =
992 (GSList *)g_value_get_pointer (value);
995 case PROP_SCHEME_FILTERS:
996 model->priv->scheme_filter_values =
997 (GSList *)g_value_get_pointer (value);
1000 case PROP_SORT_TYPE:
1001 model->priv->sort_type = g_value_get_int (value);
1005 egg_recent_model_set_limit (model,
1006 g_value_get_int (value));
1010 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1016 egg_recent_model_get_property (GObject *object,
1021 EggRecentModel *model = EGG_RECENT_MODEL (object);
1025 case PROP_MIME_FILTERS:
1026 g_value_set_pointer (value, model->priv->mime_filter_values);
1029 case PROP_GROUP_FILTERS:
1030 g_value_set_pointer (value, model->priv->group_filter_values);
1033 case PROP_SCHEME_FILTERS:
1034 g_value_set_pointer (value, model->priv->scheme_filter_values);
1037 case PROP_SORT_TYPE:
1038 g_value_set_int (value, model->priv->sort_type);
1042 g_value_set_int (value, model->priv->limit);
1046 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1052 egg_recent_model_class_init (EggRecentModelClass * klass)
1054 GObjectClass *object_class;
1056 object_class = G_OBJECT_CLASS (klass);
1057 object_class->set_property = egg_recent_model_set_property;
1058 object_class->get_property = egg_recent_model_get_property;
1059 object_class->finalize = egg_recent_model_finalize;
1061 model_signals[CHANGED] = g_signal_new ("changed",
1062 G_OBJECT_CLASS_TYPE (object_class),
1064 G_STRUCT_OFFSET (EggRecentModelClass, changed),
1066 g_cclosure_marshal_VOID__POINTER,
1071 g_object_class_install_property (object_class,
1073 g_param_spec_pointer ("mime-filters",
1075 "List of mime types to be allowed.",
1076 G_PARAM_READWRITE));
1078 g_object_class_install_property (object_class,
1080 g_param_spec_pointer ("group-filters",
1082 "List of groups to be allowed.",
1083 G_PARAM_READWRITE));
1085 g_object_class_install_property (object_class,
1086 PROP_SCHEME_FILTERS,
1087 g_param_spec_pointer ("scheme-filters",
1089 "List of URI schemes to be allowed.",
1090 G_PARAM_READWRITE));
1092 g_object_class_install_property (object_class,
1094 g_param_spec_int ("sort-type",
1096 "Type of sorting to be done.",
1097 0, EGG_RECENT_MODEL_SORT_NONE,
1098 EGG_RECENT_MODEL_SORT_MRU,
1099 G_PARAM_READWRITE));
1101 g_object_class_install_property (object_class,
1103 g_param_spec_int ("limit",
1105 "Max number of items allowed.",
1106 -1, EGG_RECENT_MODEL_MAX_ITEMS,
1107 EGG_RECENT_MODEL_DEFAULT_LIMIT,
1108 G_PARAM_READWRITE));
1110 klass->changed = NULL;
1116 egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
1117 GConfEntry *entry, gpointer user_data)
1119 EggRecentModel *model;
1122 model = EGG_RECENT_MODEL (user_data);
1124 g_return_if_fail (model != NULL);
1126 if (model->priv->use_default_limit == FALSE)
1127 return; /* ignore this key */
1129 /* the key was unset, and the schema has apparently failed */
1133 value = gconf_entry_get_value (entry);
1135 if (value->type != GCONF_VALUE_INT) {
1136 g_warning ("Expected GConfValue of type integer, "
1137 "got something else");
1141 egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
1145 egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
1146 GConfEntry *entry, gpointer user_data)
1152 egg_recent_model_init (EggRecentModel * model)
1154 if (!gnome_vfs_init ()) {
1155 g_warning ("gnome-vfs initialization failed.");
1160 model->priv = g_new0 (EggRecentModelPrivate, 1);
1162 model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
1165 model->priv->mime_filter_values = NULL;
1166 model->priv->group_filter_values = NULL;
1167 model->priv->scheme_filter_values = NULL;
1169 model->priv->client = gconf_client_get_default ();
1170 gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR,
1171 GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
1173 model->priv->limit_change_notify_id =
1174 gconf_client_notify_add (model->priv->client,
1175 EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY,
1176 egg_recent_model_limit_changed,
1179 model->priv->expiration_change_notify_id =
1180 gconf_client_notify_add (model->priv->client,
1181 EGG_RECENT_MODEL_EXPIRE_KEY,
1182 egg_recent_model_expiration_changed,
1185 model->priv->expire_days = gconf_client_get_int (
1186 model->priv->client,
1187 EGG_RECENT_MODEL_EXPIRE_KEY,
1191 /* keep this out, for now */
1192 model->priv->limit = gconf_client_get_int (
1193 model->priv->client,
1194 EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL);
1195 model->priv->use_default_limit = TRUE;
1197 model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
1198 model->priv->use_default_limit = FALSE;
1200 model->priv->monitors = g_hash_table_new_full (
1201 g_str_hash, g_str_equal,
1202 (GDestroyNotify) g_free,
1203 (GDestroyNotify) gnome_vfs_monitor_cancel);
1205 model->priv->monitor = NULL;
1206 egg_recent_model_monitor (model, TRUE);
1211 * egg_recent_model_new:
1212 * @sort: the type of sorting to use
1213 * @limit: maximum number of items in the list
1215 * This creates a new EggRecentModel object.
1217 * Returns: a EggRecentModel object
1220 egg_recent_model_new (EggRecentModelSort sort)
1222 EggRecentModel *model;
1224 model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
1225 "sort-type", sort, NULL));
1227 g_return_val_if_fail (model, NULL);
1233 * egg_recent_model_add_full:
1234 * @model: A EggRecentModel object.
1235 * @item: A EggRecentItem
1237 * This function adds an item to the list of recently used URIs.
1242 egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
1246 gboolean ret = FALSE;
1247 gboolean updated = FALSE;
1251 g_return_val_if_fail (model != NULL, FALSE);
1252 g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1254 uri = egg_recent_item_get_uri (item);
1255 if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
1262 file = egg_recent_model_open_file (model);
1263 g_return_val_if_fail (file != NULL, FALSE);
1266 egg_recent_item_set_timestamp (item, t);
1268 if (egg_recent_model_lock_file (file)) {
1270 /* read existing stuff */
1271 list = egg_recent_model_read (model, file);
1273 /* if it's already there, we just update it */
1274 updated = egg_recent_model_update_item (list, item);
1277 list = g_list_prepend (list, item);
1279 egg_recent_model_enforce_limit (list,
1280 EGG_RECENT_MODEL_MAX_ITEMS);
1283 /* write new stuff */
1284 if (!egg_recent_model_write (model, file, list))
1285 g_warning ("Write failed: %s", strerror (errno));
1288 list = g_list_remove (list, item);
1290 EGG_RECENT_ITEM_LIST_UNREF (list);
1293 g_warning ("Failed to lock: %s", strerror (errno));
1298 if (!egg_recent_model_unlock_file (file))
1299 g_warning ("Failed to unlock: %s", strerror (errno));
1303 if (model->priv->monitor == NULL) {
1304 /* since monitoring isn't working, at least give a
1305 * local notification
1307 egg_recent_model_changed (model);
1314 * egg_recent_model_add:
1315 * @model: A EggRecentModel object.
1316 * @uri: A string URI
1318 * This function adds an item to the list of recently used URIs.
1323 egg_recent_model_add (EggRecentModel *model, const gchar *uri)
1325 EggRecentItem *item;
1326 gboolean ret = FALSE;
1328 g_return_val_if_fail (model != NULL, FALSE);
1329 g_return_val_if_fail (uri != NULL, FALSE);
1331 item = egg_recent_item_new_from_uri (uri);
1333 g_return_val_if_fail (item != NULL, FALSE);
1335 ret = egg_recent_model_add_full (model, item);
1337 egg_recent_item_unref (item);
1345 * egg_recent_model_delete:
1346 * @model: A EggRecentModel object.
1347 * @uri: The URI you want to delete.
1349 * This function deletes a URI from the file of recently used URIs.
1354 egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
1358 unsigned int length;
1359 gboolean ret = FALSE;
1361 g_return_val_if_fail (model != NULL, FALSE);
1362 g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1363 g_return_val_if_fail (uri != NULL, FALSE);
1365 file = egg_recent_model_open_file (model);
1366 g_return_val_if_fail (file != NULL, FALSE);
1368 if (egg_recent_model_lock_file (file)) {
1369 list = egg_recent_model_read (model, file);
1374 length = g_list_length (list);
1376 list = egg_recent_model_delete_from_list (list, uri);
1378 if (length == g_list_length (list)) {
1379 /* nothing was deleted */
1380 EGG_RECENT_ITEM_LIST_UNREF (list);
1382 egg_recent_model_write (model, file, list);
1383 EGG_RECENT_ITEM_LIST_UNREF (list);
1388 g_warning ("Failed to lock: %s", strerror (errno));
1394 if (!egg_recent_model_unlock_file (file))
1395 g_warning ("Failed to unlock: %s", strerror (errno));
1399 g_hash_table_remove (model->priv->monitors, uri);
1401 if (model->priv->monitor == NULL && ret) {
1402 /* since monitoring isn't working, at least give a
1403 * local notification
1405 egg_recent_model_changed (model);
1413 * egg_recent_model_get_list:
1414 * @model: A EggRecentModel object.
1416 * This function gets the current contents of the file
1421 egg_recent_model_get_list (EggRecentModel *model)
1426 file = egg_recent_model_open_file (model);
1427 g_return_val_if_fail (file != NULL, NULL);
1429 if (egg_recent_model_lock_file (file)) {
1430 list = egg_recent_model_read (model, file);
1433 g_warning ("Failed to lock: %s", strerror (errno));
1438 if (!egg_recent_model_unlock_file (file))
1439 g_warning ("Failed to unlock: %s", strerror (errno));
1442 list = egg_recent_model_filter (model, list);
1443 list = egg_recent_model_sort (model, list);
1445 egg_recent_model_enforce_limit (list, model->priv->limit);
1456 * egg_recent_model_set_limit:
1457 * @model: A EggRecentModel object.
1458 * @limit: The maximum length of the list
1460 * This function sets the maximum length of the list. Note: This only affects
1461 * the length of the list emitted in the "changed" signal, not the list stored
1467 egg_recent_model_set_limit (EggRecentModel *model, int limit)
1469 model->priv->use_default_limit = FALSE;
1471 egg_recent_model_set_limit_internal (model, limit);
1475 * egg_recent_model_get_limit:
1476 * @model: A EggRecentModel object.
1478 * This function gets the maximum length of the list.
1483 egg_recent_model_get_limit (EggRecentModel *model)
1485 return model->priv->limit;
1490 * egg_recent_model_clear:
1491 * @model: A EggRecentModel object.
1493 * This function clears the contents of the file
1498 egg_recent_model_clear (EggRecentModel *model)
1503 file = egg_recent_model_open_file (model);
1504 g_return_if_fail (file != NULL);
1508 if (egg_recent_model_lock_file (file)) {
1511 g_warning ("Failed to lock: %s", strerror (errno));
1515 if (!egg_recent_model_unlock_file (file))
1516 g_warning ("Failed to unlock: %s", strerror (errno));
1523 * egg_recent_model_set_filter_mime_types:
1524 * @model: A EggRecentModel object.
1526 * Sets which mime types are allowed in the list.
1531 egg_recent_model_set_filter_mime_types (EggRecentModel *model,
1535 GSList *list = NULL;
1538 g_return_if_fail (model != NULL);
1540 if (model->priv->mime_filter_values != NULL) {
1541 g_slist_foreach (model->priv->mime_filter_values,
1542 (GFunc) g_pattern_spec_free, NULL);
1543 g_slist_free (model->priv->mime_filter_values);
1544 model->priv->mime_filter_values = NULL;
1547 va_start (valist, model);
1549 str = va_arg (valist, gchar*);
1551 while (str != NULL) {
1552 list = g_slist_prepend (list, g_pattern_spec_new (str));
1554 str = va_arg (valist, gchar*);
1559 model->priv->mime_filter_values = list;
1563 * egg_recent_model_set_filter_groups:
1564 * @model: A EggRecentModel object.
1566 * Sets which groups are allowed in the list.
1571 egg_recent_model_set_filter_groups (EggRecentModel *model,
1575 GSList *list = NULL;
1578 g_return_if_fail (model != NULL);
1580 if (model->priv->group_filter_values != NULL) {
1581 g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL);
1582 g_slist_free (model->priv->group_filter_values);
1583 model->priv->group_filter_values = NULL;
1586 va_start (valist, model);
1588 str = va_arg (valist, gchar*);
1590 while (str != NULL) {
1591 list = g_slist_prepend (list, g_strdup (str));
1593 str = va_arg (valist, gchar*);
1598 model->priv->group_filter_values = list;
1602 * egg_recent_model_set_filter_uri_schemes:
1603 * @model: A EggRecentModel object.
1605 * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
1610 egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
1613 GSList *list = NULL;
1616 g_return_if_fail (model != NULL);
1618 if (model->priv->scheme_filter_values != NULL) {
1619 g_slist_foreach (model->priv->scheme_filter_values,
1620 (GFunc) g_pattern_spec_free, NULL);
1621 g_slist_free (model->priv->scheme_filter_values);
1622 model->priv->scheme_filter_values = NULL;
1625 va_start (valist, model);
1627 str = va_arg (valist, gchar*);
1629 while (str != NULL) {
1630 list = g_slist_prepend (list, g_pattern_spec_new (str));
1632 str = va_arg (valist, gchar*);
1637 model->priv->scheme_filter_values = list;
1641 * egg_recent_model_set_sort:
1642 * @model: A EggRecentModel object.
1643 * @sort: A EggRecentModelSort type
1645 * Sets the type of sorting to be used.
1650 egg_recent_model_set_sort (EggRecentModel *model,
1651 EggRecentModelSort sort)
1653 g_return_if_fail (model != NULL);
1655 model->priv->sort_type = sort;
1659 * egg_recent_model_changed:
1660 * @model: A EggRecentModel object.
1662 * This function causes a "changed" signal to be emitted.
1667 egg_recent_model_changed (EggRecentModel *model)
1671 if (model->priv->limit > 0) {
1672 list = egg_recent_model_get_list (model);
1673 /* egg_recent_model_monitor_list (model, list); */
1675 g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
1680 EGG_RECENT_ITEM_LIST_UNREF (list);
1684 egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
1686 time_t current_time;
1689 time (¤t_time);
1690 day_seconds = model->priv->expire_days*24*60*60;
1692 while (list != NULL) {
1693 EggRecentItem *item = list->data;
1696 timestamp = egg_recent_item_get_timestamp (item);
1698 if ((timestamp+day_seconds) < current_time) {
1699 gchar *uri = egg_recent_item_get_uri (item);
1700 egg_recent_model_delete (model, uri);
1711 * egg_recent_model_remove_expired:
1712 * @model: A EggRecentModel object.
1714 * Goes through the entire list, and removes any items that are older than
1715 * the user-specified expiration period.
1720 egg_recent_model_remove_expired (EggRecentModel *model)
1725 g_return_if_fail (model != NULL);
1727 file = egg_recent_model_open_file (model);
1728 g_return_if_fail (file != NULL);
1730 if (egg_recent_model_lock_file (file)) {
1731 list = egg_recent_model_read (model, file);
1734 g_warning ("Failed to lock: %s", strerror (errno));
1738 if (!egg_recent_model_unlock_file (file))
1739 g_warning ("Failed to unlock: %s", strerror (errno));
1742 egg_recent_model_remove_expired_list (model, list);
1743 EGG_RECENT_ITEM_LIST_UNREF (list);
1750 * egg_recent_model_get_type:
1752 * This returns a GType representing a EggRecentModel object.
1757 egg_recent_model_get_type (void)
1759 static GType egg_recent_model_type = 0;
1761 if(!egg_recent_model_type) {
1762 static const GTypeInfo egg_recent_model_info = {
1763 sizeof (EggRecentModelClass),
1764 NULL, /* base init */
1765 NULL, /* base finalize */
1766 (GClassInitFunc)egg_recent_model_class_init, /* class init */
1767 NULL, /* class finalize */
1768 NULL, /* class data */
1769 sizeof (EggRecentModel),
1771 (GInstanceInitFunc) egg_recent_model_init
1774 egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
1776 &egg_recent_model_info, 0);
1779 return egg_recent_model_type;