1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3
* This program is free software; you can redistribute it and/or modify
4
* it under the terms of the GNU General Public License as
5
* published by the Free Software Foundation; either version 2 of the
6
* License, or (at your option) any later version.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program; if not, write to the Free Software
15
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
18
* James Willcox <jwillcox@cs.indiana.edu>
34
#include <libgnomevfs/gnome-vfs.h>
35
#include <libgnomevfs/gnome-vfs-mime-utils.h>
36
#include <gconf/gconf-client.h>
37
#include "egg-recent-model.h"
38
#include "egg-recent-item.h"
40
#define EGG_RECENT_MODEL_FILE_PATH "/.recently-used"
41
#define EGG_RECENT_MODEL_BUFFER_SIZE 8192
43
#define EGG_RECENT_MODEL_MAX_ITEMS 500
44
#define EGG_RECENT_MODEL_DEFAULT_LIMIT 10
45
#define EGG_RECENT_MODEL_TIMEOUT_LENGTH 200
47
#define EGG_RECENT_MODEL_KEY_DIR "/desktop/gnome/recent_files"
48
#define EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY EGG_RECENT_MODEL_KEY_DIR "/default_limit"
49
#define EGG_RECENT_MODEL_EXPIRE_KEY EGG_RECENT_MODEL_KEY_DIR "/expire"
51
struct _EggRecentModelPrivate {
52
GSList *mime_filter_values; /* list of mime types we allow */
53
GSList *group_filter_values; /* list of groups we allow */
54
GSList *scheme_filter_values; /* list of URI schemes we allow */
56
EggRecentModelSort sort_type; /* type of sorting to be done */
58
int limit; /* soft limit for length of the list */
59
int expire_days; /* number of days to hold an item */
61
char *path; /* path to the file we store stuff in */
65
GnomeVFSMonitorHandle *monitor;
68
gboolean use_default_limit;
70
guint limit_change_notify_id;
71
guint expiration_change_notify_id;
73
guint changed_timeout;
82
static GType model_signals[LAST_SIGNAL] = { 0 };
97
EggRecentItem *current_item;
112
typedef struct _ChangedData {
113
EggRecentModel *model;
117
#define TAG_RECENT_FILES "RecentFiles"
118
#define TAG_RECENT_ITEM "RecentItem"
119
#define TAG_URI "URI"
120
#define TAG_MIME_TYPE "Mime-Type"
121
#define TAG_TIMESTAMP "Timestamp"
122
#define TAG_PRIVATE "Private"
123
#define TAG_GROUPS "Groups"
124
#define TAG_GROUP "Group"
126
static void start_element_handler (GMarkupParseContext *context,
127
const gchar *element_name,
128
const gchar **attribute_names,
129
const gchar **attribute_values,
133
static void end_element_handler (GMarkupParseContext *context,
134
const gchar *element_name,
138
static void text_handler (GMarkupParseContext *context,
144
static void error_handler (GMarkupParseContext *context,
148
static GMarkupParser parser = {start_element_handler, end_element_handler,
154
egg_recent_model_string_match (const GSList *list, const gchar *str)
158
if (list == NULL || str == NULL)
164
if (g_pattern_match_string (tmp->data, str))
174
egg_recent_model_write_raw (EggRecentModel *model, FILE *file,
175
const gchar *content)
183
len = strlen (content);
186
if (fstat (fd, &sbuf) < 0)
187
g_warning ("Couldn't stat XML document.");
189
if ((off_t)len < sbuf.st_size) {
193
if (fputs (content, file) == EOF)
203
egg_recent_model_delete_from_list (GList *list,
214
EggRecentItem *item = tmp->data;
219
if (!strcmp (egg_recent_item_peek_uri (item), uri)) {
220
egg_recent_item_unref (item);
222
list = g_list_remove_link (list, tmp);
233
egg_recent_model_add_new_groups (EggRecentItem *item,
234
EggRecentItem *upd_item)
238
tmp = egg_recent_item_get_groups (upd_item);
241
char *group = tmp->data;
243
if (!egg_recent_item_in_group (item, group))
244
egg_recent_item_add_group (item, group);
251
egg_recent_model_update_item (GList *items, EggRecentItem *upd_item)
256
uri = egg_recent_item_peek_uri (upd_item);
261
EggRecentItem *item = tmp->data;
263
if (gnome_vfs_uris_match (egg_recent_item_peek_uri (item), uri)) {
264
egg_recent_item_set_timestamp (item, (time_t) -1);
266
egg_recent_model_add_new_groups (item, upd_item);
278
egg_recent_model_read_raw (EggRecentModel *model, FILE *file)
281
char buf[EGG_RECENT_MODEL_BUFFER_SIZE];
285
string = g_string_new (NULL);
286
while (fgets (buf, EGG_RECENT_MODEL_BUFFER_SIZE, file)) {
287
string = g_string_append (string, buf);
292
return g_string_free (string, FALSE);
298
parse_info_init (ParseInfo *info)
300
info->states = g_slist_prepend (NULL, STATE_START);
305
parse_info_free (ParseInfo *info)
307
g_slist_free (info->states);
311
push_state (ParseInfo *info,
314
info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
318
pop_state (ParseInfo *info)
320
g_return_if_fail (info->states != NULL);
322
info->states = g_slist_remove (info->states, info->states->data);
326
peek_state (ParseInfo *info)
328
g_return_val_if_fail (info->states != NULL, STATE_START);
330
return GPOINTER_TO_INT (info->states->data);
333
#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
336
start_element_handler (GMarkupParseContext *context,
337
const gchar *element_name,
338
const gchar **attribute_names,
339
const gchar **attribute_values,
343
ParseInfo *info = (ParseInfo *)user_data;
345
if (ELEMENT_IS (TAG_RECENT_FILES))
346
push_state (info, STATE_RECENT_FILES);
347
else if (ELEMENT_IS (TAG_RECENT_ITEM)) {
348
info->current_item = egg_recent_item_new ();
349
push_state (info, STATE_RECENT_ITEM);
350
} else if (ELEMENT_IS (TAG_URI))
351
push_state (info, STATE_URI);
352
else if (ELEMENT_IS (TAG_MIME_TYPE))
353
push_state (info, STATE_MIME_TYPE);
354
else if (ELEMENT_IS (TAG_TIMESTAMP))
355
push_state (info, STATE_TIMESTAMP);
356
else if (ELEMENT_IS (TAG_PRIVATE)) {
357
push_state (info, STATE_PRIVATE);
358
egg_recent_item_set_private (info->current_item, TRUE);
359
} else if (ELEMENT_IS (TAG_GROUPS))
360
push_state (info, STATE_GROUPS);
361
else if (ELEMENT_IS (TAG_GROUP))
362
push_state (info, STATE_GROUP);
366
list_compare_func_mru (gpointer a, gpointer b)
368
EggRecentItem *item_a = (EggRecentItem *)a;
369
EggRecentItem *item_b = (EggRecentItem *)b;
371
return item_a->timestamp < item_b->timestamp;
375
list_compare_func_lru (gpointer a, gpointer b)
377
EggRecentItem *item_a = (EggRecentItem *)a;
378
EggRecentItem *item_b = (EggRecentItem *)b;
380
return item_a->timestamp > item_b->timestamp;
386
end_element_handler (GMarkupParseContext *context,
387
const gchar *element_name,
391
ParseInfo *info = (ParseInfo *)user_data;
393
switch (peek_state (info)) {
394
case STATE_RECENT_ITEM:
395
info->items = g_list_append (info->items,
397
if (info->current_item->uri == NULL ||
398
strlen (info->current_item->uri) == 0)
399
g_warning ("URI NOT LOADED");
409
text_handler (GMarkupParseContext *context,
415
ParseInfo *info = (ParseInfo *)user_data;
417
switch (peek_state (info)) {
419
case STATE_RECENT_FILES:
420
case STATE_RECENT_ITEM:
425
egg_recent_item_set_uri (info->current_item, text);
427
case STATE_MIME_TYPE:
428
egg_recent_item_set_mime_type (info->current_item,
431
case STATE_TIMESTAMP:
432
egg_recent_item_set_timestamp (info->current_item,
433
(time_t)atoi (text));
436
egg_recent_item_add_group (info->current_item,
444
error_handler (GMarkupParseContext *context,
448
g_warning ("Error in parse: %s", error->message);
452
egg_recent_model_enforce_limit (GList *list, int limit)
457
/* limit < 0 means unlimited */
461
len = g_list_length (list);
466
end = g_list_nth (list, limit-1);
471
EGG_RECENT_ITEM_LIST_UNREF (next);
476
egg_recent_model_sort (EggRecentModel *model, GList *list)
478
switch (model->priv->sort_type) {
479
case EGG_RECENT_MODEL_SORT_MRU:
480
list = g_list_sort (list,
481
(GCompareFunc)list_compare_func_mru);
483
case EGG_RECENT_MODEL_SORT_LRU:
484
list = g_list_sort (list,
485
(GCompareFunc)list_compare_func_lru);
487
case EGG_RECENT_MODEL_SORT_NONE:
495
egg_recent_model_group_match (EggRecentItem *item, GSList *groups)
501
while (tmp != NULL) {
502
const gchar * group = (const gchar *)tmp->data;
504
if (egg_recent_item_in_group (item, group))
514
egg_recent_model_filter (EggRecentModel *model,
518
GList *newlist = NULL;
522
g_return_val_if_fail (list != NULL, NULL);
525
gboolean pass_mime_test = FALSE;
526
gboolean pass_group_test = FALSE;
527
gboolean pass_scheme_test = FALSE;
528
item = (EggRecentItem *)list->data;
531
uri = egg_recent_item_get_uri (item);
533
/* filter by mime type */
534
if (model->priv->mime_filter_values != NULL) {
535
mime_type = egg_recent_item_get_mime_type (item);
537
if (egg_recent_model_string_match
538
(model->priv->mime_filter_values,
540
pass_mime_test = TRUE;
544
pass_mime_test = TRUE;
546
/* filter by group */
547
if (pass_mime_test && model->priv->group_filter_values != NULL) {
548
if (egg_recent_model_group_match
549
(item, model->priv->group_filter_values))
550
pass_group_test = TRUE;
551
} else if (egg_recent_item_get_private (item)) {
552
pass_group_test = FALSE;
554
pass_group_test = TRUE;
556
/* filter by URI scheme */
557
if (pass_mime_test && pass_group_test &&
558
model->priv->scheme_filter_values != NULL) {
561
scheme = gnome_vfs_get_uri_scheme (uri);
563
if (egg_recent_model_string_match
564
(model->priv->scheme_filter_values, scheme))
565
pass_scheme_test = TRUE;
569
pass_scheme_test = TRUE;
571
if (pass_mime_test && pass_group_test && pass_scheme_test)
572
newlist = g_list_prepend (newlist, item);
578
newlist = g_list_reverse (newlist);
590
egg_recent_model_monitor_list_cb (GnomeVFSMonitorHandle *handle,
591
const gchar *monitor_uri,
592
const gchar *info_uri,
593
GnomeVFSMonitorEventType event_type,
596
EggRecentModel *model;
598
model = EGG_RECENT_MODEL (user_data);
600
if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED) {
601
egg_recent_model_delete (model, monitor_uri);
602
g_hash_table_remove (model->priv->monitors, monitor_uri);
609
egg_recent_model_monitor_list (EggRecentModel *model, GList *list)
615
EggRecentItem *item = (EggRecentItem *)tmp->data;
616
GnomeVFSMonitorHandle *handle;
622
uri = egg_recent_item_get_uri (item);
623
if (g_hash_table_lookup (model->priv->monitors, uri)) {
624
/* already monitoring this one */
629
res = gnome_vfs_monitor_add (&handle, uri,
630
GNOME_VFS_MONITOR_FILE,
631
egg_recent_model_monitor_list_cb,
634
if (res == GNOME_VFS_OK)
635
g_hash_table_insert (model->priv->monitors, uri, handle);
644
egg_recent_model_changed_timeout (EggRecentModel *model)
646
model->priv->changed_timeout = 0;
648
egg_recent_model_changed (model);
654
egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
655
const gchar *monitor_uri,
656
const gchar *info_uri,
657
GnomeVFSMonitorEventType event_type,
660
EggRecentModel *model;
662
g_return_if_fail (user_data != NULL);
663
g_return_if_fail (EGG_IS_RECENT_MODEL (user_data));
664
model = EGG_RECENT_MODEL (user_data);
666
if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
667
if (model->priv->changed_timeout > 0) {
668
g_source_remove (model->priv->changed_timeout);
671
model->priv->changed_timeout = g_timeout_add (
672
EGG_RECENT_MODEL_TIMEOUT_LENGTH,
673
(GSourceFunc)egg_recent_model_changed_timeout,
679
egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
681
if (should_monitor && model->priv->monitor == NULL) {
684
uri = gnome_vfs_get_uri_from_local_path (model->priv->path);
686
gnome_vfs_monitor_add (&model->priv->monitor,
688
GNOME_VFS_MONITOR_FILE,
689
egg_recent_model_monitor_cb,
694
/* if the above fails, don't worry about it.
695
* local notifications will still happen
698
} else if (!should_monitor && model->priv->monitor != NULL) {
699
gnome_vfs_monitor_cancel (model->priv->monitor);
700
model->priv->monitor = NULL;
705
egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
707
model->priv->limit = limit;
710
egg_recent_model_monitor (model, FALSE);
712
egg_recent_model_monitor (model, TRUE);
713
egg_recent_model_changed (model);
718
egg_recent_model_read (EggRecentModel *model, FILE *file)
722
GMarkupParseContext *ctx;
726
content = egg_recent_model_read_raw (model, file);
728
if (strlen (content) <= 0) {
733
parse_info_init (&info);
735
ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
738
if (!g_markup_parse_context_parse (ctx, content, strlen (content),
740
g_warning (error->message);
741
g_error_free (error);
747
if (!g_markup_parse_context_end_parse (ctx, &error))
750
g_markup_parse_context_free (ctx);
754
parse_info_free (&info);
759
g_print ("Total items: %d\n", g_list_length (list));
767
egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
776
string = g_string_new ("<?xml version=\"1.0\"?>\n");
777
string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
785
item = (EggRecentItem *)list->data;
788
uri = egg_recent_item_get_uri_utf8 (item);
789
escaped_uri = g_markup_escape_text (uri,
793
mime_type = egg_recent_item_get_mime_type (item);
794
timestamp = egg_recent_item_get_timestamp (item);
796
string = g_string_append (string, " <" TAG_RECENT_ITEM ">\n");
798
g_string_append_printf (string,
799
" <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
802
g_string_append_printf (string,
803
" <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
805
g_string_append_printf (string,
806
" <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
809
g_string_append_printf (string,
810
" <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
812
if (egg_recent_item_get_private (item))
813
string = g_string_append (string,
814
" <" TAG_PRIVATE "/>\n");
816
/* write the groups */
817
string = g_string_append (string,
818
" <" TAG_GROUPS ">\n");
819
groups = egg_recent_item_get_groups (item);
821
if (groups == NULL && egg_recent_item_get_private (item))
822
g_warning ("Item with URI \"%s\" marked as private, but"
823
" does not belong to any groups.\n", uri);
826
const gchar *group = (const gchar *)groups->data;
827
gchar *escaped_group;
829
escaped_group = g_markup_escape_text (group, strlen(group));
831
g_string_append_printf (string,
832
" <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
835
g_free (escaped_group);
837
groups = groups->next;
840
string = g_string_append (string, " </" TAG_GROUPS ">\n");
842
string = g_string_append (string,
843
" </" TAG_RECENT_ITEM ">\n");
846
g_free (escaped_uri);
852
string = g_string_append (string, "</" TAG_RECENT_FILES ">");
854
data = g_string_free (string, FALSE);
856
ret = egg_recent_model_write_raw (model, file, data);
864
egg_recent_model_open_file (EggRecentModel *model)
869
file = fopen (model->priv->path, "r+");
872
prev_umask = umask (077);
874
file = fopen (model->priv->path, "w+");
878
g_return_val_if_fail (file != NULL, NULL);
885
egg_recent_model_lock_file (FILE *file)
893
/* Attempt to lock the file 5 times,
894
* waiting a random interval (< 1 second)
895
* in between attempts.
896
* We should really be doing asynchronous
897
* locking, but requires substantially larger
905
if (lockf (fd, F_TLOCK, 0) == 0)
908
rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
910
g_usleep (100000 * rand_interval);
919
egg_recent_model_unlock_file (FILE *file)
926
return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
930
egg_recent_model_finalize (GObject *object)
932
EggRecentModel *model = EGG_RECENT_MODEL (object);
934
if (model->priv->changed_timeout > 0) {
935
g_source_remove (model->priv->changed_timeout);
938
egg_recent_model_monitor (model, FALSE);
941
g_slist_foreach (model->priv->mime_filter_values,
942
(GFunc) g_pattern_spec_free, NULL);
943
g_slist_free (model->priv->mime_filter_values);
944
model->priv->mime_filter_values = NULL;
946
g_slist_foreach (model->priv->scheme_filter_values,
947
(GFunc) g_pattern_spec_free, NULL);
948
g_slist_free (model->priv->scheme_filter_values);
949
model->priv->scheme_filter_values = NULL;
951
g_slist_foreach (model->priv->group_filter_values,
952
(GFunc) g_free, NULL);
953
g_slist_free (model->priv->group_filter_values);
954
model->priv->group_filter_values = NULL;
957
if (model->priv->limit_change_notify_id)
958
gconf_client_notify_remove (model->priv->client,
959
model->priv->limit_change_notify_id);
960
model->priv->expiration_change_notify_id = 0;
962
if (model->priv->expiration_change_notify_id)
963
gconf_client_notify_remove (model->priv->client,
964
model->priv->expiration_change_notify_id);
965
model->priv->expiration_change_notify_id = 0;
967
g_object_unref (model->priv->client);
968
model->priv->client = NULL;
971
g_free (model->priv->path);
972
model->priv->path = NULL;
974
g_hash_table_destroy (model->priv->monitors);
975
model->priv->monitors = NULL;
978
g_free (model->priv);
982
egg_recent_model_set_property (GObject *object,
987
EggRecentModel *model = EGG_RECENT_MODEL (object);
991
case PROP_MIME_FILTERS:
992
model->priv->mime_filter_values =
993
(GSList *)g_value_get_pointer (value);
996
case PROP_GROUP_FILTERS:
997
model->priv->group_filter_values =
998
(GSList *)g_value_get_pointer (value);
1001
case PROP_SCHEME_FILTERS:
1002
model->priv->scheme_filter_values =
1003
(GSList *)g_value_get_pointer (value);
1006
case PROP_SORT_TYPE:
1007
model->priv->sort_type = g_value_get_int (value);
1011
egg_recent_model_set_limit (model,
1012
g_value_get_int (value));
1016
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1022
egg_recent_model_get_property (GObject *object,
1027
EggRecentModel *model = EGG_RECENT_MODEL (object);
1031
case PROP_MIME_FILTERS:
1032
g_value_set_pointer (value, model->priv->mime_filter_values);
1035
case PROP_GROUP_FILTERS:
1036
g_value_set_pointer (value, model->priv->group_filter_values);
1039
case PROP_SCHEME_FILTERS:
1040
g_value_set_pointer (value, model->priv->scheme_filter_values);
1043
case PROP_SORT_TYPE:
1044
g_value_set_int (value, model->priv->sort_type);
1048
g_value_set_int (value, model->priv->limit);
1052
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1058
egg_recent_model_class_init (EggRecentModelClass * klass)
1060
GObjectClass *object_class;
1062
object_class = G_OBJECT_CLASS (klass);
1063
object_class->set_property = egg_recent_model_set_property;
1064
object_class->get_property = egg_recent_model_get_property;
1065
object_class->finalize = egg_recent_model_finalize;
1067
model_signals[CHANGED] = g_signal_new ("changed",
1068
G_OBJECT_CLASS_TYPE (object_class),
1070
G_STRUCT_OFFSET (EggRecentModelClass, changed),
1072
g_cclosure_marshal_VOID__POINTER,
1077
g_object_class_install_property (object_class,
1079
g_param_spec_pointer ("mime-filters",
1081
"List of mime types to be allowed.",
1082
G_PARAM_READWRITE));
1084
g_object_class_install_property (object_class,
1086
g_param_spec_pointer ("group-filters",
1088
"List of groups to be allowed.",
1089
G_PARAM_READWRITE));
1091
g_object_class_install_property (object_class,
1092
PROP_SCHEME_FILTERS,
1093
g_param_spec_pointer ("scheme-filters",
1095
"List of URI schemes to be allowed.",
1096
G_PARAM_READWRITE));
1098
g_object_class_install_property (object_class,
1100
g_param_spec_int ("sort-type",
1102
"Type of sorting to be done.",
1103
0, EGG_RECENT_MODEL_SORT_NONE,
1104
EGG_RECENT_MODEL_SORT_MRU,
1105
G_PARAM_READWRITE));
1107
g_object_class_install_property (object_class,
1109
g_param_spec_int ("limit",
1111
"Max number of items allowed.",
1112
-1, EGG_RECENT_MODEL_MAX_ITEMS,
1113
EGG_RECENT_MODEL_DEFAULT_LIMIT,
1114
G_PARAM_READWRITE));
1116
klass->changed = NULL;
1122
egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
1123
GConfEntry *entry, gpointer user_data)
1125
EggRecentModel *model;
1128
model = EGG_RECENT_MODEL (user_data);
1130
g_return_if_fail (model != NULL);
1132
if (model->priv->use_default_limit == FALSE)
1133
return; /* ignore this key */
1135
/* the key was unset, and the schema has apparently failed */
1139
value = gconf_entry_get_value (entry);
1141
if (value->type != GCONF_VALUE_INT) {
1142
g_warning ("Expected GConfValue of type integer, "
1143
"got something else");
1147
egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
1151
egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
1152
GConfEntry *entry, gpointer user_data)
1158
egg_recent_model_init (EggRecentModel * model)
1160
if (!gnome_vfs_init ()) {
1161
g_warning ("gnome-vfs initialization failed.");
1166
model->priv = g_new0 (EggRecentModelPrivate, 1);
1168
model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
1171
model->priv->mime_filter_values = NULL;
1172
model->priv->group_filter_values = NULL;
1173
model->priv->scheme_filter_values = NULL;
1175
model->priv->client = gconf_client_get_default ();
1176
gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR,
1177
GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
1179
model->priv->limit_change_notify_id =
1180
gconf_client_notify_add (model->priv->client,
1181
EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY,
1182
egg_recent_model_limit_changed,
1185
model->priv->expiration_change_notify_id =
1186
gconf_client_notify_add (model->priv->client,
1187
EGG_RECENT_MODEL_EXPIRE_KEY,
1188
egg_recent_model_expiration_changed,
1191
model->priv->expire_days = gconf_client_get_int (
1192
model->priv->client,
1193
EGG_RECENT_MODEL_EXPIRE_KEY,
1197
/* keep this out, for now */
1198
model->priv->limit = gconf_client_get_int (
1199
model->priv->client,
1200
EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL);
1201
model->priv->use_default_limit = TRUE;
1203
model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
1204
model->priv->use_default_limit = FALSE;
1206
model->priv->monitors = g_hash_table_new_full (
1207
g_str_hash, g_str_equal,
1208
(GDestroyNotify) g_free,
1209
(GDestroyNotify) gnome_vfs_monitor_cancel);
1211
model->priv->monitor = NULL;
1212
egg_recent_model_monitor (model, TRUE);
1217
* egg_recent_model_new:
1218
* @sort: the type of sorting to use
1219
* @limit: maximum number of items in the list
1221
* This creates a new EggRecentModel object.
1223
* Returns: a EggRecentModel object
1226
egg_recent_model_new (EggRecentModelSort sort)
1228
EggRecentModel *model;
1230
model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
1231
"sort-type", sort, NULL));
1233
g_return_val_if_fail (model, NULL);
1239
* egg_recent_model_add_full:
1240
* @model: A EggRecentModel object.
1241
* @item: A EggRecentItem
1243
* This function adds an item to the list of recently used URIs.
1248
egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
1252
gboolean ret = FALSE;
1253
gboolean updated = FALSE;
1257
g_return_val_if_fail (model != NULL, FALSE);
1258
g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1260
uri = egg_recent_item_get_uri (item);
1261
if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
1268
file = egg_recent_model_open_file (model);
1269
g_return_val_if_fail (file != NULL, FALSE);
1272
egg_recent_item_set_timestamp (item, t);
1274
if (egg_recent_model_lock_file (file)) {
1276
/* read existing stuff */
1277
list = egg_recent_model_read (model, file);
1279
/* if it's already there, we just update it */
1280
updated = egg_recent_model_update_item (list, item);
1283
list = g_list_prepend (list, item);
1285
egg_recent_model_enforce_limit (list,
1286
EGG_RECENT_MODEL_MAX_ITEMS);
1289
/* write new stuff */
1290
if (!egg_recent_model_write (model, file, list))
1291
g_warning ("Write failed: %s", strerror (errno));
1294
list = g_list_remove (list, item);
1296
EGG_RECENT_ITEM_LIST_UNREF (list);
1299
g_warning ("Failed to lock: %s", strerror (errno));
1304
if (!egg_recent_model_unlock_file (file))
1305
g_warning ("Failed to unlock: %s", strerror (errno));
1309
if (model->priv->monitor == NULL) {
1310
/* since monitoring isn't working, at least give a
1311
* local notification
1313
egg_recent_model_changed (model);
1320
* egg_recent_model_add:
1321
* @model: A EggRecentModel object.
1322
* @uri: A string URI
1324
* This function adds an item to the list of recently used URIs.
1329
egg_recent_model_add (EggRecentModel *model, const gchar *uri)
1331
EggRecentItem *item;
1332
gboolean ret = FALSE;
1334
g_return_val_if_fail (model != NULL, FALSE);
1335
g_return_val_if_fail (uri != NULL, FALSE);
1337
item = egg_recent_item_new_from_uri (uri);
1339
g_return_val_if_fail (item != NULL, FALSE);
1341
ret = egg_recent_model_add_full (model, item);
1343
egg_recent_item_unref (item);
1351
* egg_recent_model_delete:
1352
* @model: A EggRecentModel object.
1353
* @uri: The URI you want to delete.
1355
* This function deletes a URI from the file of recently used URIs.
1360
egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
1364
unsigned int length;
1365
gboolean ret = FALSE;
1367
g_return_val_if_fail (model != NULL, FALSE);
1368
g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
1369
g_return_val_if_fail (uri != NULL, FALSE);
1371
file = egg_recent_model_open_file (model);
1372
g_return_val_if_fail (file != NULL, FALSE);
1374
if (egg_recent_model_lock_file (file)) {
1375
list = egg_recent_model_read (model, file);
1380
length = g_list_length (list);
1382
list = egg_recent_model_delete_from_list (list, uri);
1384
if (length == g_list_length (list)) {
1385
/* nothing was deleted */
1386
EGG_RECENT_ITEM_LIST_UNREF (list);
1388
egg_recent_model_write (model, file, list);
1389
EGG_RECENT_ITEM_LIST_UNREF (list);
1394
g_warning ("Failed to lock: %s", strerror (errno));
1400
if (!egg_recent_model_unlock_file (file))
1401
g_warning ("Failed to unlock: %s", strerror (errno));
1405
g_hash_table_remove (model->priv->monitors, uri);
1407
if (model->priv->monitor == NULL && ret) {
1408
/* since monitoring isn't working, at least give a
1409
* local notification
1411
egg_recent_model_changed (model);
1419
* egg_recent_model_get_list:
1420
* @model: A EggRecentModel object.
1422
* This function gets the current contents of the file
1427
egg_recent_model_get_list (EggRecentModel *model)
1432
file = egg_recent_model_open_file (model);
1433
g_return_val_if_fail (file != NULL, NULL);
1435
if (egg_recent_model_lock_file (file)) {
1436
list = egg_recent_model_read (model, file);
1439
g_warning ("Failed to lock: %s", strerror (errno));
1444
if (!egg_recent_model_unlock_file (file))
1445
g_warning ("Failed to unlock: %s", strerror (errno));
1448
list = egg_recent_model_filter (model, list);
1449
list = egg_recent_model_sort (model, list);
1451
egg_recent_model_enforce_limit (list, model->priv->limit);
1462
* egg_recent_model_set_limit:
1463
* @model: A EggRecentModel object.
1464
* @limit: The maximum length of the list
1466
* This function sets the maximum length of the list. Note: This only affects
1467
* the length of the list emitted in the "changed" signal, not the list stored
1473
egg_recent_model_set_limit (EggRecentModel *model, int limit)
1475
model->priv->use_default_limit = FALSE;
1477
egg_recent_model_set_limit_internal (model, limit);
1481
* egg_recent_model_get_limit:
1482
* @model: A EggRecentModel object.
1484
* This function gets the maximum length of the list.
1489
egg_recent_model_get_limit (EggRecentModel *model)
1491
return model->priv->limit;
1496
* egg_recent_model_clear:
1497
* @model: A EggRecentModel object.
1499
* This function clears the contents of the file
1504
egg_recent_model_clear (EggRecentModel *model)
1509
file = egg_recent_model_open_file (model);
1510
g_return_if_fail (file != NULL);
1514
if (egg_recent_model_lock_file (file)) {
1517
g_warning ("Failed to lock: %s", strerror (errno));
1521
if (!egg_recent_model_unlock_file (file))
1522
g_warning ("Failed to unlock: %s", strerror (errno));
1529
* egg_recent_model_set_filter_mime_types:
1530
* @model: A EggRecentModel object.
1532
* Sets which mime types are allowed in the list.
1537
egg_recent_model_set_filter_mime_types (EggRecentModel *model,
1541
GSList *list = NULL;
1544
g_return_if_fail (model != NULL);
1546
if (model->priv->mime_filter_values != NULL) {
1547
g_slist_foreach (model->priv->mime_filter_values,
1548
(GFunc) g_pattern_spec_free, NULL);
1549
g_slist_free (model->priv->mime_filter_values);
1550
model->priv->mime_filter_values = NULL;
1553
va_start (valist, model);
1555
str = va_arg (valist, gchar*);
1557
while (str != NULL) {
1558
list = g_slist_prepend (list, g_pattern_spec_new (str));
1560
str = va_arg (valist, gchar*);
1565
model->priv->mime_filter_values = list;
1569
* egg_recent_model_set_filter_groups:
1570
* @model: A EggRecentModel object.
1572
* Sets which groups are allowed in the list.
1577
egg_recent_model_set_filter_groups (EggRecentModel *model,
1581
GSList *list = NULL;
1584
g_return_if_fail (model != NULL);
1586
if (model->priv->group_filter_values != NULL) {
1587
g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL);
1588
g_slist_free (model->priv->group_filter_values);
1589
model->priv->group_filter_values = NULL;
1592
va_start (valist, model);
1594
str = va_arg (valist, gchar*);
1596
while (str != NULL) {
1597
list = g_slist_prepend (list, g_strdup (str));
1599
str = va_arg (valist, gchar*);
1604
model->priv->group_filter_values = list;
1608
* egg_recent_model_set_filter_uri_schemes:
1609
* @model: A EggRecentModel object.
1611
* Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
1616
egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
1619
GSList *list = NULL;
1622
g_return_if_fail (model != NULL);
1624
if (model->priv->scheme_filter_values != NULL) {
1625
g_slist_foreach (model->priv->scheme_filter_values,
1626
(GFunc) g_pattern_spec_free, NULL);
1627
g_slist_free (model->priv->scheme_filter_values);
1628
model->priv->scheme_filter_values = NULL;
1631
va_start (valist, model);
1633
str = va_arg (valist, gchar*);
1635
while (str != NULL) {
1636
list = g_slist_prepend (list, g_pattern_spec_new (str));
1638
str = va_arg (valist, gchar*);
1643
model->priv->scheme_filter_values = list;
1647
* egg_recent_model_set_sort:
1648
* @model: A EggRecentModel object.
1649
* @sort: A EggRecentModelSort type
1651
* Sets the type of sorting to be used.
1656
egg_recent_model_set_sort (EggRecentModel *model,
1657
EggRecentModelSort sort)
1659
g_return_if_fail (model != NULL);
1661
model->priv->sort_type = sort;
1665
* egg_recent_model_changed:
1666
* @model: A EggRecentModel object.
1668
* This function causes a "changed" signal to be emitted.
1673
egg_recent_model_changed (EggRecentModel *model)
1677
if (model->priv->limit > 0) {
1678
list = egg_recent_model_get_list (model);
1679
/* egg_recent_model_monitor_list (model, list); */
1681
g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
1686
EGG_RECENT_ITEM_LIST_UNREF (list);
1690
egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
1692
time_t current_time;
1695
time (¤t_time);
1696
day_seconds = model->priv->expire_days*24*60*60;
1698
while (list != NULL) {
1699
EggRecentItem *item = list->data;
1702
timestamp = egg_recent_item_get_timestamp (item);
1704
if ((timestamp+day_seconds) < current_time) {
1705
gchar *uri = egg_recent_item_get_uri (item);
1706
egg_recent_model_delete (model, uri);
1717
* egg_recent_model_remove_expired:
1718
* @model: A EggRecentModel object.
1720
* Goes through the entire list, and removes any items that are older than
1721
* the user-specified expiration period.
1726
egg_recent_model_remove_expired (EggRecentModel *model)
1731
g_return_if_fail (model != NULL);
1733
file = egg_recent_model_open_file (model);
1734
g_return_if_fail (file != NULL);
1736
if (egg_recent_model_lock_file (file)) {
1737
list = egg_recent_model_read (model, file);
1740
g_warning ("Failed to lock: %s", strerror (errno));
1744
if (!egg_recent_model_unlock_file (file))
1745
g_warning ("Failed to unlock: %s", strerror (errno));
1748
egg_recent_model_remove_expired_list (model, list);
1749
EGG_RECENT_ITEM_LIST_UNREF (list);
1756
* egg_recent_model_get_type:
1758
* This returns a GType representing a EggRecentModel object.
1763
egg_recent_model_get_type (void)
1765
static GType egg_recent_model_type = 0;
1767
if(!egg_recent_model_type) {
1768
static const GTypeInfo egg_recent_model_info = {
1769
sizeof (EggRecentModelClass),
1770
NULL, /* base init */
1771
NULL, /* base finalize */
1772
(GClassInitFunc)egg_recent_model_class_init, /* class init */
1773
NULL, /* class finalize */
1774
NULL, /* class data */
1775
sizeof (EggRecentModel),
1777
(GInstanceInitFunc) egg_recent_model_init
1780
egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
1782
&egg_recent_model_info, 0);
1785
return egg_recent_model_type;