~ubuntu-branches/ubuntu/maverick/conglomerate/maverick

« back to all changes in this revision

Viewing changes to src/recent-files/egg-recent-model.c

  • Committer: Bazaar Package Importer
  • Author(s): Daniel T Chen
  • Date: 2005-11-08 05:07:06 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051108050706-bcg60nwqf1z3w0d6
Tags: 0.9.1-1ubuntu1
* Resynchronise with Debian (Closes: #4397).
  - Thanks, Jordan Mantha.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 
2
/*
 
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.
 
7
 *
 
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.
 
12
 *
 
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.
 
16
 *
 
17
 * Authors:
 
18
 *   James Willcox <jwillcox@cs.indiana.edu>
 
19
 */
 
20
 
 
21
#ifdef HAVE_CONFIG_H
 
22
#include <config.h>
 
23
#endif
 
24
 
 
25
#include <stdio.h>
 
26
#include <string.h>
 
27
#include <errno.h>
 
28
#include <stdlib.h>
 
29
#include <unistd.h>
 
30
#include <fcntl.h>
 
31
#include <sys/time.h>
 
32
#include <time.h>
 
33
#include <gtk/gtk.h>
 
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"
 
39
 
 
40
#define EGG_RECENT_MODEL_FILE_PATH "/.recently-used"
 
41
#define EGG_RECENT_MODEL_BUFFER_SIZE 8192
 
42
 
 
43
#define EGG_RECENT_MODEL_MAX_ITEMS 500
 
44
#define EGG_RECENT_MODEL_DEFAULT_LIMIT 10
 
45
#define EGG_RECENT_MODEL_TIMEOUT_LENGTH 200
 
46
 
 
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"
 
50
 
 
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 */
 
55
 
 
56
        EggRecentModelSort sort_type; /* type of sorting to be done */
 
57
 
 
58
        int limit;                      /* soft limit for length of the list */
 
59
        int expire_days;                /* number of days to hold an item */
 
60
 
 
61
        char *path;                     /* path to the file we store stuff in */
 
62
 
 
63
        GHashTable *monitors;
 
64
 
 
65
        GnomeVFSMonitorHandle *monitor;
 
66
 
 
67
        GConfClient *client;
 
68
        gboolean use_default_limit;
 
69
 
 
70
        guint limit_change_notify_id;
 
71
        guint expiration_change_notify_id;
 
72
 
 
73
        guint changed_timeout;
 
74
};
 
75
 
 
76
/* signals */
 
77
enum {
 
78
        CHANGED,
 
79
        LAST_SIGNAL
 
80
};
 
81
 
 
82
static GType model_signals[LAST_SIGNAL] = { 0 };
 
83
 
 
84
/* properties */
 
85
enum {
 
86
        PROP_BOGUS,
 
87
        PROP_MIME_FILTERS,
 
88
        PROP_GROUP_FILTERS,
 
89
        PROP_SCHEME_FILTERS,
 
90
        PROP_SORT_TYPE,
 
91
        PROP_LIMIT
 
92
};
 
93
 
 
94
typedef struct {
 
95
        GSList *states;
 
96
        GList *items;
 
97
        EggRecentItem *current_item;
 
98
}ParseInfo;
 
99
 
 
100
typedef enum {
 
101
        STATE_START,
 
102
        STATE_RECENT_FILES,
 
103
        STATE_RECENT_ITEM,
 
104
        STATE_URI,
 
105
        STATE_MIME_TYPE,
 
106
        STATE_TIMESTAMP,
 
107
        STATE_PRIVATE,
 
108
        STATE_GROUPS,
 
109
        STATE_GROUP
 
110
} ParseState;
 
111
 
 
112
typedef struct _ChangedData {
 
113
        EggRecentModel *model;
 
114
        GList *list;
 
115
}ChangedData;
 
116
 
 
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"
 
125
 
 
126
static void start_element_handler (GMarkupParseContext *context,
 
127
                              const gchar *element_name,
 
128
                              const gchar **attribute_names,
 
129
                              const gchar **attribute_values,
 
130
                              gpointer user_data,
 
131
                              GError **error);
 
132
 
 
133
static void end_element_handler (GMarkupParseContext *context,
 
134
                            const gchar *element_name,
 
135
                            gpointer user_data,
 
136
                            GError **error);
 
137
 
 
138
static void text_handler (GMarkupParseContext *context,
 
139
                     const gchar *text,
 
140
                     gsize text_len,
 
141
                     gpointer user_data,
 
142
                     GError **error);
 
143
 
 
144
static void error_handler (GMarkupParseContext *context,
 
145
                      GError *error,
 
146
                      gpointer user_data);
 
147
 
 
148
static GMarkupParser parser = {start_element_handler, end_element_handler,
 
149
                        text_handler,
 
150
                        NULL,
 
151
                        error_handler};
 
152
 
 
153
static gboolean
 
154
egg_recent_model_string_match (const GSList *list, const gchar *str)
 
155
{
 
156
        const GSList *tmp;
 
157
 
 
158
        if (list == NULL || str == NULL)
 
159
                return TRUE;
 
160
 
 
161
        tmp = list;
 
162
        
 
163
        while (tmp) {
 
164
                if (g_pattern_match_string (tmp->data, str))
 
165
                        return TRUE;
 
166
                
 
167
                tmp = tmp->next;
 
168
        }
 
169
 
 
170
        return FALSE;
 
171
}
 
172
 
 
173
static gboolean
 
174
egg_recent_model_write_raw (EggRecentModel *model, FILE *file,
 
175
                              const gchar *content)
 
176
{
 
177
        int len;
 
178
        int fd;
 
179
        struct stat sbuf;
 
180
 
 
181
        rewind (file);
 
182
 
 
183
        len = strlen (content);
 
184
        fd = fileno (file);
 
185
 
 
186
        if (fstat (fd, &sbuf) < 0)
 
187
                g_warning ("Couldn't stat XML document.");
 
188
 
 
189
        if ((off_t)len < sbuf.st_size) {
 
190
                ftruncate (fd, len);
 
191
        }
 
192
 
 
193
        if (fputs (content, file) == EOF)
 
194
                return FALSE;
 
195
 
 
196
        fsync (fd);
 
197
        rewind (file);
 
198
 
 
199
        return TRUE;
 
200
}
 
201
 
 
202
static GList *
 
203
egg_recent_model_delete_from_list (GList *list,
 
204
                                       const gchar *uri)
 
205
{
 
206
        GList *tmp;
 
207
 
 
208
        if (!uri)
 
209
                return list;
 
210
 
 
211
        tmp = list;
 
212
 
 
213
        while (tmp) {
 
214
                EggRecentItem *item = tmp->data;
 
215
                GList         *next;
 
216
 
 
217
                next = tmp->next;
 
218
 
 
219
                if (!strcmp (egg_recent_item_peek_uri (item), uri)) {
 
220
                        egg_recent_item_unref (item);
 
221
 
 
222
                        list = g_list_remove_link (list, tmp);
 
223
                        g_list_free_1 (tmp);
 
224
                }
 
225
 
 
226
                tmp = next;
 
227
        }
 
228
 
 
229
        return list;
 
230
}
 
