diff -urN ../gnome-desktop-2.12.2-pristine/libgnome-desktop/egg-recent-item.c ./libgnome-desktop/egg-recent-item.c --- ../gnome-desktop-2.12.2-pristine/libgnome-desktop/egg-recent-item.c 1969-12-31 19:00:00.000000000 -0500 +++ ./libgnome-desktop/egg-recent-item.c 2006-05-08 16:18:44.000000000 -0400 @@ -0,0 +1,426 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: + * James Willcox + */ + + +#include +#include +#include +#include +#include +#include "egg-recent-item.h" + + + +EggRecentItem * +egg_recent_item_new (void) +{ + EggRecentItem *item; + + item = g_new (EggRecentItem, 1); + + item->groups = NULL; + item->private_data = FALSE; + item->uri = NULL; + item->mime_type = NULL; + item->mime_type_is_explicit = FALSE; + + item->refcount = 1; + + return item; +} + +static void +egg_recent_item_free (EggRecentItem *item) +{ + if (item->uri) + g_free (item->uri); + + if (item->mime_type) + g_free (item->mime_type); + + if (item->groups) { + g_list_foreach (item->groups, (GFunc)g_free, NULL); + g_list_free (item->groups); + item->groups = NULL; + } + + g_free (item); +} + +EggRecentItem * +egg_recent_item_ref (EggRecentItem *item) +{ + item->refcount++; + return item; +} + +EggRecentItem * +egg_recent_item_unref (EggRecentItem *item) +{ + item->refcount--; + + if (item->refcount == 0) { + egg_recent_item_free (item); + } + + return item; +} + + +EggRecentItem * +egg_recent_item_new_from_uri (const gchar *uri) +{ + EggRecentItem *item; + + g_return_val_if_fail (uri != NULL, NULL); + + item = egg_recent_item_new (); + + if (!egg_recent_item_set_uri (item ,uri)) { + egg_recent_item_free (item); + return NULL; + } + + return item; +} + +static void +egg_recent_item_update_mime_type (EggRecentItem *item) +{ + if (!item->mime_type_is_explicit) { + g_free (item->mime_type); + item->mime_type = NULL; + + if (item->uri) + item->mime_type = gnome_vfs_get_mime_type (item->uri); + + if (!item->mime_type) + item->mime_type = g_strdup (GNOME_VFS_MIME_TYPE_UNKNOWN); + } +} + +gboolean +egg_recent_item_set_uri (EggRecentItem *item, const gchar *uri) +{ + gchar *utf8_uri; + + /* if G_BROKEN_FILENAMES is not set, this should succede */ + if (g_utf8_validate (uri, -1, NULL)) { + item->uri = gnome_vfs_make_uri_from_input (uri); + } else { + utf8_uri = g_filename_to_utf8 (uri, -1, NULL, NULL, NULL); + + if (utf8_uri == NULL) { + g_warning ("Couldn't convert URI to UTF-8"); + return FALSE; + } + + if (g_utf8_validate (utf8_uri, -1, NULL)) { + item->uri = gnome_vfs_make_uri_from_input (utf8_uri); + } else { + g_free (utf8_uri); + return FALSE; + } + + g_free (utf8_uri); + } + + return TRUE; +} + +gchar * +egg_recent_item_get_uri (const EggRecentItem *item) +{ + return g_strdup (item->uri); +} + +G_CONST_RETURN gchar * +egg_recent_item_peek_uri (const EggRecentItem *item) +{ + return item->uri; +} + +gchar * +egg_recent_item_get_uri_utf8 (const EggRecentItem *item) +{ + /* this could fail, but it's not likely, since we've already done it + * once in set_uri() + */ + return g_filename_to_utf8 (item->uri, -1, NULL, NULL, NULL); +} + +gchar * +egg_recent_item_get_uri_for_display (const EggRecentItem *item) +{ + return gnome_vfs_format_uri_for_display (item->uri); +} + +/* Stolen from gnome_vfs_make_valid_utf8() */ +static char * +make_valid_utf8 (const char *name) +{ + GString *string; + const char *remainder, *invalid; + int remaining_bytes, valid_bytes; + + string = NULL; + remainder = name; + remaining_bytes = name ? strlen (name) : 0; + + while (remaining_bytes != 0) { + if (g_utf8_validate (remainder, remaining_bytes, &invalid)) + break; + + valid_bytes = invalid - remainder; + + if (string == NULL) + string = g_string_sized_new (remaining_bytes); + + g_string_append_len (string, remainder, valid_bytes); + g_string_append_c (string, '?'); + + remaining_bytes -= valid_bytes + 1; + remainder = invalid + 1; + } + + if (string == NULL) + return g_strdup (name); + + g_string_append (string, remainder); +/* g_string_append (string, _(" (invalid file name)")); */ + g_assert (g_utf8_validate (string->str, -1, NULL)); + + return g_string_free (string, FALSE); +} + +static gchar * +get_uri_shortname_for_display (GnomeVFSURI *uri) +{ + gchar *name; + gboolean validated; + + validated = FALSE; + name = gnome_vfs_uri_extract_short_name (uri); + + if (name == NULL) + { + name = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_PASSWORD); + } + else if (g_ascii_strcasecmp (uri->method_string, "file") == 0) + { + gchar *text_uri; + gchar *local_file; + text_uri = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_PASSWORD); + local_file = gnome_vfs_get_local_path_from_uri (text_uri); + + if (local_file != NULL) + { + g_free (name); + name = g_filename_display_basename (local_file); + validated = TRUE; + } + + g_free (local_file); + g_free (text_uri); + } + else if (!gnome_vfs_uri_has_parent (uri)) + { + const gchar *method; + + method = uri->method_string; + + if (name == NULL || + strcmp (name, GNOME_VFS_URI_PATH_STR) == 0) + { + g_free (name); + name = g_strdup (method); + } + else + { + gchar *tmp; + + tmp = name; + name = g_strdup_printf ("%s: %s", method, name); + g_free (tmp); + } + } + + if (!validated && !g_utf8_validate (name, -1, NULL)) + { + gchar *utf8_name; + + utf8_name = make_valid_utf8 (name); + g_free (name); + name = utf8_name; + } + + return name; +} + +/** + * egg_recent_item_get_short_name: + * @item: an #EggRecentItem + * + * Computes a valid UTF-8 string that can be used as the name of the item in a + * menu or list. For example, calling this function on an item that refers to + * "file:///foo/bar.txt" will yield "bar.txt". + * + * Return value: A newly-allocated string in UTF-8 encoding; free it with + * g_free(). + **/ +gchar * +egg_recent_item_get_short_name (const EggRecentItem *item) +{ + GnomeVFSURI *uri; + gchar *short_name; + + g_return_val_if_fail (item != NULL, NULL); + + if (item->uri == NULL) + return NULL; + + uri = gnome_vfs_uri_new (item->uri); + if (uri == NULL) + return NULL; + + short_name = get_uri_shortname_for_display (uri); + + gnome_vfs_uri_unref (uri); + + return short_name; +} + +void +egg_recent_item_set_mime_type (EggRecentItem *item, const gchar *mime) +{ + g_free (item->mime_type); + item->mime_type = NULL; + + if (mime && mime[0]) { + item->mime_type_is_explicit = TRUE; + item->mime_type = g_strdup (mime); + } else { + item->mime_type_is_explicit = FALSE; + } +} + +gchar * +egg_recent_item_get_mime_type (EggRecentItem *item) +{ + egg_recent_item_update_mime_type (item); + + return g_strdup (item->mime_type); +} + +void +egg_recent_item_set_timestamp (EggRecentItem *item, time_t timestamp) +{ + if (timestamp == (time_t) -1) + time (×tamp); + + item->timestamp = timestamp; +} + +time_t +egg_recent_item_get_timestamp (const EggRecentItem *item) +{ + return item->timestamp; +} + +G_CONST_RETURN GList * +egg_recent_item_get_groups (const EggRecentItem *item) +{ + return item->groups; +} + +gboolean +egg_recent_item_in_group (const EggRecentItem *item, const gchar *group_name) +{ + GList *tmp; + + tmp = item->groups; + while (tmp != NULL) { + gchar *val = (gchar *)tmp->data; + + if (strcmp (group_name, val) == 0) + return TRUE; + + tmp = tmp->next; + } + + return FALSE; +} + +void +egg_recent_item_add_group (EggRecentItem *item, const gchar *group_name) +{ + g_return_if_fail (group_name != NULL); + + if (!egg_recent_item_in_group (item, group_name)) + item->groups = g_list_append (item->groups, g_strdup (group_name)); +} + +void +egg_recent_item_remove_group (EggRecentItem *item, const gchar *group_name) +{ + GList *tmp; + + g_return_if_fail (group_name != NULL); + + tmp = item->groups; + while (tmp != NULL) { + gchar *val = (gchar *)tmp->data; + + if (strcmp (group_name, val) == 0) { + item->groups = g_list_remove (item->groups, + val); + g_free (val); + break; + } + + tmp = tmp->next; + } +} + +void +egg_recent_item_set_private (EggRecentItem *item, gboolean priv) +{ + item->private_data = priv; +} + +gboolean +egg_recent_item_get_private (const EggRecentItem *item) +{ + return item->private_data; +} + +GType +egg_recent_item_get_type (void) +{ + static GType boxed_type = 0; + + if (!boxed_type) { + boxed_type = g_boxed_type_register_static ("EggRecentItem", + (GBoxedCopyFunc)egg_recent_item_ref, + (GBoxedFreeFunc)egg_recent_item_unref); + } + + return boxed_type; +} diff -urN ../gnome-desktop-2.12.2-pristine/libgnome-desktop/egg-recent-item.h ./libgnome-desktop/egg-recent-item.h --- ../gnome-desktop-2.12.2-pristine/libgnome-desktop/egg-recent-item.h 1969-12-31 19:00:00.000000000 -0500 +++ ./libgnome-desktop/egg-recent-item.h 2006-05-08 16:18:44.000000000 -0400 @@ -0,0 +1,80 @@ + +#ifndef __EGG_RECENT_ITEM_H__ +#define __EGG_RECENT_ITEM_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_RECENT_ITEM (egg_recent_item_get_type ()) + +#define EGG_RECENT_ITEM_LIST_UNREF(list) \ + g_list_foreach (list, (GFunc)egg_recent_item_unref, NULL); \ + g_list_free (list); + +typedef struct _EggRecentItem EggRecentItem; + +struct _EggRecentItem { + /* do not access any of these directly */ + gchar *uri; + gchar *mime_type; + time_t timestamp; + + gboolean private_data; + + GList *groups; + + int refcount; + + guint mime_type_is_explicit : 1; +}; + +GType egg_recent_item_get_type (void) G_GNUC_CONST; + +/* constructors */ +EggRecentItem * egg_recent_item_new (void); + +EggRecentItem * egg_recent_item_ref (EggRecentItem *item); +EggRecentItem * egg_recent_item_unref (EggRecentItem *item); + +/* automatically fetches the mime type, etc */ +EggRecentItem * egg_recent_item_new_from_uri (const gchar *uri); + +gboolean egg_recent_item_set_uri (EggRecentItem *item, const gchar *uri); +gchar * egg_recent_item_get_uri (const EggRecentItem *item); +gchar * egg_recent_item_get_uri_utf8 (const EggRecentItem *item); +gchar * egg_recent_item_get_uri_for_display (const EggRecentItem *item); +gchar * egg_recent_item_get_short_name (const EggRecentItem *item); + +void egg_recent_item_set_mime_type (EggRecentItem *item, const gchar *mime); +gchar * egg_recent_item_get_mime_type (EggRecentItem *item); + +void egg_recent_item_set_timestamp (EggRecentItem *item, time_t timestamp); +time_t egg_recent_item_get_timestamp (const EggRecentItem *item); + +G_CONST_RETURN gchar *egg_recent_item_peek_uri (const EggRecentItem *item); + + +/* groups */ +G_CONST_RETURN GList * egg_recent_item_get_groups (const EggRecentItem *item); + +gboolean egg_recent_item_in_group (const EggRecentItem *item, + const gchar *group_name); + +void egg_recent_item_add_group (EggRecentItem *item, + const gchar *group_name); + +void egg_recent_item_remove_group (EggRecentItem *item, + const gchar *group_name); + +void egg_recent_item_set_private (EggRecentItem *item, + gboolean priv); + +gboolean egg_recent_item_get_private (const EggRecentItem *item); + + +G_END_DECLS + +#endif /* __EGG_RECENT_ITEM_H__ */ diff -urN ../gnome-desktop-2.12.2-pristine/libgnome-desktop/egg-recent-model-multi.c ./libgnome-desktop/egg-recent-model-multi.c --- ../gnome-desktop-2.12.2-pristine/libgnome-desktop/egg-recent-model-multi.c 1969-12-31 19:00:00.000000000 -0500 +++ ./libgnome-desktop/egg-recent-model-multi.c 2006-05-08 17:03:13.000000000 -0400 @@ -0,0 +1,1990 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: + * James Willcox + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "egg-recent-model-multi.h" +#include "egg-recent-item.h" + +#define EGG_RECENT_MODEL_MULTI_DEFAULT_FILE_PATH ".recently-used" +#define EGG_RECENT_MODEL_MULTI_BUFFER_SIZE 8192 + +#define EGG_RECENT_MODEL_MULTI_MAX_ITEMS 500 +#define EGG_RECENT_MODEL_MULTI_DEFAULT_LIMIT 10 +#define EGG_RECENT_MODEL_MULTI_TIMEOUT_LENGTH 200 +#define EGG_RECENT_MODEL_MULTI_POLL_TIME 3 + +/* needed for Darwin */ +#if !HAVE_DECL_LOCKF +int lockf (int filedes, int function, off_t size); +#endif + +#define EGG_RECENT_MODEL_MULTI_KEY_DIR "/desktop/gnome/recent_files" +#define EGG_RECENT_MODEL_MULTI_DEFAULT_LIMIT_KEY EGG_RECENT_MODEL_MULTI_KEY_DIR "/default_limit" +#define EGG_RECENT_MODEL_MULTI_EXPIRE_KEY EGG_RECENT_MODEL_MULTI_KEY_DIR "/expire" + +struct _EggRecentModelMultiPrivate { + GSList *mime_filter_values; /* list of mime types we allow */ + GSList *group_filter_values; /* list of groups we allow */ + GSList *scheme_filter_values; /* list of URI schemes we allow */ + + EggRecentModelMultiSort sort_type; /* type of sorting to be done */ + + int limit; /* soft limit for length of the list */ + int expire_days; /* number of days to hold an item */ + + char *path; /* path to the file we store stuff in */ + + GHashTable *monitors; + + GnomeVFSMonitorHandle *monitor; + + GConfClient *client; + gboolean use_default_limit; + + guint limit_change_notify_id; + guint expiration_change_notify_id; + + guint changed_timeout; + guint poll_timeout; + time_t last_mtime; +}; + +/* signals */ +enum { + CHANGED, + LAST_SIGNAL +}; + +static GType model_signals[LAST_SIGNAL] = { 0 }; + +/* properties */ +enum { + PROP_BOGUS, + PROP_MIME_FILTERS, + PROP_GROUP_FILTERS, + PROP_SCHEME_FILTERS, + PROP_SORT_TYPE, + PROP_LIMIT, + PROP_FILE_PATH +}; + +typedef struct { + GSList *states; + GList *items; + EggRecentItem *current_item; +} ParseInfo; + +typedef enum { + STATE_START, + STATE_RECENT_FILES, + STATE_RECENT_ITEM, + STATE_URI, + STATE_MIME_TYPE, + STATE_TIMESTAMP, + STATE_PRIVATE, + STATE_GROUPS, + STATE_GROUP +} ParseState; + +typedef struct { + EggRecentModelMulti *model; + GList *list; +} ChangedData; + +#define TAG_RECENT_FILES "RecentFiles" +#define TAG_RECENT_ITEM "RecentItem" +#define TAG_URI "URI" +#define TAG_MIME_TYPE "Mime-Type" +#define TAG_TIMESTAMP "Timestamp" +#define TAG_PRIVATE "Private" +#define TAG_GROUPS "Groups" +#define TAG_GROUP "Group" + +static void start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error); + +static void end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error); + +static void text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error); + +static void error_handler (GMarkupParseContext *context, + GError *error, + gpointer user_data); + +static GMarkupParser parser = {start_element_handler, end_element_handler, + text_handler, + NULL, + error_handler}; + +static GObjectClass *parent_class; + +static void egg_recent_model_multi_clear_mime_filter (EggRecentModelMulti *model); +static void egg_recent_model_multi_clear_group_filter (EggRecentModelMulti *model); +static void egg_recent_model_multi_clear_scheme_filter (EggRecentModelMulti *model); + +static GObjectClass *parent_class; + +static gboolean +egg_recent_model_multi_string_match (const GSList *list, const gchar *str) +{ + const GSList *tmp; + + if (list == NULL || str == NULL) + return TRUE; + + tmp = list; + + while (tmp) { + if (g_pattern_match_string (tmp->data, str)) + return TRUE; + + tmp = tmp->next; + } + + return FALSE; +} + +static gboolean +egg_recent_model_multi_write_raw (EggRecentModelMulti *model, FILE *file, + const gchar *content) +{ + int len; + int fd; + struct stat sbuf; + + rewind (file); + + len = strlen (content); + fd = fileno (file); + + if (fstat (fd, &sbuf) < 0) + g_warning ("Couldn't stat XML document."); + + if ((off_t)len < sbuf.st_size) { + ftruncate (fd, len); + } + + if (fputs (content, file) == EOF) + return FALSE; + +#ifndef G_OS_WIN32 + fsync (fd); +#endif + rewind (file); + + return TRUE; +} + +static GList * +egg_recent_model_multi_delete_from_list (GList *list, + const gchar *uri) +{ + GList *tmp; + + if (!uri) + return list; + + tmp = list; + + while (tmp) { + EggRecentItem *item = tmp->data; + GList *next; + + next = tmp->next; + + if (!strcmp (egg_recent_item_peek_uri (item), uri)) { + egg_recent_item_unref (item); + + list = g_list_remove_link (list, tmp); + g_list_free_1 (tmp); + } + + tmp = next; + } + + return list; +} + +static void +egg_recent_model_multi_add_new_groups (EggRecentItem *item, + EggRecentItem *upd_item) +{ + const GList *tmp; + + tmp = egg_recent_item_get_groups (upd_item); + + while (tmp) { + char *group = tmp->data; + + if (!egg_recent_item_in_group (item, group)) + egg_recent_item_add_group (item, group); + + tmp = tmp->next; + } +} + +static gboolean +egg_recent_model_multi_update_item (GList *items, EggRecentItem *upd_item) +{ + GList *tmp; + const char *uri; + + uri = egg_recent_item_peek_uri (upd_item); + + tmp = items; + + while (tmp) { + EggRecentItem *item = tmp->data; + + if (gnome_vfs_uris_match (egg_recent_item_peek_uri (item), uri)) { + egg_recent_item_set_timestamp (item, (time_t) -1); + + egg_recent_model_multi_add_new_groups (item, upd_item); + + return TRUE; + } + + tmp = tmp->next; + } + + return FALSE; +} + +static gchar * +egg_recent_model_multi_read_raw (EggRecentModelMulti *model, FILE *file) +{ + GString *string; + char buf[EGG_RECENT_MODEL_MULTI_BUFFER_SIZE]; + + rewind (file); + + string = g_string_new (NULL); + while (fgets (buf, EGG_RECENT_MODEL_MULTI_BUFFER_SIZE, file)) { + string = g_string_append (string, buf); + } + + rewind (file); + + return g_string_free (string, FALSE); +} + + + +static ParseInfo * +parse_info_init (void) +{ + ParseInfo *retval; + + retval = g_new0 (ParseInfo, 1); + retval->states = g_slist_prepend (NULL, STATE_START); + retval->items = NULL; + + return retval; +} + +static void +parse_info_free (ParseInfo *info) +{ + g_slist_free (info->states); + g_free (info); +} + +static void +push_state (ParseInfo *info, + ParseState state) +{ + info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state)); +} + +static void +pop_state (ParseInfo *info) +{ + g_return_if_fail (info->states != NULL); + + info->states = g_slist_remove (info->states, info->states->data); +} + +static ParseState +peek_state (ParseInfo *info) +{ + g_return_val_if_fail (info->states != NULL, STATE_START); + + return GPOINTER_TO_INT (info->states->data); +} + +#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0) + +static gboolean +valid_element (ParseInfo *info, + int valid_parent_state, + const gchar *element_name, + const gchar *valid_element, + GError **error) +{ + if (peek_state (info) != valid_parent_state) { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Unexpected tag '%s', tag '%s' expected", + element_name, valid_element); + return FALSE; + } + + return TRUE; +} + +static void +start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseInfo *info = (ParseInfo *)user_data; + + if (ELEMENT_IS (TAG_RECENT_FILES)) + push_state (info, STATE_RECENT_FILES); + else if (ELEMENT_IS (TAG_RECENT_ITEM)) { + if (valid_element (info, STATE_RECENT_FILES, + TAG_RECENT_ITEM, TAG_RECENT_FILES, error)) { + info->current_item = egg_recent_item_new (); + push_state (info, STATE_RECENT_ITEM); + } + } else if (ELEMENT_IS (TAG_URI)) { + if (valid_element (info, STATE_RECENT_ITEM, + TAG_URI, TAG_RECENT_ITEM, error)) { + push_state (info, STATE_URI); + } + } else if (ELEMENT_IS (TAG_MIME_TYPE)) { + if (valid_element (info, STATE_RECENT_ITEM, + TAG_MIME_TYPE, TAG_RECENT_ITEM, error)) { + push_state (info, STATE_MIME_TYPE); + } + } else if (ELEMENT_IS (TAG_TIMESTAMP)) { + if (valid_element (info, STATE_RECENT_ITEM, + TAG_TIMESTAMP, TAG_RECENT_ITEM, error)) { + push_state (info, STATE_TIMESTAMP); + } + } else if (ELEMENT_IS (TAG_PRIVATE)) { + if (valid_element (info, STATE_RECENT_ITEM, + TAG_PRIVATE, TAG_RECENT_ITEM, error)) { + push_state (info, STATE_PRIVATE); + egg_recent_item_set_private (info->current_item, TRUE); + } + } else if (ELEMENT_IS (TAG_GROUPS)) { + if (valid_element (info, STATE_RECENT_ITEM, + TAG_GROUPS, TAG_RECENT_ITEM, error)) { + push_state (info, STATE_GROUPS); + } + } else if (ELEMENT_IS (TAG_GROUP)) { + if (valid_element (info, STATE_GROUPS, + TAG_GROUP, TAG_GROUPS, error)) { + push_state (info, STATE_GROUP); + } + } +} + +static gint +list_compare_func_mru (gpointer a, gpointer b) +{ + EggRecentItem *item_a = (EggRecentItem *)a; + EggRecentItem *item_b = (EggRecentItem *)b; + + return item_a->timestamp < item_b->timestamp; +} + +static gint +list_compare_func_lru (gpointer a, gpointer b) +{ + EggRecentItem *item_a = (EggRecentItem *)a; + EggRecentItem *item_b = (EggRecentItem *)b; + + return item_a->timestamp > item_b->timestamp; +} + + + +static void +end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseInfo *info = (ParseInfo *)user_data; + + switch (peek_state (info)) { + case STATE_RECENT_ITEM: + if (!info->current_item) { + g_warning ("No recent item found\n"); + break; + } + + if (!info->current_item->uri) { + g_warning ("Invalid item found\n"); + break; + } + + info->items = g_list_prepend (info->items, + info->current_item); + info->current_item = NULL; + break; + default: + break; + } + + pop_state (info); +} + +static void +text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + ParseInfo *info = (ParseInfo *)user_data; + gchar *value; + + value = g_strndup (text, text_len); + + switch (peek_state (info)) { + case STATE_START: + case STATE_RECENT_FILES: + case STATE_RECENT_ITEM: + case STATE_PRIVATE: + case STATE_GROUPS: + break; + case STATE_URI: + egg_recent_item_set_uri (info->current_item, value); + break; + case STATE_MIME_TYPE: + egg_recent_item_set_mime_type (info->current_item, value); + break; + case STATE_TIMESTAMP: + egg_recent_item_set_timestamp (info->current_item, + (time_t)atoi (value)); + break; + case STATE_GROUP: + egg_recent_item_add_group (info->current_item, + text); + break; + } + + g_free (value); +} + +static void +error_handler (GMarkupParseContext *context, + GError *error, + gpointer user_data) +{ + g_warning ("Error in parse: %s", error->message); +} + +static void +egg_recent_model_multi_enforce_limit (GList *list, int limit) +{ + int len; + GList *end; + + /* limit < 0 means unlimited */ + if (limit <= 0) + return; + + len = g_list_length (list); + + if (len > limit) { + GList *next; + + end = g_list_nth (list, limit-1); + next = end->next; + + end->next = NULL; + + EGG_RECENT_ITEM_LIST_UNREF (next); + } +} + +static GList * +egg_recent_model_multi_sort (EggRecentModelMulti *model, GList *list) +{ + switch (model->priv->sort_type) { + case EGG_RECENT_MODEL_MULTI_SORT_MRU: + list = g_list_sort (list, + (GCompareFunc)list_compare_func_mru); + break; + case EGG_RECENT_MODEL_MULTI_SORT_LRU: + list = g_list_sort (list, + (GCompareFunc)list_compare_func_lru); + break; + case EGG_RECENT_MODEL_MULTI_SORT_NONE: + break; + } + + return list; +} + +static gboolean +egg_recent_model_multi_group_match (EggRecentItem *item, GSList *groups) +{ + GSList *tmp; + + tmp = groups; + + while (tmp != NULL) { + const gchar * group = (const gchar *)tmp->data; + + if (egg_recent_item_in_group (item, group)) + return TRUE; + + tmp = tmp->next; + } + + return FALSE; +} + +static GList * +egg_recent_model_multi_filter (EggRecentModelMulti *model, GList *list) +{ + GList *newlist = NULL; + GList *l; + gchar *mime_type; + gchar *uri; + + g_return_val_if_fail (list != NULL, NULL); + + for (l = list; l != NULL ; l = l->next) { + EggRecentItem *item = (EggRecentItem *) l->data; + gboolean pass_mime_test = FALSE; + gboolean pass_group_test = FALSE; + gboolean pass_scheme_test = FALSE; + + g_assert (item != NULL); + + uri = egg_recent_item_get_uri (item); + + /* filter by mime type */ + if (model->priv->mime_filter_values != NULL) { + mime_type = egg_recent_item_get_mime_type (item); + + if (egg_recent_model_multi_string_match + (model->priv->mime_filter_values, + mime_type)) + pass_mime_test = TRUE; + + g_free (mime_type); + } else + pass_mime_test = TRUE; + + /* filter by group */ + if (pass_mime_test && model->priv->group_filter_values != NULL) { + if (egg_recent_model_multi_group_match + (item, model->priv->group_filter_values)) + pass_group_test = TRUE; + } else if (egg_recent_item_get_private (item)) { + pass_group_test = FALSE; + } else + pass_group_test = TRUE; + + /* filter by URI scheme */ + if (pass_mime_test && pass_group_test && + model->priv->scheme_filter_values != NULL) { + gchar *scheme; + + scheme = gnome_vfs_get_uri_scheme (uri); + + if (egg_recent_model_multi_string_match + (model->priv->scheme_filter_values, scheme)) + pass_scheme_test = TRUE; + + g_free (scheme); + } else + pass_scheme_test = TRUE; + + if (pass_mime_test && pass_group_test && pass_scheme_test) + newlist = g_list_prepend (newlist, item); + else + egg_recent_item_unref (item); + + g_free (uri); + } + + g_list_free (list); + + return g_list_reverse (newlist); +} + + + +#if 0 +static void +egg_recent_model_multi_monitor_list_cb (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, + const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + EggRecentModelMulti *model; + + model = EGG_RECENT_MODEL_MULTI (user_data); + + if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED) { + egg_recent_model_multi_delete (model, monitor_uri); + g_hash_table_remove (model->priv->monitors, monitor_uri); + } +} + + + +static void +egg_recent_model_multi_monitor_list (EggRecentModelMulti *model, GList *list) +{ + GList *tmp; + + tmp = list; + while (tmp) { + EggRecentItem *item = (EggRecentItem *)tmp->data; + GnomeVFSMonitorHandle *handle; + GnomeVFSResult res; + gchar *uri; + + tmp = tmp->next; + + uri = egg_recent_item_get_uri (item); + if (g_hash_table_lookup (model->priv->monitors, uri)) { + /* already monitoring this one */ + g_free (uri); + continue; + } + + res = gnome_vfs_monitor_add (&handle, uri, + GNOME_VFS_MONITOR_FILE, + egg_recent_model_multi_monitor_list_cb, + model); + + if (res == GNOME_VFS_OK) + g_hash_table_insert (model->priv->monitors, uri, handle); + else + g_free (uri); + } +} +#endif + + +static gboolean +egg_recent_model_multi_changed_timeout (EggRecentModelMulti *model) +{ + model->priv->changed_timeout = 0; + + egg_recent_model_multi_changed (model); + + return FALSE; +} + +static void +egg_recent_model_multi_monitor_cb (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, + const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + EggRecentModelMulti *model; + + g_return_if_fail (user_data != NULL); + g_return_if_fail (EGG_IS_RECENT_MODEL_MULTI (user_data)); + model = EGG_RECENT_MODEL_MULTI (user_data); + + if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED || + event_type == GNOME_VFS_MONITOR_EVENT_CREATED || + event_type == GNOME_VFS_MONITOR_EVENT_DELETED) { + if (model->priv->changed_timeout > 0) { + g_source_remove (model->priv->changed_timeout); + } + + model->priv->changed_timeout = g_timeout_add ( + EGG_RECENT_MODEL_MULTI_TIMEOUT_LENGTH, + (GSourceFunc)egg_recent_model_multi_changed_timeout, + model); + } +} + +static gboolean +egg_recent_model_multi_poll_timeout (gpointer user_data) +{ + EggRecentModelMulti *model; + struct stat stat_buf; + int stat_res; + + model = EGG_RECENT_MODEL_MULTI (user_data); + stat_res = stat (model->priv->path, &stat_buf); + + if (!stat_res && stat_buf.st_mtime && + stat_buf.st_mtime != model->priv->last_mtime) { + model->priv->last_mtime = stat_buf.st_mtime; + + if (model->priv->changed_timeout > 0) + g_source_remove (model->priv->changed_timeout); + + model->priv->changed_timeout = g_timeout_add ( + EGG_RECENT_MODEL_MULTI_TIMEOUT_LENGTH, + (GSourceFunc)egg_recent_model_multi_changed_timeout, + model); + } + return TRUE; +} + +static void +egg_recent_model_multi_monitor (EggRecentModelMulti *model, gboolean should_monitor) +{ + if (should_monitor && model->priv->monitor == NULL) { + char *uri; + GnomeVFSResult result; + + uri = gnome_vfs_get_uri_from_local_path (model->priv->path); + + result = gnome_vfs_monitor_add (&model->priv->monitor, + uri, + GNOME_VFS_MONITOR_FILE, + egg_recent_model_multi_monitor_cb, + model); + + g_free (uri); + + /* if the above fails, don't worry about it. + * local notifications will still happen + */ + if (result == GNOME_VFS_ERROR_NOT_SUPPORTED) { + if (model->priv->poll_timeout > 0) + g_source_remove (model->priv->poll_timeout); + + model->priv->poll_timeout = g_timeout_add ( + EGG_RECENT_MODEL_MULTI_POLL_TIME * 1000, + egg_recent_model_multi_poll_timeout, + model); + } + + } else if (!should_monitor && model->priv->monitor != NULL) { + gnome_vfs_monitor_cancel (model->priv->monitor); + model->priv->monitor = NULL; + } +} + +static void +egg_recent_model_multi_set_limit_internal (EggRecentModelMulti *model, int limit) +{ + model->priv->limit = limit; + + if (limit <= 0) + egg_recent_model_multi_monitor (model, FALSE); + else { + egg_recent_model_multi_monitor (model, TRUE); + egg_recent_model_multi_changed (model); + } +} + +static GList * +egg_recent_model_multi_read (EggRecentModelMulti *model, FILE *file) +{ + GList *list=NULL; + gchar *content; + GMarkupParseContext *ctx; + ParseInfo *info; + GError *error; + + content = egg_recent_model_multi_read_raw (model, file); + + if (strlen (content) <= 0) { + g_free (content); + return NULL; + } + + info = parse_info_init (); + + ctx = g_markup_parse_context_new (&parser, 0, info, NULL); + + error = NULL; + if (!g_markup_parse_context_parse (ctx, content, strlen (content), &error)) { + g_warning ("Error while parsing the .recently-used file: %s\n", + error->message); + + g_error_free (error); + parse_info_free (info); + + return NULL; + } + + error = NULL; + if (!g_markup_parse_context_end_parse (ctx, &error)) { + g_warning ("Unable to complete parsing of the .recently-used file: %s\n", + error->message); + + g_error_free (error); + g_markup_parse_context_free (ctx); + parse_info_free (info); + + return NULL; + } + + list = g_list_reverse (info->items); + + g_markup_parse_context_free (ctx); + parse_info_free (info); + g_free (content); + + return list; +} + + +static gboolean +egg_recent_model_multi_write (EggRecentModelMulti *model, FILE *file, GList *list) +{ + GString *string; + gchar *data; + EggRecentItem *item; + const GList *groups; + int i; + int ret; + + string = g_string_new ("\n"); + string = g_string_append (string, "<" TAG_RECENT_FILES ">\n"); + + i=0; + while (list) { + gchar *uri; + gchar *mime_type; + gchar *escaped_uri; + time_t timestamp; + item = (EggRecentItem *)list->data; + + + uri = egg_recent_item_get_uri_utf8 (item); + escaped_uri = g_markup_escape_text (uri, + strlen (uri)); + g_free (uri); + + mime_type = egg_recent_item_get_mime_type (item); + timestamp = egg_recent_item_get_timestamp (item); + + string = g_string_append (string, " <" TAG_RECENT_ITEM ">\n"); + + g_string_append_printf (string, + " <" TAG_URI ">%s\n", escaped_uri); + + if (mime_type) + g_string_append_printf (string, + " <" TAG_MIME_TYPE ">%s\n", mime_type); + else + g_string_append_printf (string, + " <" TAG_MIME_TYPE ">\n"); + + + g_string_append_printf (string, + " <" TAG_TIMESTAMP ">%d\n", (int)timestamp); + + if (egg_recent_item_get_private (item)) + string = g_string_append (string, + " <" TAG_PRIVATE "/>\n"); + + /* write the groups */ + string = g_string_append (string, + " <" TAG_GROUPS ">\n"); + groups = egg_recent_item_get_groups (item); + + if (groups == NULL && egg_recent_item_get_private (item)) + g_warning ("Item with URI \"%s\" marked as private, but" + " does not belong to any groups.\n", uri); + + while (groups) { + const gchar *group = (const gchar *)groups->data; + gchar *escaped_group; + + escaped_group = g_markup_escape_text (group, strlen(group)); + + g_string_append_printf (string, + " <" TAG_GROUP ">%s\n", + escaped_group); + + g_free (escaped_group); + + groups = groups->next; + } + + string = g_string_append (string, " \n"); + + string = g_string_append (string, + " \n"); + + g_free (mime_type); + g_free (escaped_uri); + + list = list->next; + i++; + } + + string = g_string_append (string, ""); + + data = g_string_free (string, FALSE); + + ret = egg_recent_model_multi_write_raw (model, file, data); + + g_free (data); + + return ret; +} + +static FILE * +egg_recent_model_multi_open_file (EggRecentModelMulti *model, + gboolean for_writing) +{ + FILE *file; + mode_t prev_umask; + + file = fopen (model->priv->path, "r+"); + if (file == NULL && for_writing) { + /* be paranoid */ + prev_umask = umask (077); + + file = fopen (model->priv->path, "w+"); + + umask (prev_umask); + + g_return_val_if_fail (file != NULL, NULL); + } + + return file; +} + +static gboolean +egg_recent_model_multi_lock_file (FILE *file) +{ +#ifdef HAVE_LOCKF + int fd; + gint try = 5; + + rewind (file); + fd = fileno (file); + + /* Attempt to lock the file 5 times, + * waiting a random interval (< 1 second) + * in between attempts. + * We should really be doing asynchronous + * locking, but requires substantially larger + * changes. + */ + + while (try > 0) + { + int rand_interval; + + if (lockf (fd, F_TLOCK, 0) == 0) + return TRUE; + + rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0)); + + g_usleep (100000 * rand_interval); + + --try; + } + + return FALSE; +#else + return TRUE; +#endif /* HAVE_LOCKF */ +} + +static gboolean +egg_recent_model_multi_unlock_file (FILE *file) +{ +#ifdef HAVE_LOCKF + int fd; + + rewind (file); + fd = fileno (file); + + return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE; +#else + return TRUE; +#endif /* HAVE_LOCKF */ +} + +static void +egg_recent_model_multi_finalize (GObject *object) +{ + EggRecentModelMulti *model = EGG_RECENT_MODEL_MULTI (object); + + if (model->priv->changed_timeout > 0) { + g_source_remove (model->priv->changed_timeout); + } + + egg_recent_model_multi_monitor (model, FALSE); + + + g_slist_foreach (model->priv->mime_filter_values, + (GFunc) g_pattern_spec_free, NULL); + g_slist_free (model->priv->mime_filter_values); + model->priv->mime_filter_values = NULL; + + g_slist_foreach (model->priv->scheme_filter_values, + (GFunc) g_pattern_spec_free, NULL); + g_slist_free (model->priv->scheme_filter_values); + model->priv->scheme_filter_values = NULL; + + g_slist_foreach (model->priv->group_filter_values, + (GFunc) g_free, NULL); + g_slist_free (model->priv->group_filter_values); + model->priv->group_filter_values = NULL; + + + if (model->priv->limit_change_notify_id) + gconf_client_notify_remove (model->priv->client, + model->priv->limit_change_notify_id); + model->priv->expiration_change_notify_id = 0; + + if (model->priv->expiration_change_notify_id) + gconf_client_notify_remove (model->priv->client, + model->priv->expiration_change_notify_id); + model->priv->expiration_change_notify_id = 0; + + g_object_unref (model->priv->client); + model->priv->client = NULL; + + + g_free (model->priv->path); + model->priv->path = NULL; + + g_hash_table_destroy (model->priv->monitors); + model->priv->monitors = NULL; + + if (model->priv->poll_timeout > 0) + g_source_remove (model->priv->poll_timeout); + model->priv->poll_timeout =0; + + g_free (model->priv); + + parent_class->finalize (object); +} + +static void +egg_recent_model_multi_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggRecentModelMulti *model = EGG_RECENT_MODEL_MULTI (object); + + switch (prop_id) + { + case PROP_MIME_FILTERS: + if (model->priv->mime_filter_values != NULL) + egg_recent_model_multi_clear_mime_filter (model); + + model->priv->mime_filter_values = + (GSList *)g_value_get_pointer (value); + break; + + case PROP_GROUP_FILTERS: + if (model->priv->group_filter_values != NULL) + egg_recent_model_multi_clear_group_filter (model); + + model->priv->group_filter_values = + (GSList *)g_value_get_pointer (value); + break; + + case PROP_SCHEME_FILTERS: + if (model->priv->scheme_filter_values != NULL) + egg_recent_model_multi_clear_scheme_filter (model); + + model->priv->scheme_filter_values = + (GSList *)g_value_get_pointer (value); + break; + + case PROP_SORT_TYPE: + model->priv->sort_type = g_value_get_int (value); + break; + + case PROP_FILE_PATH: + model->priv->path = g_strdup_printf ( + "%s/%s", + g_get_home_dir (), + g_value_get_string (value) + ); + egg_recent_model_multi_monitor (model, TRUE); + break; + + case PROP_LIMIT: + egg_recent_model_multi_set_limit (model, + g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_recent_model_multi_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggRecentModelMulti *model = EGG_RECENT_MODEL_MULTI (object); + + switch (prop_id) + { + case PROP_MIME_FILTERS: + g_value_set_pointer (value, model->priv->mime_filter_values); + break; + + case PROP_GROUP_FILTERS: + g_value_set_pointer (value, model->priv->group_filter_values); + break; + + case PROP_SCHEME_FILTERS: + g_value_set_pointer (value, model->priv->scheme_filter_values); + break; + + case PROP_SORT_TYPE: + g_value_set_int (value, model->priv->sort_type); + break; + + case PROP_FILE_PATH: + g_value_set_string (value, model->priv->path); + break; + + case PROP_LIMIT: + g_value_set_int (value, model->priv->limit); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_recent_model_multi_class_init (EggRecentModelMultiClass * klass) +{ + GObjectClass *object_class; + + parent_class = g_type_class_peek_parent (klass); + + parent_class = g_type_class_peek_parent (klass); + + object_class = G_OBJECT_CLASS (klass); + object_class->set_property = egg_recent_model_multi_set_property; + object_class->get_property = egg_recent_model_multi_get_property; + object_class->finalize = egg_recent_model_multi_finalize; + + model_signals[CHANGED] = g_signal_new ("changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggRecentModelMultiClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + + g_object_class_install_property (object_class, + PROP_MIME_FILTERS, + g_param_spec_pointer ("mime-filters", + "Mime Filters", + "List of mime types to be allowed.", + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_GROUP_FILTERS, + g_param_spec_pointer ("group-filters", + "Group Filters", + "List of groups to be allowed.", + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SCHEME_FILTERS, + g_param_spec_pointer ("scheme-filters", + "Scheme Filters", + "List of URI schemes to be allowed.", + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SORT_TYPE, + g_param_spec_int ("sort-type", + "Sort Type", + "Type of sorting to be done.", + 0, EGG_RECENT_MODEL_MULTI_SORT_NONE, + EGG_RECENT_MODEL_MULTI_SORT_MRU, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_FILE_PATH, + g_param_spec_string ("file-path", + "File Path", + "File path relative to $HOME.", + EGG_RECENT_MODEL_MULTI_DEFAULT_FILE_PATH, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, + PROP_LIMIT, + g_param_spec_int ("limit", + "Limit", + "Max number of items allowed.", + -1, EGG_RECENT_MODEL_MULTI_MAX_ITEMS, + EGG_RECENT_MODEL_MULTI_DEFAULT_LIMIT, + G_PARAM_READWRITE)); + + klass->changed = NULL; +} + + + +static void +egg_recent_model_multi_limit_changed (GConfClient *client, guint cnxn_id, + GConfEntry *entry, gpointer user_data) +{ + EggRecentModelMulti *model; + GConfValue *value; + + model = EGG_RECENT_MODEL_MULTI (user_data); + + g_return_if_fail (model != NULL); + + if (model->priv->use_default_limit == FALSE) + return; /* ignore this key */ + + /* the key was unset, and the schema has apparently failed */ + if (entry == NULL) + return; + + value = gconf_entry_get_value (entry); + + if (value->type != GCONF_VALUE_INT) { + g_warning ("Expected GConfValue of type integer, " + "got something else"); + } + + + egg_recent_model_multi_set_limit_internal (model, gconf_value_get_int (value)); +} + +static void +egg_recent_model_multi_expiration_changed (GConfClient *client, guint cnxn_id, + GConfEntry *entry, gpointer user_data) +{ + +} + +static void +egg_recent_model_multi_init (EggRecentModelMulti * model) +{ + if (!gnome_vfs_init ()) { + g_warning ("gnome-vfs initialization failed."); + return; + } + + + model->priv = g_new0 (EggRecentModelMultiPrivate, 1); + + model->priv->mime_filter_values = NULL; + model->priv->group_filter_values = NULL; + model->priv->scheme_filter_values = NULL; + + model->priv->client = gconf_client_get_default (); + gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_MULTI_KEY_DIR, + GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + + model->priv->limit_change_notify_id = + gconf_client_notify_add (model->priv->client, + EGG_RECENT_MODEL_MULTI_DEFAULT_LIMIT_KEY, + egg_recent_model_multi_limit_changed, + model, NULL, NULL); + + model->priv->expiration_change_notify_id = + gconf_client_notify_add (model->priv->client, + EGG_RECENT_MODEL_MULTI_EXPIRE_KEY, + egg_recent_model_multi_expiration_changed, + model, NULL, NULL); + + model->priv->expire_days = gconf_client_get_int ( + model->priv->client, + EGG_RECENT_MODEL_MULTI_EXPIRE_KEY, + NULL); + +#if 0 + /* keep this out, for now */ + model->priv->limit = gconf_client_get_int ( + model->priv->client, + EGG_RECENT_MODEL_MULTI_DEFAULT_LIMIT_KEY, NULL); + model->priv->use_default_limit = TRUE; +#endif + model->priv->limit = EGG_RECENT_MODEL_MULTI_DEFAULT_LIMIT; + model->priv->use_default_limit = FALSE; + + model->priv->monitors = g_hash_table_new_full ( + g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) gnome_vfs_monitor_cancel); + + model->priv->monitor = NULL; + model->priv->poll_timeout = 0; + model->priv->last_mtime = 0; +} + + +/** + * egg_recent_model_multi_new: + * @sort: the type of sorting to use + * @limit: maximum number of items in the list + * + * This creates a new EggRecentModelMulti object. + * + * Returns: a EggRecentModelMulti object + */ +EggRecentModelMulti * +egg_recent_model_multi_new (EggRecentModelMultiSort sort) +{ + return egg_recent_model_multi_new_with_file_path (sort, EGG_RECENT_MODEL_MULTI_DEFAULT_FILE_PATH); +} + +EggRecentModelMulti * +egg_recent_model_multi_new_with_file_path (EggRecentModelMultiSort sort, const gchar *file_path) +{ + EggRecentModelMulti *model; + + if (! file_path) + file_path = EGG_RECENT_MODEL_MULTI_DEFAULT_FILE_PATH; + + model = EGG_RECENT_MODEL_MULTI (g_object_new (egg_recent_model_multi_get_type (), + "sort-type", sort, "file-path", file_path, NULL)); + + g_return_val_if_fail (model, NULL); + + return model; +} + +/** + * egg_recent_model_multi_add_full: + * @model: A EggRecentModelMulti object. + * @item: A EggRecentItem + * + * This function adds an item to the list of recently used URIs. + * + * Returns: gboolean + */ +gboolean +egg_recent_model_multi_add_full (EggRecentModelMulti * model, EggRecentItem *item) +{ + FILE *file; + GList *list = NULL; + gboolean ret = FALSE; + gboolean updated = FALSE; + char *uri; + time_t t; + + g_return_val_if_fail (model != NULL, FALSE); + g_return_val_if_fail (EGG_IS_RECENT_MODEL_MULTI (model), FALSE); + + uri = egg_recent_item_get_uri (item); + if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) { + g_free (uri); + return FALSE; + } else { + g_free (uri); + } + + file = egg_recent_model_multi_open_file (model, TRUE); + g_return_val_if_fail (file != NULL, FALSE); + + time (&t); + egg_recent_item_set_timestamp (item, t); + + if (egg_recent_model_multi_lock_file (file)) { + + /* read existing stuff */ + list = egg_recent_model_multi_read (model, file); + + /* if it's already there, we just update it */ + updated = egg_recent_model_multi_update_item (list, item); + + if (!updated) { + list = g_list_prepend (list, item); + + egg_recent_model_multi_enforce_limit (list, + EGG_RECENT_MODEL_MULTI_MAX_ITEMS); + } + + /* write new stuff */ + if (!egg_recent_model_multi_write (model, file, list)) + g_warning ("Write failed: %s", strerror (errno)); + + if (!updated) + list = g_list_remove (list, item); + + EGG_RECENT_ITEM_LIST_UNREF (list); + ret = TRUE; + } else { + g_warning ("Failed to lock: %s", strerror (errno)); + fclose (file); + return FALSE; + } + + if (!egg_recent_model_multi_unlock_file (file)) + g_warning ("Failed to unlock: %s", strerror (errno)); + + fclose (file); + + if (model->priv->monitor == NULL) { + /* since monitoring isn't working, at least give a + * local notification + */ + egg_recent_model_multi_changed (model); + } + + return ret; +} + +/** + * egg_recent_model_multi_add: + * @model: A EggRecentModelMulti object. + * @uri: A string URI + * + * This function adds an item to the list of recently used URIs. + * + * Returns: gboolean + */ +gboolean +egg_recent_model_multi_add (EggRecentModelMulti *model, const gchar *uri) +{ + EggRecentItem *item; + gboolean ret = FALSE; + + g_return_val_if_fail (model != NULL, FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + item = egg_recent_item_new_from_uri (uri); + + g_return_val_if_fail (item != NULL, FALSE); + + ret = egg_recent_model_multi_add_full (model, item); + + egg_recent_item_unref (item); + + return ret; +} + + + +/** + * egg_recent_model_multi_delete: + * @model: A EggRecentModelMulti object. + * @uri: The URI you want to delete. + * + * This function deletes a URI from the file of recently used URIs. + * + * Returns: gboolean + */ +gboolean +egg_recent_model_multi_delete (EggRecentModelMulti * model, const gchar * uri) +{ + FILE *file; + GList *list; + unsigned int length; + gboolean ret = FALSE; + + g_return_val_if_fail (model != NULL, FALSE); + g_return_val_if_fail (EGG_IS_RECENT_MODEL_MULTI (model), FALSE); + g_return_val_if_fail (uri != NULL, FALSE); + + file = egg_recent_model_multi_open_file (model, TRUE); + g_return_val_if_fail (file != NULL, FALSE); + + if (egg_recent_model_multi_lock_file (file)) { + list = egg_recent_model_multi_read (model, file); + + if (list == NULL) + goto out; + + length = g_list_length (list); + + list = egg_recent_model_multi_delete_from_list (list, uri); + + if (length == g_list_length (list)) { + /* nothing was deleted */ + EGG_RECENT_ITEM_LIST_UNREF (list); + } else { + egg_recent_model_multi_write (model, file, list); + EGG_RECENT_ITEM_LIST_UNREF (list); + ret = TRUE; + + } + } else { + g_warning ("Failed to lock: %s", strerror (errno)); + return FALSE; + } + +out: + + if (!egg_recent_model_multi_unlock_file (file)) + g_warning ("Failed to unlock: %s", strerror (errno)); + + fclose (file); + + g_hash_table_remove (model->priv->monitors, uri); + + if (model->priv->monitor == NULL && ret) { + /* since monitoring isn't working, at least give a + * local notification + */ + egg_recent_model_multi_changed (model); + } + + return ret; +} + + +/** + * egg_recent_model_multi_get_list: + * @model: A EggRecentModelMulti object. + * + * This function gets the current contents of the file + * + * Returns: a GList + */ +GList * +egg_recent_model_multi_get_list (EggRecentModelMulti *model) +{ + FILE *file; + GList *list = NULL; + + file = egg_recent_model_multi_open_file (model, FALSE); + if (file == NULL) + return NULL; + + if (egg_recent_model_multi_lock_file (file)) + list = egg_recent_model_multi_read (model, file); + else { + g_warning ("Failed to lock: %s", strerror (errno)); + fclose (file); + return NULL; + } + + if (!egg_recent_model_multi_unlock_file (file)) + g_warning ("Failed to unlock: %s", strerror (errno)); + + if (list != NULL) { + list = egg_recent_model_multi_filter (model, list); + list = egg_recent_model_multi_sort (model, list); + + egg_recent_model_multi_enforce_limit (list, model->priv->limit); + } + + fclose (file); + + return list; +} + + + +/** + * egg_recent_model_multi_set_limit: + * @model: A EggRecentModelMulti object. + * @limit: The maximum length of the list + * + * This function sets the maximum length of the list. Note: This only affects + * the length of the list emitted in the "changed" signal, not the list stored + * on disk. + * + * Returns: void + */ +void +egg_recent_model_multi_set_limit (EggRecentModelMulti *model, int limit) +{ + model->priv->use_default_limit = FALSE; + + egg_recent_model_multi_set_limit_internal (model, limit); +} + +/** + * egg_recent_model_multi_get_limit: + * @model: A EggRecentModelMulti object. + * + * This function gets the maximum length of the list. + * + * Returns: int + */ +int +egg_recent_model_multi_get_limit (EggRecentModelMulti *model) +{ + return model->priv->limit; +} + + +/** + * egg_recent_model_multi_clear: + * @model: A EggRecentModelMulti object. + * + * This function clears the contents of the file + * + * Returns: void + */ +void +egg_recent_model_multi_clear (EggRecentModelMulti *model) +{ + FILE *file; + int fd; + + file = egg_recent_model_multi_open_file (model, TRUE); + g_return_if_fail (file != NULL); + + fd = fileno (file); + + if (egg_recent_model_multi_lock_file (file)) { + ftruncate (fd, 0); + } else { + g_warning ("Failed to lock: %s", strerror (errno)); + return; + } + + if (!egg_recent_model_multi_unlock_file (file)) + g_warning ("Failed to unlock: %s", strerror (errno)); + + fclose (file); + + if (model->priv->monitor == NULL) { + /* since monitoring isn't working, at least give a + * local notification + */ + egg_recent_model_multi_changed (model); + } +} + +static void +egg_recent_model_multi_clear_mime_filter (EggRecentModelMulti *model) +{ + g_return_if_fail (model != NULL); + + if (model->priv->mime_filter_values != NULL) { + g_slist_foreach (model->priv->mime_filter_values, + (GFunc) g_pattern_spec_free, NULL); + g_slist_free (model->priv->mime_filter_values); + model->priv->mime_filter_values = NULL; + } +} + +/** + * egg_recent_model_multi_set_filter_mime_types: + * @model: A EggRecentModelMulti object. + * + * Sets which mime types are allowed in the list. + * + * Returns: void + */ +void +egg_recent_model_multi_set_filter_mime_types (EggRecentModelMulti *model, + ...) +{ + va_list valist; + GSList *list = NULL; + gchar *str; + + g_return_if_fail (model != NULL); + + egg_recent_model_multi_clear_mime_filter (model); + + va_start (valist, model); + + str = va_arg (valist, gchar*); + + while (str != NULL) { + list = g_slist_prepend (list, g_pattern_spec_new (str)); + + str = va_arg (valist, gchar*); + } + + va_end (valist); + + model->priv->mime_filter_values = list; +} + +static void +egg_recent_model_multi_clear_group_filter (EggRecentModelMulti *model) +{ + g_return_if_fail (model != NULL); + + if (model->priv->group_filter_values != NULL) { + g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL); + g_slist_free (model->priv->group_filter_values); + model->priv->group_filter_values = NULL; + } +} + +/** + * egg_recent_model_multi_set_filter_groups: + * @model: A EggRecentModelMulti object. + * + * Sets which groups are allowed in the list. + * + * Returns: void + */ +void +egg_recent_model_multi_set_filter_groups (EggRecentModelMulti *model, + ...) +{ + va_list valist; + GSList *list = NULL; + gchar *str; + + g_return_if_fail (model != NULL); + + egg_recent_model_multi_clear_group_filter (model); + + va_start (valist, model); + + str = va_arg (valist, gchar*); + + while (str != NULL) { + list = g_slist_prepend (list, g_strdup (str)); + + str = va_arg (valist, gchar*); + } + + va_end (valist); + + model->priv->group_filter_values = list; +} + +static void +egg_recent_model_multi_clear_scheme_filter (EggRecentModelMulti *model) +{ + g_return_if_fail (model != NULL); + + if (model->priv->scheme_filter_values != NULL) { + g_slist_foreach (model->priv->scheme_filter_values, + (GFunc) g_pattern_spec_free, NULL); + g_slist_free (model->priv->scheme_filter_values); + model->priv->scheme_filter_values = NULL; + } +} + +/** + * egg_recent_model_multi_set_filter_uri_schemes: + * @model: A EggRecentModelMulti object. + * + * Sets which URI schemes (file, http, ftp, etc) are allowed in the list. + * + * Returns: void + */ +void +egg_recent_model_multi_set_filter_uri_schemes (EggRecentModelMulti *model, ...) +{ + va_list valist; + GSList *list = NULL; + gchar *str; + + g_return_if_fail (model != NULL); + + egg_recent_model_multi_clear_scheme_filter (model); + + va_start (valist, model); + + str = va_arg (valist, gchar*); + + while (str != NULL) { + list = g_slist_prepend (list, g_pattern_spec_new (str)); + + str = va_arg (valist, gchar*); + } + + va_end (valist); + + model->priv->scheme_filter_values = list; +} + +/** + * egg_recent_model_multi_set_sort: + * @model: A EggRecentModelMulti object. + * @sort: A EggRecentModelMultiSort type + * + * Sets the type of sorting to be used. + * + * Returns: void + */ +void +egg_recent_model_multi_set_sort (EggRecentModelMulti *model, + EggRecentModelMultiSort sort) +{ + g_return_if_fail (model != NULL); + + model->priv->sort_type = sort; +} + +/** + * egg_recent_model_multi_changed: + * @model: A EggRecentModelMulti object. + * + * This function causes a "changed" signal to be emitted. + * + * Returns: void + */ +void +egg_recent_model_multi_changed (EggRecentModelMulti *model) +{ + GList *list = NULL; + + if (model->priv->limit > 0) { + list = egg_recent_model_multi_get_list (model); + /* egg_recent_model_multi_monitor_list (model, list); */ + + g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0, + list); + } + + if (list) + EGG_RECENT_ITEM_LIST_UNREF (list); +} + +static void +egg_recent_model_multi_remove_expired_list (EggRecentModelMulti *model, GList *list) +{ + time_t current_time; + time_t day_seconds; + + time (¤t_time); + day_seconds = model->priv->expire_days*24*60*60; + + while (list != NULL) { + EggRecentItem *item = list->data; + time_t timestamp; + + timestamp = egg_recent_item_get_timestamp (item); + + if ((timestamp+day_seconds) < current_time) { + gchar *uri = egg_recent_item_get_uri (item); + egg_recent_model_multi_delete (model, uri); + + g_strdup (uri); + } + + list = list->next; + } +} + + +/** + * egg_recent_model_multi_remove_expired: + * @model: A EggRecentModelMulti object. + * + * Goes through the entire list, and removes any items that are older than + * the user-specified expiration period. + * + * Returns: void + */ +void +egg_recent_model_multi_remove_expired (EggRecentModelMulti *model) +{ + FILE *file; + GList *list=NULL; + + g_return_if_fail (model != NULL); + + file = egg_recent_model_multi_open_file (model, FALSE); + if (file == NULL) + return; + + if (egg_recent_model_multi_lock_file (file)) { + list = egg_recent_model_multi_read (model, file); + + } else { + g_warning ("Failed to lock: %s", strerror (errno)); + return; + } + + if (!egg_recent_model_multi_unlock_file (file)) + g_warning ("Failed to unlock: %s", strerror (errno)); + + if (list != NULL) { + egg_recent_model_multi_remove_expired_list (model, list); + EGG_RECENT_ITEM_LIST_UNREF (list); + } + + fclose (file); +} + +/** + * egg_recent_model_multi_get_type: + * + * This returns a GType representing a EggRecentModelMulti object. + * + * Returns: a GType + */ +GType +egg_recent_model_multi_get_type (void) +{ + static GType egg_recent_model_multi_type = 0; + + if(!egg_recent_model_multi_type) { + static const GTypeInfo egg_recent_model_multi_info = { + sizeof (EggRecentModelMultiClass), + NULL, /* base init */ + NULL, /* base finalize */ + (GClassInitFunc)egg_recent_model_multi_class_init, /* class init */ + NULL, /* class finalize */ + NULL, /* class data */ + sizeof (EggRecentModelMulti), + 0, + (GInstanceInitFunc) egg_recent_model_multi_init + }; + + egg_recent_model_multi_type = g_type_register_static (G_TYPE_OBJECT, + "EggRecentModelMulti", + &egg_recent_model_multi_info, 0); + } + + return egg_recent_model_multi_type; +} + diff -urN ../gnome-desktop-2.12.2-pristine/libgnome-desktop/egg-recent-model-multi.h ./libgnome-desktop/egg-recent-model-multi.h --- ../gnome-desktop-2.12.2-pristine/libgnome-desktop/egg-recent-model-multi.h 1969-12-31 19:00:00.000000000 -0500 +++ ./libgnome-desktop/egg-recent-model-multi.h 2006-05-08 16:48:27.000000000 -0400 @@ -0,0 +1,81 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +#ifndef __EGG_RECENT_MODEL_MULTI_H__ +#define __EGG_RECENT_MODEL_MULTI_H__ + +#include "egg-recent-item.h" + +G_BEGIN_DECLS + +#define EGG_TYPE_RECENT_MODEL_MULTI (egg_recent_model_multi_get_type ()) +#define EGG_RECENT_MODEL_MULTI(obj) G_TYPE_CHECK_INSTANCE_CAST (obj, EGG_TYPE_RECENT_MODEL_MULTI, EggRecentModelMulti) +#define EGG_RECENT_MODEL_MULTI_CLASS(klass) G_TYPE_CHECK_CLASS_CAST (klass, EGG_TYPE_RECENT_MODEL_MULTI, EggRecentModelMultiClass) +#define EGG_IS_RECENT_MODEL_MULTI(obj) G_TYPE_CHECK_INSTANCE_TYPE (obj, egg_recent_model_multi_get_type ()) + +typedef struct _EggRecentModelMulti EggRecentModelMulti; +typedef struct _EggRecentModelMultiPrivate EggRecentModelMultiPrivate; +typedef struct _EggRecentModelMultiClass EggRecentModelMultiClass; + +struct _EggRecentModelMulti { + GObject parent_instance; + + EggRecentModelMultiPrivate *priv; +}; + +struct _EggRecentModelMultiClass { + GObjectClass parent_class; + + void (*changed) (EggRecentModelMulti *model, GList *list); +}; + +typedef enum { + EGG_RECENT_MODEL_MULTI_SORT_MRU, + EGG_RECENT_MODEL_MULTI_SORT_LRU, + EGG_RECENT_MODEL_MULTI_SORT_NONE +} EggRecentModelMultiSort; + + +/* Standard group names */ +#define EGG_RECENT_GROUP_LAUNCHERS "Launchers" + + +GType egg_recent_model_multi_get_type (void); + +/* constructors */ +EggRecentModelMulti * egg_recent_model_multi_new (EggRecentModelMultiSort sort); +EggRecentModelMulti * egg_recent_model_multi_new_with_file_path (EggRecentModelMultiSort sort, const gchar *file_path); + +/* public methods */ +void egg_recent_model_multi_set_filter_mime_types (EggRecentModelMulti *model, + ...); + +void egg_recent_model_multi_set_filter_groups (EggRecentModelMulti *model, ...); + +void egg_recent_model_multi_set_filter_uri_schemes (EggRecentModelMulti *model, + ...); + +void egg_recent_model_multi_set_sort (EggRecentModelMulti *model, + EggRecentModelMultiSort sort); + +gboolean egg_recent_model_multi_add_full (EggRecentModelMulti *model, + EggRecentItem *item); + +gboolean egg_recent_model_multi_add (EggRecentModelMulti *model, + const gchar *uri); + +gboolean egg_recent_model_multi_delete (EggRecentModelMulti *model, + const gchar *uri); + +void egg_recent_model_multi_clear (EggRecentModelMulti *model); + +GList * egg_recent_model_multi_get_list (EggRecentModelMulti *model); + +void egg_recent_model_multi_changed (EggRecentModelMulti *model); + +void egg_recent_model_multi_set_limit (EggRecentModelMulti *model, int limit); +int egg_recent_model_multi_get_limit (EggRecentModelMulti *model); + +void egg_recent_model_multi_remove_expired (EggRecentModelMulti *model); + +G_END_DECLS + +#endif /* __EGG_RECENT_MODEL_MULTI_H__ */ diff -urN ../gnome-desktop-2.12.2-pristine/libgnome-desktop/gnome-desktop-item.c ./libgnome-desktop/gnome-desktop-item.c --- ../gnome-desktop-2.12.2-pristine/libgnome-desktop/gnome-desktop-item.c 2005-11-04 03:43:42.000000000 -0500 +++ ./libgnome-desktop/gnome-desktop-item.c 2006-05-08 17:07:23.000000000 -0400 @@ -59,6 +59,9 @@ #include #endif +#include "egg-recent-item.h" +#include "egg-recent-model-multi.h" + #define sure_string(s) ((s)!=NULL?(s):"") struct _GnomeDesktopItem { @@ -128,6 +131,8 @@ const char *uri, GError **error); +static void update_recently_used_apps (const GnomeDesktopItem *item); + static int readbuf_getc (ReadBuf *rb) { @@ -2091,6 +2096,8 @@ (flags & GNOME_DESKTOP_ITEM_LAUNCH_APPEND_PATHS), error); + update_recently_used_apps (item); + return ret; } @@ -4095,3 +4102,22 @@ return q; } + +static void +update_recently_used_apps (const GnomeDesktopItem *item) +{ + EggRecentModelMulti *model; + + + if (! item) + return; + + model = egg_recent_model_multi_new_with_file_path (EGG_RECENT_MODEL_MULTI_SORT_MRU, ".recently-used-apps"); + + if (! model) + return; + + egg_recent_model_multi_add (model, gnome_desktop_item_get_location (item)); + + g_object_unref (G_OBJECT (model)); +} Files ../gnome-desktop-2.12.2-pristine/libgnome-desktop/.gnome-desktop-item.c.swp and ./libgnome-desktop/.gnome-desktop-item.c.swp differ diff -urN ../gnome-desktop-2.12.2-pristine/libgnome-desktop/Makefile.am ./libgnome-desktop/Makefile.am --- ../gnome-desktop-2.12.2-pristine/libgnome-desktop/Makefile.am 2004-11-18 12:42:53.000000000 -0500 +++ ./libgnome-desktop/Makefile.am 2006-05-08 16:55:25.000000000 -0400 @@ -16,9 +16,13 @@ noinst_PROGRAMS = test-ditem test-hint test-ditem-edit -libgnome_desktop_2_la_SOURCES = \ - gnome-desktop-item.c \ - gnome-ditem-edit.c \ +libgnome_desktop_2_la_SOURCES = \ + gnome-desktop-item.c \ + gnome-ditem-edit.c \ + egg-recent-item.h \ + egg-recent-item.c \ + egg-recent-model-multi.h \ + egg-recent-model-multi.c \ gnome-hint.c libgnome_desktop_2_la_LIBADD = \