~ubuntu-branches/ubuntu/quantal/gbonds/quantal

« back to all changes in this revision

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

  • Committer: Bazaar Package Importer
  • Author(s): Richard Laager
  • Date: 2007-03-14 23:50:34 UTC
  • Revision ID: james.westby@ubuntu.com-20070314235034-997qegw33jx0wb9r
Tags: upstream-2.0.2
ImportĀ upstreamĀ versionĀ 2.0.2

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
        egg_recent_model_changed (model);
 
647
 
 
648
        return FALSE;
 
649
}
 
650
 
 
651
static void
 
652
egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
 
653
                               const gchar *monitor_uri,
 
654
                               const gchar *info_uri,
 
655
                               GnomeVFSMonitorEventType event_type,
 
656
                               gpointer user_data)
 
657
{
 
658
        EggRecentModel *model;
 
659
 
 
660
        g_return_if_fail (user_data != NULL);
 
661
        g_return_if_fail (EGG_IS_RECENT_MODEL (user_data));
 
662
        model = EGG_RECENT_MODEL (user_data);
 
663
 
 
664
        if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
 
665
                if (model->priv->changed_timeout > 0) {
 
666
                        g_source_remove (model->priv->changed_timeout);
 
667
                }
 
668
 
 
669
                model->priv->changed_timeout = g_timeout_add (
 
670
                        EGG_RECENT_MODEL_TIMEOUT_LENGTH,
 
671
                        (GSourceFunc)egg_recent_model_changed_timeout,
 
672
                        model);
 
673
        }
 
674
}
 
675
 
 
676
static void
 
677
egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
 
678
{
 
679
        if (should_monitor && model->priv->monitor == NULL) {
 
680
 
 
681
                gnome_vfs_monitor_add (&model->priv->monitor,
 
682
                                             model->priv->path,
 
683
                                             GNOME_VFS_MONITOR_FILE,
 
684
                                             egg_recent_model_monitor_cb,
 
685
                                             model);
 
686
 
 
687
                /* if the above fails, don't worry about it.
 
688
                 * local notifications will still happen
 
689
                 */
 
690
 
 
691
        } else if (!should_monitor && model->priv->monitor != NULL) {
 
692
                gnome_vfs_monitor_cancel (model->priv->monitor);
 
693
                model->priv->monitor = NULL;
 
694
        }
 
695
}
 
696
 
 
697
static void
 
698
egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
 
699
{
 
700
        model->priv->limit = limit;
 
701
 
 
702
        if (limit <= 0)
 
703
                egg_recent_model_monitor (model, FALSE);
 
704
        else {
 
705
                egg_recent_model_monitor (model, TRUE);
 
706
                egg_recent_model_changed (model);
 
707
        }
 
708
}
 
709
 
 
710
static GList *
 
711
egg_recent_model_read (EggRecentModel *model, FILE *file)
 
712
{
 
713
        GList *list=NULL;
 
714
        gchar *content;
 
715
        GMarkupParseContext *ctx;
 
716
        ParseInfo info;
 
717
        GError *error;
 
718
 
 
719
        content = egg_recent_model_read_raw (model, file);
 
720
 
 
721
        if (strlen (content) <= 0) {
 
722
                g_free (content);
 
723
                return NULL;
 
724
        }
 
725
 
 
726
        parse_info_init (&info);
 
727
        
 
728
        ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
 
729
        
 
730
        error = NULL;
 
731
        if (!g_markup_parse_context_parse (ctx, content, strlen (content),
 
732
                                           &error)) {
 
733
                g_warning (error->message);
 
734
                g_error_free (error);
 
735
                error = NULL;
 
736
                goto out;
 
737
        }
 
738
 
 
739
        error = NULL;
 
740
        if (!g_markup_parse_context_end_parse (ctx, &error))
 
741
                goto out;
 
742
        
 
743
        g_markup_parse_context_free (ctx);
 
744
out:
 
745
        list = info.items;
 
746
 
 
747
        parse_info_free (&info);
 
748
 
 
749
        g_free (content);
 
750
 
 
751
        /*
 
752
        g_print ("Total items: %d\n", g_list_length (list));
 
753
        */
 
754
 
 
755
        return list;
 
756
}
 
757
 
 
758
 
 
759
static gboolean
 
760
egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
 