231
 
 
232
static void
 
233
egg_recent_model_add_new_groups (EggRecentItem *item,
 
234
                                 EggRecentItem *upd_item)
 
235
{
 
236
        const GList *tmp;
 
237
 
 
238
        tmp = egg_recent_item_get_groups (upd_item);
 
239
 
 
240
        while (tmp) {
 
241
                char *group = tmp->data;
 
242
 
 
243
                if (!egg_recent_item_in_group (item, group))
 
244
                        egg_recent_item_add_group (item, group);
 
245
 
 
246
                tmp = tmp->next;
 
247
        }
 
248
}
 
249
 
 
250
static gboolean
 
251
egg_recent_model_update_item (GList *items, EggRecentItem *upd_item)
 
252
{
 
253
        GList      *tmp;
 
254
        const char *uri;
 
255
 
 
256
        uri = egg_recent_item_peek_uri (upd_item);
 
257
 
 
258
        tmp = items;
 
259
 
 
260
        while (tmp) {
 
261
                EggRecentItem *item = tmp->data;
 
262
 
 
263
                if (gnome_vfs_uris_match (egg_recent_item_peek_uri (item), uri)) {
 
264
                        egg_recent_item_set_timestamp (item, (time_t) -1);
 
265
 
 
266
                        egg_recent_model_add_new_groups (item, upd_item);
 
267
 
 
268
                        return TRUE;
 
269
                }
 
270
 
 
271
                tmp = tmp->next;
 
272
        }
 
273
 
 
274
        return FALSE;
 
275
}
 
276
 
 
277
static gchar *
 
278
egg_recent_model_read_raw (EggRecentModel *model, FILE *file)
 
279
{
 
280
        GString *string;
 
281
        char buf[EGG_RECENT_MODEL_BUFFER_SIZE];
 
282
 
 
283
        rewind (file);
 
284
 
 
285
        string = g_string_new (NULL);
 
286
        while (fgets (buf, EGG_RECENT_MODEL_BUFFER_SIZE, file)) {
 
287
                string = g_string_append (string, buf);
 
288
        }
 
289
 
 
290
        rewind (file);
 
291
 
 
292
        return g_string_free (string, FALSE);
 
293
}
 
294
 
 
295
 
 
296
 
 
297
static void
 
298
parse_info_init (ParseInfo *info)
 
299
{
 
300
        info->states = g_slist_prepend (NULL, STATE_START);
 
301
        info->items = NULL;
 
302
}
 
303
 
 
304
static void
 
305
parse_info_free (ParseInfo *info)
 
306
{
 
307
        g_slist_free (info->states);
 
308
}
 
309
 
 
310
static void
 
311
push_state (ParseInfo  *info,
 
312
            ParseState  state)
 
313
{
 
314
  info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
 
315
}
 
316
 
 
317
static void
 
318
pop_state (ParseInfo *info)
 
319
{
 
320
  g_return_if_fail (info->states != NULL);
 
321
 
 
322
  info->states = g_slist_remove (info->states, info->states->data);
 
323
}
 
324
 
 
325
static ParseState
 
326
peek_state (ParseInfo *info)
 
327
{
 
328
  g_return_val_if_fail (info->states != NULL, STATE_START);
 
329
 
 
330
  return GPOINTER_TO_INT (info->states->data);
 
331
}
 
332
 
 
333
#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
 
334
 
 
335
static void
 
336
start_element_handler (GMarkupParseContext *context,
 
337
                              const gchar *element_name,
 
338
                              const gchar **attribute_names,
 
339
                              const gchar **attribute_values,
 
340
                              gpointer user_data,
 
341
                              GError **error)
 
342
{
 
343
        ParseInfo *info = (ParseInfo *)user_data;
 
344
 
 
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);
 
363
}
 
364
 
 
365
static gint
 
366
list_compare_func_mru (gpointer a, gpointer b)
 
367
{
 
368
        EggRecentItem *item_a = (EggRecentItem *)a;
 
369
        EggRecentItem *item_b = (EggRecentItem *)b;
 
370
 
 
371
        return item_a->timestamp < item_b->timestamp;
 
372
}
 
373
 
 
374
static gint
 
375
list_compare_func_lru (gpointer a, gpointer b)
 
376
{
 
377
        EggRecentItem *item_a = (EggRecentItem *)a;
 
378
        EggRecentItem *item_b = (EggRecentItem *)b;
 
379
 
 
380
        return item_a->timestamp > item_b->timestamp;
 
381
}
 
382
 
 
383
 
 
384
 
 
385
static void
 
386
end_element_handler (GMarkupParseContext *context,
 
387
                            const gchar *element_name,
 
388
                            gpointer user_data,
 
389
                            GError **error)
 
390
{
 
391
        ParseInfo *info = (ParseInfo *)user_data;
 
392
 
 
393
        switch (peek_state (info)) {
 
394
                case STATE_RECENT_ITEM:
 
395
                        info->items = g_list_append (info->items,
 
396
                                                    info->current_item);
 
397
                        if (info->current_item->uri == NULL ||
 
398
                            strlen (info->current_item->uri) == 0)
 
399
                                g_warning ("URI NOT LOADED");
 
400
                break;
 
401
                default:
 
402
                break;
 
403
        }
 
404
 
 
405
        pop_state (info);
 
406
}
 
407
 
 
408
static void
 
409
text_handler (GMarkupParseContext *context,
 
410
                     const gchar *text,
 
411
                     gsize text_len,
 
412
                     gpointer user_data,
 
413
                     GError **error)
 
414
{
 
415
        ParseInfo *info = (ParseInfo *)user_data;
 
416
 
 
417
        switch (peek_state (info)) {
 
418
                case STATE_START:
 
419
                case STATE_RECENT_FILES:
 
420
                case STATE_RECENT_ITEM:
 
421
                case STATE_PRIVATE:
 
422
                case STATE_GROUPS:
 
423
                break;
 
424
                case STATE_URI:
 
425
                        egg_recent_item_set_uri (info->current_item, text);
 
426
                break;
 
427
                case STATE_MIME_TYPE:
 
428
                        egg_recent_item_set_mime_type (info->current_item,
 
429
                                                         text);
 
430
                break;
 
431
                case STATE_TIMESTAMP:
 
432
                        egg_recent_item_set_timestamp (info->current_item,
 
433
                                                         (time_t)atoi (text));
 
434
                break;
 
435
                case STATE_GROUP:
 
436
                        egg_recent_item_add_group (info->current_item,
 
437
                                                     text);
 
438
                break;
 
439
        }
 
440
                        
 
441
}
 
442
 
 
443
static void
 
444
error_handler (GMarkupParseContext *context,
 
445
                      GError *error,
 
446
                      gpointer user_data)
 
447
{
 
448
        g_warning ("Error in parse: %s", error->message);
 
449
}
 
450
 
 
451
static void
 
452
egg_recent_model_enforce_limit (GList *list, int limit)
 
