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