761
{
 
762
        GString *string;
 
763
        gchar *data;
 
764
        EggRecentItem *item;
 
765
        const GList *groups;
 
766
        int i;
 
767
        int ret;
 
768
        
 
769
        string = g_string_new ("<?xml version=\"1.0\"?>\n");
 
770
        string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
 
771
 
 
772
        i=0;
 
773
        while (list) {
 
774
                gchar *uri;
 
775
                gchar *mime_type;
 
776
                gchar *escaped_uri;
 
777
                time_t timestamp;
 
778
                item = (EggRecentItem *)list->data;
 
779
 
 
780
 
 
781
                uri = egg_recent_item_get_uri_utf8 (item);
 
782
                escaped_uri = g_markup_escape_text (uri,
 
783
                                                    strlen (uri));
 
784
                g_free (uri);
 
785
 
 
786
                mime_type = egg_recent_item_get_mime_type (item);
 
787
                timestamp = egg_recent_item_get_timestamp (item);
 
788
                
 
789
                string = g_string_append (string, "  <" TAG_RECENT_ITEM ">\n");
 
790
 
 
791
                g_string_append_printf (string,
 
792
                                "    <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
 
793
 
 
794
                if (mime_type)
 
795
                        g_string_append_printf (string,
 
796
                                "    <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
 
797
                else
 
798
                        g_string_append_printf (string,
 
799
                                "    <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
 
800
 
 
801
                
 
802
                g_string_append_printf (string,
 
803
                                "    <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
 
804
 
 
805
                if (egg_recent_item_get_private (item))
 
806
                        string = g_string_append (string,
 
807
                                        "    <" TAG_PRIVATE "/>\n");
 
808
 
 
809
                /* write the groups */
 
810
                string = g_string_append (string,
 
811
                                "    <" TAG_GROUPS ">\n");
 
812
                groups = egg_recent_item_get_groups (item);
 
813
 
 
814
                if (groups == NULL && egg_recent_item_get_private (item))
 
815
                        g_warning ("Item with URI \"%s\" marked as private, but"
 
816
                                   " does not belong to any groups.\n", uri);
 
817
                
 
818
                while (groups) {
 
819
                        const gchar *group = (const gchar *)groups->data;
 
820
                        gchar *escaped_group;
 
821
 
 
822
                        escaped_group = g_markup_escape_text (group, strlen(group));
 
823
 
 
824
                        g_string_append_printf (string,
 
825
                                        "      <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
 
826
                                        escaped_group);
 
827
 
 
828
                        g_free (escaped_group);
 
829
 
 
830
                        groups = groups->next;
 
831
                }
 
832
                
 
833
                string = g_string_append (string, "    </" TAG_GROUPS ">\n");
 
834
 
 
835
                string = g_string_append (string,
 
836
                                "  </" TAG_RECENT_ITEM ">\n");
 
837
 
 
838
                g_free (mime_type);
 
839
                g_free (escaped_uri);
 
840
 
 
841
                list = list->next;
 
842
                i++;
 
843
        }
 
844
 
 
845
        string = g_string_append (string, "</" TAG_RECENT_FILES ">");
 
846
 
 
847
        data = g_string_free (string, FALSE);
 
848
 
 
849
        ret = egg_recent_model_write_raw (model, file, data);
 
850
 
 
851
        g_free (data);
 
852
 
 
853
        return ret;
 
854
}
 
855
 
 
856
static FILE *
 
857
egg_recent_model_open_file (EggRecentModel *model)
 
858
{
 
859
        FILE *file;
 
860
        
 
861
        file = fopen (model->priv->path, "r+");
 
862
        if (file == NULL) {
 
863
                /* be paranoid */
 
864
                umask (077);
 
865
 
 
866
                file = fopen (model->priv->path, "w+");
 
867
 
 
868
                g_return_val_if_fail (file != NULL, NULL);
 
869
        }
 
870
 
 
871
        return file;
 
872
}
 
873
 
 
874
static gboolean
 
875
egg_recent_model_lock_file (FILE *file)
 
876
{
 
877
        int fd;
 
878
        gint    try = 5;
 
879
 
 
880
        rewind (file);
 
881
        fd = fileno (file);
 
882
 
 
883
        /* Attempt to lock the file 5 times,
 
884
         * waiting a random interval (< 1 second) 
 
885
         * in between attempts.
 
886
         * We should really be doing asynchronous
 
887
         * locking, but requires substantially larger
 
888
         * changes.
 
889
         */
 
890
        
 
891
        while (try > 0)
 
892
        {
 
893
                int rand_interval;
 
894
 
 
895
                if (lockf (fd, F_TLOCK, 0) == 0)
 
896
                        return TRUE;
 
897
 
 
898
                rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0));
 
899
                         
 
900
                g_usleep (100000 * rand_interval);
 
901
 
 
902
                --try;
 
903
        }
 
904
 
 
905
        return FALSE;
 
906
}
 
907
 
 
908
static gboolean
 
909
egg_recent_model_unlock_file (FILE *file)
 
910
{
 
911
        int fd;
 
912
 
 
913
        rewind (file);
 
914
        fd = fileno (file);
 
915
 
 
916
        return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE;
 
917
}
 
918
 
 
919
static void
 
920
egg_recent_model_finalize (GObject *object)
 
921
{
 
922
        EggRecentModel *model = EGG_RECENT_MODEL (object);
 
923
 
 
924
        egg_recent_model_monitor (model, FALSE);
 
925
 
 
926
 
 
927
        g_slist_foreach (model->priv->mime_filter_values,
 
928
                         (GFunc) g_pattern_spec_free, NULL);
 
929
        g_slist_free (model->priv->mime_filter_values);
 
930
        model->priv->mime_filter_values = NULL;
 
931
 
 
932
        g_slist_foreach (model->priv->scheme_filter_values,
 
933
                         (GFunc) g_pattern_spec_free, NULL);
 
934
        g_slist_free (model->priv->scheme_filter_values);
 
935
        model->priv->scheme_filter_values = NULL;
 
936
 
 
937
        g_slist_foreach (model->priv->group_filter_values,
 
938
                         (GFunc) g_free, NULL);
 
939
        g_slist_free (model->priv->group_filter_values);
 
940
        model->priv->group_filter_values = NULL;
 
941
 
 
942
 
 
943
        if (model->priv->limit_change_notify_id)
 
944
                gconf_client_notify_remove (model->priv->client,
 
945
                                            model->priv->limit_change_notify_id);
 
946
        model->priv->expiration_change_notify_id = 0;
 
947
 
 
948
        if (model->priv->expiration_change_notify_id)
 
949
                gconf_client_notify_remove (model->priv->client,
 
950
                                            model->priv->expiration_change_notify_id);
 
951
        model->priv->expiration_change_notify_id = 0;
 
952
 
 
953
        g_object_unref (model->priv->client);
 
954
        model->priv->client = NULL;
 
955
 
 
956
 
 
957
        g_free (model->priv->path);
 
958
        model->priv->path = NULL;
 
959
        
 
960
        g_hash_table_destroy (model->priv->monitors);
 
961
        model->priv->monitors = NULL;
 
962
 
 
963
 
 
964
        g_free (model->priv);
 
965
}
 
966
 
 
967
static void
 
968
egg_recent_model_set_property (GObject *object,
 
969
                               guint prop_id,
 
970
                               const GValue *value,
 
971
                               GParamSpec *pspec)
 
972
{
 
973
        EggRecentModel *model = EGG_RECENT_MODEL (object);
 
974
 
 
975
        switch (prop_id)
 
976
        {
 
977
                case PROP_MIME_FILTERS:
 
978
                        model->priv->mime_filter_values =
 
979
                                (GSList *)g_value_get_pointer (value);
 
980
                break;
 
981
 
 
982
                case PROP_GROUP_FILTERS:
 
983
                        model->priv->group_filter_values =
 
984
                                (GSList *)g_value_get_pointer (value);
 
985
                break;
 
986
 
 
987
                case PROP_SCHEME_FILTERS:
 
988
                        model->priv->scheme_filter_values =
 
989
                                (GSList *)g_value_get_pointer (value);
 
990
                break;
 
991
 
 
992
                case PROP_SORT_TYPE:
 
993
                        model->priv->sort_type = g_value_get_int (value);
 
994
                break;
 
995
 
 
996
                case PROP_LIMIT:
 
997
                        egg_recent_model_set_limit (model,
 
998
                                                g_value_get_int (value));
 
999
                break;
 
1000
 
 
1001
                default:
 
1002
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
1003
                break;
 
1004
        }
 
1005
}
 
1006
 
 
1007
static void
 
1008
egg_recent_model_get_property (GObject *object,
 
1009
                               guint prop_id,
 
1010
                               GValue *value,
 
1011
                               GParamSpec *pspec)
 
1012
{
 
1013
        EggRecentModel *model = EGG_RECENT_MODEL (object);
 
1014
 
 
1015
        switch (prop_id)
 
1016
        {
 
1017
                case PROP_MIME_FILTERS:
 
1018
                        g_value_set_pointer (value, model->priv->mime_filter_values);
 
1019
                break;
 
1020
 
 
1021
                case PROP_GROUP_FILTERS:
 
1022
                        g_value_set_pointer (value, model->priv->group_filter_values);
 
1023
                break;
 
1024
 
 
1025
                case PROP_SCHEME_FILTERS:
 
1026
                        g_value_set_pointer (value, model->priv->scheme_filter_values);
 
1027
                break;
 
1028
 
 
1029
                case PROP_SORT_TYPE:
 
1030
                        g_value_set_int (value, model->priv->sort_type);
 
1031
                break;
 
1032
 
 
1033
                case PROP_LIMIT:
 
1034
                        g_value_set_int (value, model->priv->limit);
 
1035
                break;
 
1036
 
 
1037
                default:
 
1038
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
1039
                break;
 
1040
        }
 
1041
}
 
1042
 
 
1043
static void
 
1044
egg_recent_model_class_init (EggRecentModelClass * klass)
 
1045
{
 
1046
        GObjectClass *object_class;
 
1047
 
 
1048
        object_class = G_OBJECT_CLASS (klass);
 
1049
        object_class->set_property = egg_recent_model_set_property;
 
1050
        object_class->get_property = egg_recent_model_get_property;
 
1051
        object_class->finalize     = egg_recent_model_finalize;
 
1052
 
 
1053
        model_signals[CHANGED] = g_signal_new ("changed",
 
1054
                        G_OBJECT_CLASS_TYPE (object_class),
 
1055
                        G_SIGNAL_RUN_LAST,
 
1056
                        G_STRUCT_OFFSET (EggRecentModelClass, changed),
 
1057
                        NULL, NULL,
 
1058
                        g_cclosure_marshal_VOID__POINTER,
 
1059
                        G_TYPE_NONE, 1,
 
1060
                        G_TYPE_POINTER);
 
1061
 
 
1062
        
 
1063
        g_object_class_install_property (object_class,
 
1064
                                         PROP_MIME_FILTERS,
 
1065
                                         g_param_spec_pointer ("mime-filters",
 
1066
                                         "Mime Filters",
 
1067
                                         "List of mime types to be allowed.",
 
1068
                                         G_PARAM_READWRITE));
 
1069
        
 
1070
        g_object_class_install_property (object_class,
 
1071
                                         PROP_GROUP_FILTERS,
 
1072
                                         g_param_spec_pointer ("group-filters",
 
1073
                                         "Group Filters",
 
1074
                                         "List of groups to be allowed.",
 
1075
                                         G_PARAM_READWRITE));
 
1076
 
 
1077
        g_object_class_install_property (object_class,
 
1078
                                         PROP_SCHEME_FILTERS,
 
1079
                                         g_param_spec_pointer ("scheme-filters",
 
1080
                                         "Scheme Filters",
 
1081
                                         "List of URI schemes to be allowed.",
 
1082
                                         G_PARAM_READWRITE));
 
1083
 
 
1084
        g_object_class_install_property (object_class,
 
1085
                                         PROP_SORT_TYPE,
 
1086
                                         g_param_spec_int ("sort-type",
 
1087
                                         "Sort Type",
 
1088
                                         "Type of sorting to be done.",
 
1089
                                         0, EGG_RECENT_MODEL_SORT_NONE,
 
1090
                                         EGG_RECENT_MODEL_SORT_MRU,
 
1091
                                         G_PARAM_READWRITE));
 
1092
 
 
1093
        g_object_class_install_property (object_class,
 
1094
                                         PROP_LIMIT,
 
1095
                                         g_param_spec_int ("limit",
 
1096
                                         "Limit",
 
1097
                                         "Max number of items allowed.",
 
1098
                                         -1, EGG_RECENT_MODEL_MAX_ITEMS,
 
1099
                                         EGG_RECENT_MODEL_DEFAULT_LIMIT,
 
1100
                                         G_PARAM_READWRITE));
 
1101
 
 
1102
        klass->changed = NULL;
 
1103
}
 
1104
 
 
1105
 
 
1106
 
 
1107
static void
 
1108
egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
 
1109
                                GConfEntry *entry, gpointer user_data)
 
1110
{
 
1111
        EggRecentModel *model;
 
1112
        GConfValue *value;
 
1113
 
 
1114
        model = EGG_RECENT_MODEL (user_data);
 
1115
 
 
1116
        g_return_if_fail (model != NULL);
 
1117
 
 
1118
        if (model->priv->use_default_limit == FALSE)
 
1119
                return; /* ignore this key */
 
1120
 
 
1121
        /* the key was unset, and the schema has apparently failed */
 
1122
        if (entry == NULL)
 
1123
                return;
 
1124
 
 
1125
        value = gconf_entry_get_value (entry);
 
1126
 
 
1127
        if (value->type != GCONF_VALUE_INT) {
 
1128
                g_warning ("Expected GConfValue of type integer, "
 
1129
                           "got something else");
 
1130
        }
 
1131
 
 
1132
 
 
1133
        egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
 
1134
}
 
1135
 
 
1136
static void
 
1137
egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
 
1138
                                     GConfEntry *entry, gpointer user_data)
 