453
{
 
454
        int len;
 
455
        GList *end;
 
456
 
 
457
        /* limit < 0 means unlimited */
 
458
        if (limit <= 0)
 
459
                return;
 
460
 
 
461
        len = g_list_length (list);
 
462
 
 
463
        if (len > limit) {
 
464
                GList *next;
 
465
 
 
466
                end = g_list_nth (list, limit-1);
 
467
                next = end->next;
 
468
 
 
469
                end->next = NULL;
 
470
 
 
471
                EGG_RECENT_ITEM_LIST_UNREF (next);
 
472
        }
 
473
}
 
474
 
 
475
static GList *
 
476
egg_recent_model_sort (EggRecentModel *model, GList *list)
 
477
{
 
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);   
 
482
                break;
 
483
                case EGG_RECENT_MODEL_SORT_LRU:
 
484
                        list = g_list_sort (list,
 
485
                                        (GCompareFunc)list_compare_func_lru);
 
486
                break;
 
487
                case EGG_RECENT_MODEL_SORT_NONE:
 
488
                break;
 
489
        }
 
490
 
 
491
        return list;
 
492
}
 
493
 
 
494
static gboolean
 
495
egg_recent_model_group_match (EggRecentItem *item, GSList *groups)
 
496
{
 
497
        GSList *tmp;
 
498
 
 
499
        tmp = groups;
 
500
 
 
501
        while (tmp != NULL) {
 
502
                const gchar * group = (const gchar *)tmp->data;
 
503
 
 
504
                if (egg_recent_item_in_group (item, group))
 
505
                        return TRUE;
 
506
 
 
507
                tmp = tmp->next;
 
508
        }
 
509
 
 
510
        return FALSE;
 
511
}
 
512
 
 
513
static GList *
 
514
egg_recent_model_filter (EggRecentModel *model,
 
515
                                GList *list)
 
516
{
 
517
        EggRecentItem *item;
 
518
        GList *newlist = NULL;
 
519
        gchar *mime_type;
 
520
        gchar *uri;
 
521
 
 
522
        g_return_val_if_fail (list != NULL, NULL);
 
523
 
 
524
        while (list) {
 
525
                gboolean pass_mime_test = FALSE;
 
526
                gboolean pass_group_test = FALSE;
 
527
                gboolean pass_scheme_test = FALSE;
 
528
                item = (EggRecentItem *)list->data;
 
529
                list = list->next;
 
530
 
 
531
                uri = egg_recent_item_get_uri (item);
 
532
 
 
533
                /* filter by mime type */
 
534
                if (model->priv->mime_filter_values != NULL) {
 
535
                        mime_type = egg_recent_item_get_mime_type (item);
 
536
 
 
537
                        if (egg_recent_model_string_match
 
538
                                        (model->priv->mime_filter_values,
 
539
                                         mime_type))
 
540
                                pass_mime_test = TRUE;
 
541
 
 
542
                        g_free (mime_type);
 
543
                } else
 
544
                        pass_mime_test = TRUE;
 
545
 
 
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;
 
553
                } else
 
554
                        pass_group_test = TRUE;
 
555
 
 
556
                /* filter by URI scheme */
 
557
                if (pass_mime_test && pass_group_test &&
 
558
                    model->priv->scheme_filter_values != NULL) {
 
559
                        gchar *scheme;
 
560
                        
 
561
                        scheme = gnome_vfs_get_uri_scheme (uri);
 
562
 
 
563
                        if (egg_recent_model_string_match
 
564
                                (model->priv->scheme_filter_values, scheme))
 
565
                                pass_scheme_test = TRUE;
 
566
 
 
567
                        g_free (scheme);
 
568
                } else
 
569
                        pass_scheme_test = TRUE;
 
570
 
 
571
                if (pass_mime_test && pass_group_test && pass_scheme_test)
 
572
                        newlist = g_list_prepend (newlist, item);
 
573
 
 
574
                g_free (uri);
 
575
        }
 
576
 
 
577
        if (newlist) {
 
578
                newlist = g_list_reverse (newlist);
 
579
                g_list_free (list);
 
580
        }
 
581
 
 
582
        
 
583
        return newlist;
 
584
}
 
585
 
 
586
 
 
587
 
 
588
#if 0
 
589
static void
 
590
egg_recent_model_monitor_list_cb (GnomeVFSMonitorHandle *handle,
 
591
                               const gchar *monitor_uri,
 
592
                               const gchar *info_uri,
 
593
                               GnomeVFSMonitorEventType event_type,
 
594
                               gpointer user_data)
 
595
{
 
596
        EggRecentModel *model;
 
597
 
 
598
        model = EGG_RECENT_MODEL (user_data);
 
599
 
 
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);
 
603
        }
 
604
}
 
605
 
 
606
 
 
607
 
 
608
static void
 
609
egg_recent_model_monitor_list (EggRecentModel *model, GList *list)
 
610
{
 
611
        GList *tmp;
 
612
 
 
613
        tmp = list;
 
614
        while (tmp) {
 
615
                EggRecentItem *item = (EggRecentItem *)tmp->data;
 
616
                GnomeVFSMonitorHandle *handle;
 
617
                GnomeVFSResult res;
 
618
                gchar *uri;
 
619
 
 
620
                tmp = tmp->next;
 
621
                
 
622
                uri = egg_recent_item_get_uri (item);
 
623
                if (g_hash_table_lookup (model->priv->monitors, uri)) {
 
624
                        /* already monitoring this one */
 
625
                        g_free (uri);
 
626
                        continue;
 
627
                }
 
628
 
 
629
                res = gnome_vfs_monitor_add (&handle, uri,
 
630
                                             GNOME_VFS_MONITOR_FILE,
 
631
                                             egg_recent_model_monitor_list_cb,
 
632
                                             model);
 
633
                
 
634
                if (res == GNOME_VFS_OK)
 
635
                        g_hash_table_insert (model->priv->monitors, uri, handle);
 
636
                else
 
637
                        g_free (uri);
 
638
        }
 
639
}
 
640
#endif
 
641
 
 
642
 
 
643
static gboolean
 
644
egg_recent_model_changed_timeout (EggRecentModel *model)
 
645
{
 
646
        model->priv->changed_timeout = 0;
 
647
 
 
648
        egg_recent_model_changed (model);
 
649
 
 
650
        return FALSE;
 
651
}
 
652
 
 
653
static void
 
654
egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
 
655
                               const gchar *monitor_uri,
 
656
                               const gchar *info_uri,
 
657
                               GnomeVFSMonitorEventType event_type,
 
658
                               gpointer user_data)
 
659
{
 
660
        EggRecentModel *model;
 
661
 
 
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);
 
665
 
 
666
        if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
 
667
                if (model->priv->changed_timeout > 0) {
 
668
                        g_source_remove (model->priv->changed_timeout);
 
669
                }
 
670
 
 
671
                model->priv->changed_timeout = g_timeout_add (
 
672
                        EGG_RECENT_MODEL_TIMEOUT_LENGTH,
 
673
                        (GSourceFunc)egg_recent_model_changed_timeout,
 
674
                        model);
 
675
        }
 