1139
{
 
1140
 
 
1141
}
 
1142
 
 
1143
static void
 
1144
egg_recent_model_init (EggRecentModel * model)
 
1145
{
 
1146
        if (!gnome_vfs_init ()) {
 
1147
                g_warning ("gnome-vfs initialization failed.");
 
1148
                return;
 
1149
        }
 
1150
        
 
1151
 
 
1152
        model->priv = g_new0 (EggRecentModelPrivate, 1);
 
1153
 
 
1154
        model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
 
1155
                                             g_get_home_dir ());
 
1156
 
 
1157
        model->priv->mime_filter_values   = NULL;
 
1158
        model->priv->group_filter_values  = NULL;
 
1159
        model->priv->scheme_filter_values = NULL;
 
1160
        
 
1161
        model->priv->client = gconf_client_get_default ();
 
1162
        gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR,
 
1163
                              GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
 
1164
 
 
1165
        model->priv->limit_change_notify_id =
 
1166
                        gconf_client_notify_add (model->priv->client,
 
1167
                                                 EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY,
 
1168
                                                 egg_recent_model_limit_changed,
 
1169
                                                 model, NULL, NULL);
 
1170
 
 
1171
        model->priv->expiration_change_notify_id =
 
1172
                        gconf_client_notify_add (model->priv->client,
 
1173
                                                 EGG_RECENT_MODEL_EXPIRE_KEY,
 
1174
                                                 egg_recent_model_expiration_changed,
 
1175
                                                 model, NULL, NULL);
 