676
}
 
677
 
 
678
static void
 
679
egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
 
680
{
 
681
        if (should_monitor && model->priv->monitor == NULL) {
 
682
                char *uri;
 
683
 
 
684
                uri = gnome_vfs_get_uri_from_local_path (model->priv->path);
 
685
 
 
686
                gnome_vfs_monitor_add (&model->priv->monitor,
 
687
                                       uri,
 
688
                                       GNOME_VFS_MONITOR_FILE,
 
689
                                       egg_recent_model_monitor_cb,
 
690
                                       model);
 
691
 
 
692
                g_free (uri);
 
693
 
 
694
                /* if the above fails, don't worry about it.
 
695
                 * local notifications will still happen
 
696
                 */
 
697
 
 
698
        } else if (!should_monitor && model->priv->monitor != NULL) {
 
699
                gnome_vfs_monitor_cancel (model->priv->monitor);
 
700
                model->priv->monitor = NULL;
 
701
        }
 
702
}
 
703
 
 
704
static void
 
705
egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
 
706
{
 
707
        model->priv->limit = limit;
 
708
 
 
709
        if (limit <= 0)
 
710
                egg_recent_model_monitor (model, FALSE);
 
711
        else {
 
712
                egg_recent_model_monitor (model, TRUE);
 
713
                egg_recent_model_changed (model);
 
714
        }
 
715
}
 
716
 
 
717
static GList *
 
718
egg_recent_model_read (EggRecentModel *model, FILE *file)
 
719
{
 
720
        GList *list=NULL;
 
721
        gchar *content;
 
722
        GMarkupParseContext *ctx;
 
723
        ParseInfo info;
 
724
        GError *error;
 
725
 
 
726
        content = egg_recent_model_read_raw (model, file);
 
727
 
 
728
        if (strlen (content) <= 0) {
 
729
                g_free (content);
 
730
                return NULL;
 
731
        }
 
732
 
 
733
        parse_info_init (&info);
 
734
        
 
735
        ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
 
736
        
 
737
        error = NULL;
 
738
        if (!g_markup_parse_context_parse (ctx, content, strlen (content),
 
739
                                           &error)) {
 
740
                g_warning (error->message);
 
741
                g_error_free (error);
 
742
                error = NULL;
 
743
                goto out;
 
744
        }
 
745
 
 
746
        error = NULL;
 
747
        if (!g_markup_parse_context_end_parse (ctx, &error))
 
748
                goto out;
 
749
        
 
750
        g_markup_parse_context_free (ctx);
 
751
out:
 
752
        list = info.items;
 
753
 
 
754
        parse_info_free (&info);
 
755
 
 
756
        g_free (content);
 
757
 
 
758
        /*
 
759
        g_print ("Total items: %d\n", g_list_length (list));
 
760
        */
 
761
 
 
762
        return list;
 
763
}
 
764
 
 
765
 
 
766
static gboolean
 
767
egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
 