1176
 
 
1177
        model->priv->expire_days = gconf_client_get_int (
 
1178
                                        model->priv->client,
 
1179
                                        EGG_RECENT_MODEL_EXPIRE_KEY,
 
1180
                                        NULL);
 
1181
                                        
 
1182
#if 0
 
1183
        /* keep this out, for now */
 
1184
        model->priv->limit = gconf_client_get_int (
 
1185
                                        model->priv->client,
 
1186
                                        EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL);
 
1187
        model->priv->use_default_limit = TRUE;
 
1188
#endif
 
1189
        model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
 
1190
        model->priv->use_default_limit = FALSE;
 
1191
 
 
1192
        model->priv->monitors = g_hash_table_new_full (
 
1193
                                        g_str_hash, g_str_equal,
 
1194
                                        (GDestroyNotify) g_free,
 
1195
                                        (GDestroyNotify) gnome_vfs_monitor_cancel);
 
1196
 
 
1197
        model->priv->monitor = NULL;
 
1198
        egg_recent_model_monitor (model, TRUE);
 
1199
}
 
1200
 
 
1201
 
 
1202
/**
 
1203
 * egg_recent_model_new:
 
1204
 * @sort:  the type of sorting to use
 
1205
 * @limit:  maximum number of items in the list
 
1206
 *
 
1207
 * This creates a new EggRecentModel object.
 
1208
 *
 
1209
 * Returns: a EggRecentModel object
 
1210
 */
 