768
{
 
769
        GString *string;
 
770
        gchar *data;
 
771
        EggRecentItem *item;
 
772
        const GList *groups;
 
773
        int i;
 
774
        int ret;
 
775
        
 
776
        string = g_string_new ("<?xml version=\"1.0\"?>\n");
 
777
        string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
 
778
 
 
779
        i=0;
 
780
        while (list) {
 
781
                gchar *uri;
 
782
                gchar *mime_type;
 
783
                gchar *escaped_uri;
 
784
                time_t timestamp;
 
785
                item = (EggRecentItem *)list->data;
 
786
 
 
787
 
 
788
                uri = egg_recent_item_get_uri_utf8 (item);
 
789
                escaped_uri = g_markup_escape_text (uri,
 
790
                                                    strlen (uri));
 
791
                g_free (uri);
 
792
 
 
793
                mime_type = egg_recent_item_get_mime_type (item);
 
794
                timestamp = egg_recent_item_get_timestamp (item);
 
795
                
 
796
                string = g_string_append (string, "  <" TAG_RECENT_ITEM ">\n");
 
797
 
 
798
                g_string_append_printf (string,
 
799
                                "    <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
 
800
 
 
801
                if (mime_type)
 
802
                        g_string_append_printf (string,
 
803
                                "    <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
 
804
                else
 
805
                        g_string_append_printf (string,
 
806
                                "    <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
 
807
 
 
808
                
 
809
                g_string_append_printf (string,
 
810
                                "    <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
 
811
 
 
812
                if (egg_recent_item_get_private (item))
 
813
                        string = g_string_append (string,
 
814
                                        "    <" TAG_PRIVATE "/>\n");
 
815
 
 
816
                /* write the groups */
 
817
                string = g_string_append (string,
 
818
                                "    <" TAG_GROUPS ">\n");
 
819
                groups = egg_recent_item_get_groups (item);
 
820
 
 
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);
 
824
                
 
825
                while (groups) {
 
826
                        const gchar *group = (const gchar *)groups->data;
 
827
                        gchar *escaped_group;
 
828
 
 
829
                        escaped_group = g_markup_escape_text (group, strlen(group));
 
830
 
 
831
                        g_string_append_printf (string,
 
832
                                        "      <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
 
833
                                        escaped_group);
 
834
 
 
835
                        g_free (escaped_group);
 
836
 
 
837
                        groups = groups->next;
 
838
                }
 
839
                
 
840
                string = g_string_append (string, "    </" TAG_GROUPS ">\n");
 
841
 
 
842
                string = g_string_append (string,
 
843
                                "  </" TAG_RECENT_ITEM ">\n");
 
844
 
 
845
                g_free (mime_type);
 
846
                g_free (escaped_uri);
 
847
 
 
848
                list = list->next;
 
849
                i++;
 
850
        }
 
851
 
 
852
        string = g_string_append (string, "</" TAG_RECENT_FILES ">");
 
853
 
 
854
        data = g_string_free (string, FALSE);
 
855
 
 
856
        ret = egg_recent_model_write_raw (model, file, data);
 
857
 
 
858
        g_free (data);
 
859
 
 
860
        return ret;
 
861
}
 
862
 
 
863
static FILE *
 
864
egg_recent_model_open_file (EggRecentModel *model)
 
865
{
 
866
        FILE *file;
 
867
        mode_t prev_umask;
 
868
        
 
869
        file = fopen (model->priv->path, "r+");
 
870
        if (file == NULL) {
 
871
                /* be paranoid */
 
872
                prev_umask = umask (077);
 
873
 
 
874
                file = fopen (model->priv->path, "w+");
 
875
 
 
876
                umask (prev_umask);
 
877
 
 
878
                g_return_val_if_fail (file != NULL, NULL);
 
879
        }
 
880
 
 
881
        return file;
 
882
}
 
883
 
 
884
static gboolean
 
885
egg_recent_model_lock_file (FILE *file)
 
886
{
 
887
        int fd;
 
888
        gint    try = 5;
 
889
 
 
890
        rewind (file);
 
891
        fd = fileno (file);
 
892
 
 
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
 
898
         * changes.
 
899
         */
 
900
        
 
901
        while (try > 0)
 
902
        {
 
903
                int rand_interval;
 
904
 
 
905
                if (lockf (fd, F_TLOCK, 0) == 0)
 
906
                        return TRUE;
 
907
 
 
908
                rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
 
909
                         
 
910
                g_usleep (100000 * rand_interval);
 
911
 
 
912
                --try;
 
913
        }
 
914
 
 
915
        return FALSE;
 
916
}
 
917
 
 
918
static gboolean
 
919
egg_recent_model_unlock_file (FILE *file)
 
920
{
 
921
        int fd;
 
922
 
 
923
        rewind (file);
 
924
        fd = fileno (file);
 
925
 
 
926
        return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
 
927
}
 
928
 
 
929
static void
 
930
egg_recent_model_finalize (GObject *object)
 
931
{
 
932
        EggRecentModel *model = EGG_RECENT_MODEL (object);
 
933
 
 
934
        if (model->priv->changed_timeout > 0) {
 
935
                g_source_remove (model->priv->changed_timeout);
 
936
        }
 
937
 
 
938
        egg_recent_model_monitor (model, FALSE);
 
939
 
 
940
 
 
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;
 
945
 
 
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;
 
950
 
 
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;
 
955
 
 
956
 
 
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;
 
961
 
 
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;
 
966
 
 
967
        g_object_unref (model->priv->client);
 
968
        model->priv->client = NULL;
 
969
 
 
970
 
 
971
        g_free (model->priv->path);
 
972
        model->priv->path = NULL;
 
973
        
 
974
        g_hash_table_destroy (model->priv->monitors);
 
975
        model->priv->monitors = NULL;
 
976
 
 
977
 
 
978
        g_free (model->priv);
 
979
}
 
980
 
 
981
static void
 
982
egg_recent_model_set_property (GObject *object,
 
983
                               guint prop_id,
 
984
                               const GValue *value,
 
985
                               GParamSpec *pspec)
 
986
{
 
987
        EggRecentModel *model = EGG_RECENT_MODEL (object);
 
988
 
 
989
        switch (prop_id)
 
990
        {
 
991
                case PROP_MIME_FILTERS:
 
992
                        model->priv->mime_filter_values =
 
993
                                (GSList *)g_value_get_pointer (value);
 
994
                break;
 
995
 
 
996
                case PROP_GROUP_FILTERS:
 
997
                        model->priv->group_filter_values =
 
998
                                (GSList *)g_value_get_pointer (value);
 
999
                break;
 
1000
 
 
1001
                case PROP_SCHEME_FILTERS:
 
1002
                        model->priv->scheme_filter_values =
 
1003
                                (GSList *)g_value_get_pointer (value);
 
1004
                break;
 
1005
 
 
1006
                case PROP_SORT_TYPE:
 
1007
                        model->priv->sort_type = g_value_get_int (value);
 
1008
                break;
 
1009
 
 
1010
                case PROP_LIMIT:
 
1011
                        egg_recent_model_set_limit (model,
 
1012
                                                g_value_get_int (value));
 
1013
                break;
 
1014
 
 
1015
                default:
 
1016
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
1017
                break;
 
1018
        }
 
1019
}
 
1020
 
 
1021
static void
 
1022
egg_recent_model_get_property (GObject *object,
 
1023
                               guint prop_id,
 
1024
                               GValue *value,
 
1025
                               GParamSpec *pspec)
 
1026
{
 
1027
        EggRecentModel *model = EGG_RECENT_MODEL (object);
 
1028
 
 
1029
        switch (prop_id)
 
1030
        {
 
1031
                case PROP_MIME_FILTERS:
 
1032
                        g_value_set_pointer (value, model->priv->mime_filter_values);
 
1033
                break;
 
1034
 
 
1035
                case PROP_GROUP_FILTERS:
 
1036
                        g_value_set_pointer (value, model->priv->group_filter_values);
 
1037
                break;
 
1038
 
 
1039
                case PROP_SCHEME_FILTERS:
 
1040
                        g_value_set_pointer (value, model->priv->scheme_filter_values);
 
1041
                break;
 
1042
 
 
1043
                case PROP_SORT_TYPE:
 
1044
                        g_value_set_int (value, model->priv->sort_type);
 
1045
                break;
 
1046
 
 
1047
                case PROP_LIMIT:
 
1048
                        g_value_set_int (value, model->priv->limit);
 
1049
                break;
 
1050
 
 
1051
                default:
 
1052
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
1053
                break;
 
1054
        }
 
1055
}
 
1056
 
 
1057
static void
 
1058
egg_recent_model_class_init (EggRecentModelClass * klass)
 
1059
{
 
1060
        GObjectClass *object_class;
 
1061
 
 
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;
 
1066
 
 
1067
        model_signals[CHANGED] = g_signal_new ("changed",
 
1068
                        G_OBJECT_CLASS_TYPE (object_class),
 
1069
                        G_SIGNAL_RUN_LAST,
 
1070
                        G_STRUCT_OFFSET (EggRecentModelClass, changed),
 
1071
                        NULL, NULL,
 
1072
                        g_cclosure_marshal_VOID__POINTER,
 
1073
                        G_TYPE_NONE, 1,
 
1074
                        G_TYPE_POINTER);
 
1075
 
 
1076
        
 
1077
        g_object_class_install_property (object_class,
 
1078
                                         PROP_MIME_FILTERS,
 
1079
                                         g_param_spec_pointer ("mime-filters",
 
1080
                                         "Mime Filters",
 
1081
                                         "List of mime types to be allowed.",
 
1082
                                         G_PARAM_READWRITE));
 
1083
        
 
1084
        g_object_class_install_property (object_class,
 
1085
                                         PROP_GROUP_FILTERS,
 
1086
                                         g_param_spec_pointer ("group-filters",
 
1087
                                         "Group Filters",
 
1088
                                         "List of groups to be allowed.",
 
1089
                                         G_PARAM_READWRITE));
 
1090
 
 
1091
        g_object_class_install_property (object_class,
 
1092
                                         PROP_SCHEME_FILTERS,
 
1093
                                         g_param_spec_pointer ("scheme-filters",
 
1094
                                         "Scheme Filters",
 
1095
                                         "List of URI schemes to be allowed.",
 
1096
                                         G_PARAM_READWRITE));
 
1097
 
 
1098
        g_object_class_install_property (object_class,
 
1099
                                         PROP_SORT_TYPE,
 
1100
                                         g_param_spec_int ("sort-type",
 
1101
                                         "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));
 
1106
 
 
1107
        g_object_class_install_property (object_class,
 
1108
                                         PROP_LIMIT,
 
1109
                                         g_param_spec_int ("limit",
 
1110
                                         "Limit",
 
1111
                                         "Max number of items allowed.",
 
1112
                                         -1, EGG_RECENT_MODEL_MAX_ITEMS,
 
1113
                                         EGG_RECENT_MODEL_DEFAULT_LIMIT,
 
1114
                                         G_PARAM_READWRITE));
 
1115
 
 
1116
        klass->changed = NULL;
 
1117
}
 
1118
 
 
1119
 
 
1120
 
 
1121
static void
 
1122
egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
 
1123
                                GConfEntry *entry, gpointer user_data)
 
1124
{
 
1125
        EggRecentModel *model;
 
1126
        GConfValue *value;
 
1127
 
 
1128
        model = EGG_RECENT_MODEL (user_data);
 
1129
 
 
1130
        g_return_if_fail (model != NULL);
 
1131
 
 
1132
        if (model->priv->use_default_limit == FALSE)
 
1133
                return; /* ignore this key */
 
1134
 
 
1135
        /* the key was unset, and the schema has apparently failed */
 
1136
        if (entry == NULL)
 
1137
                return;
 
1138
 
 
1139
        value = gconf_entry_get_value (entry);
 
1140
 
 
1141
        if (value->type != GCONF_VALUE_INT) {
 
1142
                g_warning ("Expected GConfValue of type integer, "
 
1143
                           "got something else");
 
1144
        }
 
1145
 
 
1146
 
 
1147
        egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
 
1148
}
 
1149
 
 
1150
static void
 
1151
egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
 
1152
                                     GConfEntry *entry, gpointer user_data)
 
1153
{
 
1154
 
 
1155
}
 
1156
 
 
1157
static void
 
1158
egg_recent_model_init (EggRecentModel * model)
 
1159
{
 
1160
        if (!gnome_vfs_init ()) {
 
1161
                g_warning ("gnome-vfs initialization failed.");
 
1162
                return;
 
1163
        }
 
1164
        
 
1165
 
 
1166
        model->priv = g_new0 (EggRecentModelPrivate, 1);
 
1167
 
 
1168
        model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
 
1169
                                             g_get_home_dir ());
 
1170
 
 
1171
        model->priv->mime_filter_values   = NULL;
 
1172
        model->priv->group_filter_values  = NULL;
 
1173
        model->priv->scheme_filter_values = NULL;
 
1174
        
 
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);
 
1178
 
 
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,
 
1183
                                                 model, NULL, NULL);
 
1184
 
 
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,
 
1189
                                                 model, NULL, NULL);
 