1211
EggRecentModel *
 
1212
egg_recent_model_new (EggRecentModelSort sort)
 
1213
{
 
1214
        EggRecentModel *model;
 
1215
 
 
1216
        model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
 
1217
                                  "sort-type", sort, NULL));
 
1218
 
 
1219
        g_return_val_if_fail (model, NULL);
 
1220
 
 
1221
        return model;
 
1222
}
 
1223
 
 
1224
/**
 
1225
 * egg_recent_model_add_full:
 
1226
 * @model:  A EggRecentModel object.
 
1227
 * @item:  A EggRecentItem
 
1228
 *
 
1229
 * This function adds an item to the list of recently used URIs.
 
1230
 *
 
1231
 * Returns: gboolean
 
1232
 */
 
1233
gboolean
 
1234
egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
 
1235
{
 
1236
        FILE *file;
 
1237
        GList *list = NULL;
 
1238
        gboolean ret = FALSE;
 
1239
        gboolean updated = FALSE;
 
1240
        char *uri;
 
1241
        time_t t;
 
1242
        
 
1243
        g_return_val_if_fail (model != NULL, FALSE);
 
1244
        g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
 
1245
 
 
1246
        uri = egg_recent_item_get_uri (item);
 
1247
        if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
 
1248
                g_free (uri);
 
1249
                return FALSE;
 
1250
        } else {
 
1251
                g_free (uri);
 
1252
        }
 
1253
 
 
1254
        file = egg_recent_model_open_file (model);
 
1255
        g_return_val_if_fail (file != NULL, FALSE);
 
1256
 
 
1257
        time (&t);
 
1258
        egg_recent_item_set_timestamp (item, t);
 
1259
 
 
1260
        if (egg_recent_model_lock_file (file)) {
 
1261
 
 
1262
                /* read existing stuff */
 
1263
                list = egg_recent_model_read (model, file);
 
1264
 
 
1265
                /* if it's already there, we just update it */
 
1266
                updated = egg_recent_model_update_item (list, item);
 
1267
 
 
1268
                if (!updated) {
 
1269
                        list = g_list_prepend (list, item);
 
1270
 
 
1271
                        egg_recent_model_enforce_limit (list,
 
1272
                                                EGG_RECENT_MODEL_MAX_ITEMS);
 
1273
                }
 
1274
 
 
1275
                /* write new stuff */
 
1276
                if (!egg_recent_model_write (model, file, list))
 
1277
                        g_warning ("Write failed: %s", strerror (errno));
 
1278
 
 
1279
                if (!updated)
 
1280
                        list = g_list_remove (list, item);
 
1281
 
 
1282
                EGG_RECENT_ITEM_LIST_UNREF (list);
 
1283
                ret = TRUE;
 
1284
        } else {
 
1285
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1286
                return FALSE;
 
1287
        }
 
1288
 
 
1289
        if (!egg_recent_model_unlock_file (file))
 
1290
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1291
 
 
1292
        fclose (file);
 
1293
 
 
1294
        if (model->priv->monitor == NULL) {
 
1295
                /* since monitoring isn't working, at least give a
 
1296
                 * local notification
 
1297
                 */
 
1298
                egg_recent_model_changed (model);
 
1299
        }
 
1300
 
 
1301
        return ret;
 
1302
}
 
1303
 
 
1304
/**
 
1305
 * egg_recent_model_add:
 
1306
 * @model:  A EggRecentModel object.
 
1307
 * @uri:  A string URI
 
1308
 *
 
1309
 * This function adds an item to the list of recently used URIs.
 
1310
 *
 
1311
 * Returns: gboolean
 
1312
 */
 
1313
gboolean
 
1314
egg_recent_model_add (EggRecentModel *model, const gchar *uri)
 
1315
{
 
1316
        EggRecentItem *item;
 
1317
        gboolean ret = FALSE;
 
1318
 
 
1319
        g_return_val_if_fail (model != NULL, FALSE);
 
1320
        g_return_val_if_fail (uri != NULL, FALSE);
 
1321
 
 
1322
        item = egg_recent_item_new_from_uri (uri);
 
1323
 
 
1324
        g_return_val_if_fail (item != NULL, FALSE);
 
1325
 
 
1326
        ret = egg_recent_model_add_full (model, item);
 
1327
 
 
1328
        egg_recent_item_unref (item);
 
1329
 
 
1330
        return ret;
 
1331
}
 
1332
 
 
1333
 
 
1334
 
 
1335
/**
 
1336
 * egg_recent_model_delete:
 
1337
 * @model:  A EggRecentModel object.
 
1338
 * @uri: The URI you want to delete.
 
1339
 *
 
1340
 * This function deletes a URI from the file of recently used URIs.
 
1341
 *
 
1342
 * Returns: gboolean
 
1343
 */
 
1344
gboolean
 
1345
egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
 
1346
{
 
1347
        FILE *file;
 
1348
        GList *list;
 
1349
        unsigned int length;
 
1350
        gboolean ret = FALSE;
 
1351
 
 
1352
        g_return_val_if_fail (model != NULL, FALSE);
 
1353
        g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
 
1354
        g_return_val_if_fail (uri != NULL, FALSE);
 
1355
 
 
1356
        file = egg_recent_model_open_file (model);
 
1357
        g_return_val_if_fail (file != NULL, FALSE);
 
1358
 
 
1359
        if (egg_recent_model_lock_file (file)) {
 
1360
                list = egg_recent_model_read (model, file);
 
1361
 
 
1362
                if (list == NULL)
 
1363
                        goto out;
 
1364
 
 
1365
                length = g_list_length (list);
 
1366
 
 
1367
                list = egg_recent_model_delete_from_list (list, uri);
 
1368
                
 
1369
                if (length == g_list_length (list)) {
 
1370
                        /* nothing was deleted */
 
1371
                        EGG_RECENT_ITEM_LIST_UNREF (list);
 
1372
                } else {
 
1373
                        egg_recent_model_write (model, file, list);
 
1374
                        EGG_RECENT_ITEM_LIST_UNREF (list);
 
1375
                        ret = TRUE;
 
1376
 
 
1377
                }
 
1378
        } else {
 
1379
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1380
                return FALSE;
 
1381
        }
 
1382
 
 
1383
out:
 
1384
                
 
1385
        if (!egg_recent_model_unlock_file (file))
 
1386
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1387
 
 
1388
        fclose (file);
 
1389
 
 
1390
        g_hash_table_remove (model->priv->monitors, uri);
 
1391
 
 
1392
        if (model->priv->monitor == NULL && ret) {
 
1393
                /* since monitoring isn't working, at least give a
 
1394
                 * local notification
 
1395
                 */
 
1396
                egg_recent_model_changed (model);
 
1397
        }
 
1398
 
 
1399
        return ret;
 
1400
}
 
1401
 
 
1402
 
 
1403
/**
 
1404
 * egg_recent_model_get_list:
 
1405
 * @model:  A EggRecentModel object.
 
1406
 *
 
1407
 * This function gets the current contents of the file
 
1408
 *
 
1409
 * Returns: a GList
 
1410
 */
 
1411
GList *
 
1412
egg_recent_model_get_list (EggRecentModel *model)
 
1413
{
 
1414
        FILE *file;
 
1415
        GList *list=NULL;
 
1416
 
 
1417
        file = egg_recent_model_open_file (model);
 
1418
        g_return_val_if_fail (file != NULL, NULL);
 
1419
        
 
1420
        if (egg_recent_model_lock_file (file)) {
 
1421
                list = egg_recent_model_read (model, file);
 
1422
                
 
1423
        } else {
 
1424
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1425
                fclose (file);
 
1426
                return NULL;
 
1427
        }
 
1428
 
 
1429
        if (!egg_recent_model_unlock_file (file))
 
1430
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1431
 
 
1432
        if (list != NULL) {
 
1433
                list = egg_recent_model_filter (model, list);
 
1434
                list = egg_recent_model_sort (model, list);
 
1435
 
 
1436
                egg_recent_model_enforce_limit (list, model->priv->limit);
 
1437
        }
 
1438
 
 
1439
        fclose (file);
 
1440
 
 
1441
        return list;
 
1442
}
 
1443
 
 
1444
 
 
1445
 
 
1446
/**
 
1447
 * egg_recent_model_set_limit:
 
1448
 * @model:  A EggRecentModel object.
 
1449
 * @limit:  The maximum length of the list
 
1450
 *
 
1451
 * This function sets the maximum length of the list.  Note:  This only affects
 
1452
 * the length of the list emitted in the "changed" signal, not the list stored
 
1453
 * on disk.
 
1454
 *
 
1455
 * Returns:  void
 
1456
 */
 
1457
void
 
1458
egg_recent_model_set_limit (EggRecentModel *model, int limit)
 
1459
{
 
1460
        model->priv->use_default_limit = FALSE;
 
1461
 
 
1462
        egg_recent_model_set_limit_internal (model, limit);
 
1463
}
 
1464
 
 
1465
/**
 
1466
 * egg_recent_model_get_limit:
 
1467
 * @model:  A EggRecentModel object.
 
1468
 *
 
1469
 * This function gets the maximum length of the list. 
 
1470
 *
 
1471
 * Returns:  int
 
1472
 */
 
1473
int
 
1474
egg_recent_model_get_limit (EggRecentModel *model)
 
1475
{
 
1476
        return model->priv->limit;
 
1477
}
 
1478
 
 
1479
 
 
1480
/**
 
1481
 * egg_recent_model_clear:
 
1482
 * @model:  A EggRecentModel object.
 
1483
 *
 
1484
 * This function clears the contents of the file
 
1485
 *
 
1486
 * Returns: void
 
1487
 */
 