1190
 
 
1191
        model->priv->expire_days = gconf_client_get_int (
 
1192
                                        model->priv->client,
 
1193
                                        EGG_RECENT_MODEL_EXPIRE_KEY,
 
1194
                                        NULL);
 
1195
                                        
 
1196
#if 0
 
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;
 
1202
#endif
 
1203
        model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
 
1204
        model->priv->use_default_limit = FALSE;
 
1205
 
 
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);
 
1210
 
 
1211
        model->priv->monitor = NULL;
 
1212
        egg_recent_model_monitor (model, TRUE);
 
1213
}
 
1214
 
 
1215
 
 
1216
/**
 
1217
 * egg_recent_model_new:
 
1218
 * @sort:  the type of sorting to use
 
1219
 * @limit:  maximum number of items in the list
 
1220
 *
 
1221
 * This creates a new EggRecentModel object.
 
1222
 *
 
1223
 * Returns: a EggRecentModel object
 
1224
 */
 
1225
EggRecentModel *
 
1226
egg_recent_model_new (EggRecentModelSort sort)
 
1227
{
 
1228
        EggRecentModel *model;
 
1229
 
 
1230
        model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
 
1231
                                  "sort-type", sort, NULL));
 
1232
 
 
1233
        g_return_val_if_fail (model, NULL);
 
1234
 
 
1235
        return model;
 
1236
}
 
1237
 
 
1238
/**
 
1239
 * egg_recent_model_add_full:
 
1240
 * @model:  A EggRecentModel object.
 
1241
 * @item:  A EggRecentItem
 
1242
 *
 
1243
 * This function adds an item to the list of recently used URIs.
 
1244
 *
 
1245
 * Returns: gboolean
 
1246
 */
 
1247
gboolean
 
1248
egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
 
1249
{
 
1250
        FILE *file;
 
1251
        GList *list = NULL;
 
1252
        gboolean ret = FALSE;
 
1253
        gboolean updated = FALSE;
 
1254
        char *uri;
 
1255
        time_t t;
 
1256
        
 
1257
        g_return_val_if_fail (model != NULL, FALSE);
 
1258
        g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
 
1259
 
 
1260
        uri = egg_recent_item_get_uri (item);
 
1261
        if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
 
1262
                g_free (uri);
 
1263
                return FALSE;
 
1264
        } else {
 
1265
                g_free (uri);
 
1266
        }
 
1267
 
 
1268
        file = egg_recent_model_open_file (model);
 
1269
        g_return_val_if_fail (file != NULL, FALSE);
 
1270
 
 
1271
        time (&t);
 
1272
        egg_recent_item_set_timestamp (item, t);
 
1273
 
 
1274
        if (egg_recent_model_lock_file (file)) {
 
1275
 
 
1276
                /* read existing stuff */
 
1277
                list = egg_recent_model_read (model, file);
 
1278
 
 
1279
                /* if it's already there, we just update it */
 
1280
                updated = egg_recent_model_update_item (list, item);
 
1281
 
 
1282
                if (!updated) {
 
1283
                        list = g_list_prepend (list, item);
 
1284
 
 
1285
                        egg_recent_model_enforce_limit (list,
 
1286
                                                EGG_RECENT_MODEL_MAX_ITEMS);
 
1287
                }
 
1288
 
 
1289
                /* write new stuff */
 
1290
                if (!egg_recent_model_write (model, file, list))
 
1291
                        g_warning ("Write failed: %s", strerror (errno));
 
1292
 
 
1293
                if (!updated)
 
1294
                        list = g_list_remove (list, item);
 
1295
 
 
1296
                EGG_RECENT_ITEM_LIST_UNREF (list);
 
1297
                ret = TRUE;
 
1298
        } else {
 
1299
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1300
                fclose (file);
 
1301
                return FALSE;
 
1302
        }
 
1303
 
 
1304
        if (!egg_recent_model_unlock_file (file))
 
1305
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1306
 
 
1307
        fclose (file);
 
1308
 
 
1309
        if (model->priv->monitor == NULL) {
 
1310
                /* since monitoring isn't working, at least give a
 
1311
                 * local notification
 
1312
                 */
 
1313
                egg_recent_model_changed (model);
 
1314
        }
 
1315
 
 
1316
        return ret;
 
1317
}
 
1318
 
 
1319
/**
 
1320
 * egg_recent_model_add:
 
1321
 * @model:  A EggRecentModel object.
 
1322
 * @uri:  A string URI
 
1323
 *
 
1324
 * This function adds an item to the list of recently used URIs.
 
1325
 *
 
1326
 * Returns: gboolean
 
1327
 */
 
1328
gboolean
 
1329
egg_recent_model_add (EggRecentModel *model, const gchar *uri)
 