1488
void
 
1489
egg_recent_model_clear (EggRecentModel *model)
 
1490
{
 
1491
        FILE *file;
 
1492
        int fd;
 
1493
 
 
1494
        file = egg_recent_model_open_file (model);
 
1495
        g_return_if_fail (file != NULL);
 
1496
 
 
1497
        fd = fileno (file);
 
1498
 
 
1499
        if (egg_recent_model_lock_file (file)) {
 
1500
                ftruncate (fd, 0);
 
1501
        } else {
 
1502
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1503
                return;
 
1504
        }
 
1505
 
 
1506
        if (!egg_recent_model_unlock_file (file))
 
1507
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1508
 
 
1509
        fclose (file);
 
1510
}
 
1511
 
 
1512
 
 
1513
/**
 
1514
 * egg_recent_model_set_filter_mime_types:
 
1515
 * @model:  A EggRecentModel object.
 
1516
 *
 
1517
 * Sets which mime types are allowed in the list.
 
1518
 *
 
1519
 * Returns: void
 
1520
 */
 
1521
void
 
1522
egg_recent_model_set_filter_mime_types (EggRecentModel *model,
 
1523
                               ...)
 
1524
{
 
1525
        va_list valist;
 
1526
        GSList *list = NULL;
 
1527
        gchar *str;
 
1528
 
 
1529
        g_return_if_fail (model != NULL);
 
1530
 
 
1531
        if (model->priv->mime_filter_values != NULL) {
 
1532
                g_slist_foreach (model->priv->mime_filter_values,
 
1533
                                 (GFunc) g_pattern_spec_free, NULL);
 
1534
                g_slist_free (model->priv->mime_filter_values);
 
1535
                model->priv->mime_filter_values = NULL;
 
1536
        }
 
1537
 
 
1538
        va_start (valist, model);
 
1539
 
 
1540
        str = va_arg (valist, gchar*);
 
1541
 
 
1542
        while (str != NULL) {
 
1543
                list = g_slist_prepend (list, g_pattern_spec_new (str));
 
1544
 
 
1545
                str = va_arg (valist, gchar*);
 
1546
        }
 
1547
 
 
1548
        va_end (valist);
 
1549
 
 
1550
        model->priv->mime_filter_values = list;
 
1551
}
 
1552
 
 
1553
/**
 
1554
 * egg_recent_model_set_filter_groups:
 
1555
 * @model:  A EggRecentModel object.
 
1556
 *
 
1557
 * Sets which groups are allowed in the list.
 
1558
 *
 
1559
 * Returns: void
 
1560
 */
 
1561
void
 
1562
egg_recent_model_set_filter_groups (EggRecentModel *model,
 
1563
                               ...)
 
1564
{
 
1565
        va_list valist;
 
1566
        GSList *list = NULL;
 
1567
        gchar *str;
 
1568
 
 
1569
        g_return_if_fail (model != NULL);
 
1570
 
 
1571
        if (model->priv->group_filter_values != NULL) {
 
1572
                g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL);
 
1573
                g_slist_free (model->priv->group_filter_values);
 
1574
                model->priv->group_filter_values = NULL;
 
1575
        }
 
1576
 
 
1577
        va_start (valist, model);
 
1578
 
 
1579
        str = va_arg (valist, gchar*);
 
1580
 
 
1581
        while (str != NULL) {
 
1582
                list = g_slist_prepend (list, g_strdup (str));
 
1583
 
 
1584
                str = va_arg (valist, gchar*);
 
1585
        }
 
1586
 
 
1587
        va_end (valist);
 
1588
 
 
1589
        model->priv->group_filter_values = list;
 
1590
}
 
1591
 
 
1592
/**
 
1593
 * egg_recent_model_set_filter_uri_schemes:
 
1594
 * @model:  A EggRecentModel object.
 
1595
 *
 
1596
 * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
 
1597
 *
 
1598
 * Returns: void
 
1599
 */
 
1600
void
 
1601
egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
 
1602
{
 
1603
        va_list valist;
 
1604
        GSList *list = NULL;
 
1605
        gchar *str;
 
1606
 
 
1607
        g_return_if_fail (model != NULL);
 
1608
 
 
1609
        if (model->priv->scheme_filter_values != NULL) {
 
1610
                g_slist_foreach (model->priv->scheme_filter_values,
 
1611
                                (GFunc) g_pattern_spec_free, NULL);
 
1612
                g_slist_free (model->priv->scheme_filter_values);
 
1613
                model->priv->scheme_filter_values = NULL;
 
1614
        }
 
1615
 
 
1616
        va_start (valist, model);
 
1617
 
 
1618
        str = va_arg (valist, gchar*);
 
1619
 
 
1620
        while (str != NULL) {
 
1621
                list = g_slist_prepend (list, g_pattern_spec_new (str));
 
1622
 
 
1623
                str = va_arg (valist, gchar*);
 
1624
        }
 
1625
 
 
1626
        va_end (valist);
 
1627
 
 
1628
        model->priv->scheme_filter_values = list;
 
1629
}
 