1330
{
 
1331
        EggRecentItem *item;
 
1332
        gboolean ret = FALSE;
 
1333
 
 
1334
        g_return_val_if_fail (model != NULL, FALSE);
 
1335
        g_return_val_if_fail (uri != NULL, FALSE);
 
1336
 
 
1337
        item = egg_recent_item_new_from_uri (uri);
 
1338
 
 
1339
        g_return_val_if_fail (item != NULL, FALSE);
 
1340
 
 
1341
        ret = egg_recent_model_add_full (model, item);
 
1342
 
 
1343
        egg_recent_item_unref (item);
 
1344
 
 
1345
        return ret;
 
1346
}
 
1347
 
 
1348
 
 
1349
 
 
1350
/**
 
1351
 * egg_recent_model_delete:
 
1352
 * @model:  A EggRecentModel object.
 
1353
 * @uri: The URI you want to delete.
 
1354
 *
 
1355
 * This function deletes a URI from the file of recently used URIs.
 
1356
 *
 
1357
 * Returns: gboolean
 
1358
 */
 
1359
gboolean
 
1360
egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
 
1361
{
 
1362
        FILE *file;
 
1363
        GList *list;
 
1364
        unsigned int length;
 
1365
        gboolean ret = FALSE;
 
1366
 
 
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);
 
1370
 
 
1371
        file = egg_recent_model_open_file (model);
 
1372
        g_return_val_if_fail (file != NULL, FALSE);
 
1373
 
 
1374
        if (egg_recent_model_lock_file (file)) {
 
1375
                list = egg_recent_model_read (model, file);
 
1376
 
 
1377
                if (list == NULL)
 
1378
                        goto out;
 
1379
 
 
1380
                length = g_list_length (list);
 
1381
 
 
1382
                list = egg_recent_model_delete_from_list (list, uri);
 
1383
                
 
1384
                if (length == g_list_length (list)) {
 
1385
                        /* nothing was deleted */
 
1386
                        EGG_RECENT_ITEM_LIST_UNREF (list);
 
1387
                } else {
 
1388
                        egg_recent_model_write (model, file, list);
 
1389
                        EGG_RECENT_ITEM_LIST_UNREF (list);
 
1390
                        ret = TRUE;
 
1391
 
 
1392
                }
 
1393
        } else {
 
1394
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1395
                return FALSE;
 
1396
        }
 
1397
 
 
1398
out:
 
1399
                
 
1400
        if (!egg_recent_model_unlock_file (file))
 
1401
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1402
 
 
1403
        fclose (file);
 
1404
 
 
1405
        g_hash_table_remove (model->priv->monitors, uri);
 
1406
 
 
1407
        if (model->priv->monitor == NULL && ret) {
 
1408
                /* since monitoring isn't working, at least give a
 
1409
                 * local notification
 
1410
                 */
 
1411
                egg_recent_model_changed (model);
 
1412
        }
 
1413
 
 
1414
        return ret;
 
1415
}
 
1416
 
 
1417
 
 
1418
/**
 
1419
 * egg_recent_model_get_list:
 
1420
 * @model:  A EggRecentModel object.
 
1421
 *
 
1422
 * This function gets the current contents of the file
 
1423
 *
 
1424
 * Returns: a GList
 
1425
 */
 
1426
GList *
 
1427
egg_recent_model_get_list (EggRecentModel *model)
 
1428
{
 
1429
        FILE *file;
 
1430
        GList *list=NULL;
 
1431
 
 
1432
        file = egg_recent_model_open_file (model);
 
1433
        g_return_val_if_fail (file != NULL, NULL);
 
1434
        
 
1435
        if (egg_recent_model_lock_file (file)) {
 
1436
                list = egg_recent_model_read (model, file);
 
1437
                
 
1438
        } else {
 
1439
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1440
                fclose (file);
 
1441
                return NULL;
 
1442
        }
 
1443
 
 
1444
        if (!egg_recent_model_unlock_file (file))
 
1445
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1446
 
 
1447
        if (list != NULL) {
 
1448
                list = egg_recent_model_filter (model, list);
 
1449
                list = egg_recent_model_sort (model, list);
 
1450
 
 
1451
                egg_recent_model_enforce_limit (list, model->priv->limit);
 
1452
        }
 
1453
 
 
1454
        fclose (file);
 
1455
 
 
1456
        return list;
 
1457
}
 
1458
 
 
1459
 
 
1460
 
 
1461
/**
 
1462
 * egg_recent_model_set_limit:
 
1463
 * @model:  A EggRecentModel object.
 
1464
 * @limit:  The maximum length of the list
 
1465
 *
 
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
 
1468
 * on disk.
 
1469
 *
 
1470
 * Returns:  void
 
1471
 */
 
1472
void
 
1473
egg_recent_model_set_limit (EggRecentModel *model, int limit)
 
1474
{
 
1475
        model->priv->use_default_limit = FALSE;
 
1476
 
 
1477
        egg_recent_model_set_limit_internal (model, limit);
 
1478
}
 
1479
 
 
1480
/**
 
1481
 * egg_recent_model_get_limit:
 
1482
 * @model:  A EggRecentModel object.
 
1483
 *
 
1484
 * This function gets the maximum length of the list. 
 
1485
 *
 
1486
 * Returns:  int
 
1487
 */
 
1488
int
 
1489
egg_recent_model_get_limit (EggRecentModel *model)
 
1490
{
 
1491
        return model->priv->limit;
 
1492
}
 
1493
 
 
1494
 
 
1495
/**
 
1496
 * egg_recent_model_clear:
 
1497
 * @model:  A EggRecentModel object.
 
1498
 *
 
1499
 * This function clears the contents of the file
 
1500
 *
 
1501
 * Returns: void
 
1502
 */
 
1503
void
 
1504
egg_recent_model_clear (EggRecentModel *model)
 
1505
{
 
1506
        FILE *file;
 
1507
        int fd;
 
1508
 
 
1509
        file = egg_recent_model_open_file (model);
 
1510
        g_return_if_fail (file != NULL);
 
1511
 
 
1512
        fd = fileno (file);
 
1513
 
 
1514
        if (egg_recent_model_lock_file (file)) {
 
1515
                ftruncate (fd, 0);
 
1516
        } else {
 
1517
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1518
                return;
 
1519
        }
 
1520
 
 
1521
        if (!egg_recent_model_unlock_file (file))
 
1522
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1523
 
 
1524
        fclose (file);
 
1525
}
 
1526
 
 
1527
 
 
1528
/**
 
1529
 * egg_recent_model_set_filter_mime_types:
 
1530
 * @model:  A EggRecentModel object.
 
1531
 *
 
1532
 * Sets which mime types are allowed in the list.
 
1533
 *
 
1534
 * Returns: void
 
1535
 */
 
1536
void
 
1537
egg_recent_model_set_filter_mime_types (EggRecentModel *model,
 
1538
                               ...)
 
1539
{
 
1540
        va_list valist;
 
1541
        GSList *list = NULL;
 
1542
        gchar *str;
 
1543
 
 
1544
        g_return_if_fail (model != NULL);
 
1545
 
 
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;
 
1551
        }
 
1552
 
 
1553
        va_start (valist, model);
 
1554
 
 
1555
        str = va_arg (valist, gchar*);
 