1630
 
 
1631
/**
 
1632
 * egg_recent_model_set_sort:
 
1633
 * @model:  A EggRecentModel object.
 
1634
 * @sort:  A EggRecentModelSort type
 
1635
 *
 
1636
 * Sets the type of sorting to be used.
 
1637
 *
 
1638
 * Returns: void
 
1639
 */
 
1640
void
 
1641
egg_recent_model_set_sort (EggRecentModel *model,
 
1642
                             EggRecentModelSort sort)
 
1643
{
 
1644
        g_return_if_fail (model != NULL);
 
1645
        
 
1646
        model->priv->sort_type = sort;
 
1647
}
 
1648
 
 
1649
/**
 
1650
 * egg_recent_model_changed:
 
1651
 * @model:  A EggRecentModel object.
 
1652
 *
 
1653
 * This function causes a "changed" signal to be emitted.
 
1654
 *
 
1655
 * Returns: void
 
1656
 */
 
1657
void
 
1658
egg_recent_model_changed (EggRecentModel *model)
 
1659
{
 
1660
        GList *list = NULL;
 
1661
 
 
1662
        if (model->priv->limit > 0) {
 
1663
                list = egg_recent_model_get_list (model);
 
1664
                /* egg_recent_model_monitor_list (model, list); */
 
1665
        
 
1666
                g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
 
1667
                               list);
 
1668
        }
 
1669
 
 
1670
        if (list)
 
1671
                EGG_RECENT_ITEM_LIST_UNREF (list);
 
1672
}
 
1673
 
 
1674
static void
 
1675
egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
 
1676
{
 
1677
        time_t current_time;
 
1678
        time_t day_seconds;
 
1679
 
 
1680
        time (&current_time);
 
1681
        day_seconds = model->priv->expire_days*24*60*60;
 
1682
 
 
1683
        while (list != NULL) {
 
1684
                EggRecentItem *item = list->data;
 
1685
                time_t timestamp;
 
1686
 
 
1687
                timestamp = egg_recent_item_get_timestamp (item);
 
1688
 
 
1689
                if ((timestamp+day_seconds) < current_time) {
 
1690
                        gchar *uri = egg_recent_item_get_uri (item);
 
1691
                        egg_recent_model_delete (model, uri);
 
1692
 
 
1693
                        g_strdup (uri);
 
1694
                }
 
1695
 
 
1696
                list = list->next;
 
1697
        }
 
1698
}
 
1699
 
 
1700
 
 
1701
/**
 
1702
 * egg_recent_model_remove_expired:
 
1703
 * @model:  A EggRecentModel object.
 
1704
 *
 
1705
 * Goes through the entire list, and removes any items that are older than
 
1706
 * the user-specified expiration period.
 
1707
 *
 
1708
 * Returns: void
 
1709
 */
 
1710
void
 
1711
egg_recent_model_remove_expired (EggRecentModel *model)
 
1712
{
 
1713
        FILE *file;
 
1714
        GList *list=NULL;
 
1715
 
 
1716
        g_return_if_fail (model != NULL);
 
1717
 
 
1718
        file = egg_recent_model_open_file (model);
 
1719
        g_return_if_fail (file != NULL);
 
1720
        
 
1721
        if (egg_recent_model_lock_file (file)) {
 
1722
                list = egg_recent_model_read (model, file);
 
1723
                
 
1724
        } else {
 
1725
                g_warning ("Failed to lock:  %s", strerror (errno));
 
1726
                return;
 
1727
        }
 
1728
 
 
1729
        if (!egg_recent_model_unlock_file (file))
 
1730
                g_warning ("Failed to unlock: %s", strerror (errno));
 
1731
 
 
1732
        if (list != NULL) {
 
1733
                egg_recent_model_remove_expired_list (model, list);
 
1734
                EGG_RECENT_ITEM_LIST_UNREF (list);
 
1735
        }
 
1736
 
 
1737
        fclose (file);
 
1738
}
 
1739
 
 
1740
/**
 
1741
 * egg_recent_model_get_type:
 
1742
 *
 
1743
 * This returns a GType representing a EggRecentModel object.
 
1744
 *
 
1745
 * Returns: a GType
 
1746
 */
 
1747
GType
 
1748
egg_recent_model_get_type (void)
 
1749
{
 
1750
        static GType egg_recent_model_type = 0;
 
1751
 
 
1752
        if(!egg_recent_model_type) {
 
1753
                static const GTypeInfo egg_recent_model_info = {
 
1754
                        sizeof (EggRecentModelClass),
 
1755
                        NULL, /* base init */
 
1756
                        NULL, /* base finalize */
 
1757
                        (GClassInitFunc)egg_recent_model_class_init, /* class init */
 
1758
                        NULL, /* class finalize */
 
1759
                        NULL, /* class data */
 
1760
                        sizeof (EggRecentModel),
 
1761
                        0,
 
1762
                        (GInstanceInitFunc) egg_recent_model_init
 
1763
                };
 
1764
 
 
1765
                egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
 
1766
                                                        "EggRecentModel",
 
1767
                                                        &egg_recent_model_info, 0);
 
1768
        }
 
1769
 
 
1770
        return egg_recent_model_type;
 
1771
}
 
1772