1556
 
 
1557
        while (str != NULL) {
 
1558
                list = g_slist_prepend (list, g_pattern_spec_new (str));
 
1559
 
 
1560
                str = va_arg (valist, gchar*);
 
1561
        }
 
1562
 
 
1563
        va_end (valist);
 
1564
 
 
1565
        model->priv->mime_filter_values = list;
 
1566
}
 
1567
 
 
1568
/**
 
1569
 * egg_recent_model_set_filter_groups:
 
1570
 * @model:  A EggRecentModel object.
 
1571
 *
 
1572
 * Sets which groups are allowed in the list.
 
1573
 *
 
1574
 * Returns: void
 
1575
 */
 
1576
void
 
1577
egg_recent_model_set_filter_groups (EggRecentModel *model,
 
1578
                               ...)
 
1579
{
 
1580
        va_list valist;
 
1581
        GSList *list = NULL;
 
1582
        gchar *str;
 
1583
 
 
1584
        g_return_if_fail (model != NULL);
 
1585
 
 
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;
 
1590
        }
 
1591
 
 
1592
        va_start (valist, model);
 
1593
 
 
1594
        str = va_arg (valist, gchar*);
 
1595
 
 
1596
        while (str != NULL) {
 
1597
                list = g_slist_prepend (list, g_strdup (str));
 
1598
 
 
1599
                str = va_arg (valist, gchar*);
 
1600
        }
 
1601
 
 
1602
        va_end (valist);
 
1603
 
 
1604
        model->priv->group_filter_values = list;
 
1605
}
 
1606
 
 
1607
/**
 
1608
 * egg_recent_model_set_filter_uri_schemes:
 
1609
 * @model:  A EggRecentModel object.
 
1610
 *
 
1611
 * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
 
1612
 *
 
1613
 * Returns: void
 
1614
 */
 
1615
void
 
1616
egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
 
1617
{
 
1618
        va_list valist;
 
1619
        GSList *list = NULL;
 
1620
        gchar *str;
 
1621
 
 
1622
        g_return_if_fail (model != NULL);
 
1623
 
 
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;
 
1629
        }
 
1630
 
 
1631
        va_start (valist, model);
 
1632
 
 
1633
        str = va_arg (valist, gchar*);
 
1634
 
 
1635
        while (str != NULL) {
 
1636
                list = g_slist_prepend (list, g_pattern_spec_new (str));
 
1637
 
 
1638
                str = va_arg (valist, gchar*);
 
1639
        }
 
1640
 
 
1641
        va_end (valist);
 
1642
 
 
1643
        model->priv->scheme_filter_values = list;
 
1644
}
 
1645
 
 
1646
/**
 
1647
 * egg_recent_model_set_sort:
 
1648
 * @model:  A EggRecentModel object.
 
1649
 * @sort:  A EggRecentModelSort type
 
1650
 *
 
1651
 * Sets the type of sorting to be used.
 
1652
 *
 
1653
 * Returns: void
 
1654
 */
 
1655
void
 
1656
egg_recent_model_set_sort (EggRecentModel *model,
 
1657
                             EggRecentModelSort sort)
 
1658
{
 
1659
        g_return_if_fail (model != NULL);
 
1660
        
 
1661
        model->priv->sort_type = sort;
 
1662
}
 
1663
 
 
1664
/**
 
1665
 * egg_recent_model_changed:
 
1666
 * @model:  A EggRecentModel object.
 
1667
 *
 
1668
 * This function causes a "changed" signal to be emitted.
 
1669
 *
 
1670
 * Returns: void
 
1671
 */
 
1672
void
 
1673
egg_recent_model_changed (EggRecentModel *model)
 
1674
{
 
1675
        GList *list = NULL;
 
1676
 
 
1677
        if (model->priv->limit > 0) {
 
1678
                list = egg_recent_model_get_list (model);
 
1679
                /* egg_recent_model_monitor_list (model, list); */
 
1680
        
 
1681
                g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
 
1682
                               list);
 
1683
        }
 
1684
 
 
1685
        if (list)
 
1686
                EGG_RECENT_ITEM_LIST_UNREF (list);
 
1687
}
 
1688
 
 
1689
static void
 
1690
egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
 
1691
{
 
1692
        time_t current_time;
 
1693
        time_t day_seconds;
 
1694
 
 
1695
        time (&current_time);
 
1696
        day_seconds = model->priv->expire_days*24*60*60;
 
1697
 
 
1698
        while (list != NULL) {
 
1699
                EggRecentItem *item = list->data;
 
1700
                time_t timestamp;
 
1701
 
 
1702
                timestamp = egg_recent_item_get_timestamp (item);
 
1703
 
 
1704
                if ((timestamp+day_seconds) < current_time) {
 
1705
                        gchar *uri = egg_recent_item_get_uri (item);
 
1706
                        egg_recent_model_delete (model, uri);
 
1707
 
 
1708
                        g_strdup (uri);
 
1709
                }
 
1710
 
 
1711
                list = list->next;
 
1712
        }
 
1713
}
 
1714
 
 
1715
 
 
1716
/**
 
1717
 * egg_recent_model_remove_expired:
 
1718
 * @model:  A EggRecentModel object.
 
1719
 *
 
1720
 * Goes through the entire list, and removes any items that are older than
 
1721
 * the user-specified expiration period.
 
1722
 *
 
1723
 * Returns: void
 
1724
 */
 
1725
void
 
1726
egg_recent_model_remove_expired (EggRecentModel *model)
 
1727
{
 
1728
        FILE *file;
 
1729
        GList *list=NULL;
 
1730
 
 
1731
        g_return_if_fail (model != NULL);
 
1732
 
 
1733
        file = egg_recent_model_open_file (model);
 
1734
        g_return_if_fail (file != NULL);
 
1735
        
 
1736
        if (egg_recent_model_lock_file (file)) {
 
1737
                list = egg_recent_model_read (model, file);
 
1738
                
 
1739
        } else {
 
1740
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1741
                return;
 
1742
        }
 
1743
 
 
1744
        if (!egg_recent_model_unlock_file (file))
 
1745
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1746
 
 
1747
        if (list != NULL) {
 
1748
                egg_recent_model_remove_expired_list (model, list);
 
1749
                EGG_RECENT_ITEM_LIST_UNREF (list);
 
1750
        }
 
1751
 
 
1752
        fclose (file);
 
1753
}
 
1754
 
 
1755
/**
 
1756
 * egg_recent_model_get_type:
 
1757
 *
 
1758
 * This returns a GType representing a EggRecentModel object.
 
1759
 *
 
1760
 * Returns: a GType
 
1761
 */
 
1762
GType
 
1763
egg_recent_model_get_type (void)
 
1764
{
 
1765
        static GType egg_recent_model_type = 0;
 
1766
 
 
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),
 
1776
                        0,
 
1777
                        (GInstanceInitFunc) egg_recent_model_init
 
1778
                };
 
1779
 
 
1780
                egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
 
1781
                                                        "EggRecentModel",
 
1782
                                                        &egg_recent_model_info, 0);
 
1783
        }
 
1784
 
 
1785
        return egg_recent_model_type;
 
1786
}
 
1787