Statistics
| Revision:

root / libsylph / virtual.c @ 928

History | View | Annotate | Download (13.2 kB)

1
/*
2
 * LibSylph -- E-Mail client library
3
 * Copyright (C) 1999-2006 Hiroyuki Yamamoto
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9
 *
10
 * This library is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16
 * License along with this library; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
 */
19
20
#ifdef HAVE_CONFIG_H
21
#  include "config.h"
22
#endif
23
24
#include "defs.h"
25
26
#include <glib.h>
27
#include <glib/gi18n.h>
28
#include <dirent.h>
29
#include <sys/stat.h>
30
#include <time.h>
31
#include <unistd.h>
32
#include <string.h>
33
#include <errno.h>
34
35
#undef MEASURE_TIME
36
37
#include "folder.h"
38
#include "virtual.h"
39
#include "mh.h"
40
#include "procmsg.h"
41
#include "procheader.h"
42
#include "filter.h"
43
#include "utils.h"
44
45
typedef struct _VirtualSearchInfo        VirtualSearchInfo;
46
typedef struct _SearchCacheInfo                SearchCacheInfo;
47
48
struct _VirtualSearchInfo {
49
        FilterRule *rule;
50
        GSList *mlist;
51
        GHashTable *search_cache_table;
52
        FILE *fp;
53
        gboolean requires_full_headers;
54
        gboolean exclude_trash;
55
};
56
57
struct _SearchCacheInfo {
58
        FolderItem *folder;
59
        guint msgnum;
60
        off_t size;
61
        time_t mtime;
62
        MsgFlags flags;
63
};
64
65
enum
66
{
67
        SCACHE_NOT_EXIST = 0,
68
        SCACHE_MATCHED = 1,
69
        SCACHE_NOT_MATCHED = 2
70
};
71
72
static void        virtual_folder_init        (Folder                *folder,
73
                                         const gchar        *name,
74
                                         const gchar        *path);
75
76
static GHashTable *virtual_read_search_cache
77
                                        (FolderItem        *item);
78
static void virtual_write_search_cache        (FILE                *fp,
79
                                         FolderItem        *item,
80
                                         MsgInfo        *msginfo,
81
                                         gint                 matched);
82
83
static GSList *virtual_search_folder        (VirtualSearchInfo        *info,
84
                                         FolderItem                *item);
85
static gboolean virtual_search_recursive_func
86
                                        (GNode                *node,
87
                                         gpointer         data);
88
89
static Folder        *virtual_folder_new        (const gchar        *name,
90
                                         const gchar        *path);
91
static void     virtual_folder_destroy        (Folder                *folder);
92
93
static GSList  *virtual_get_msg_list        (Folder                *folder,
94
                                         FolderItem        *item,
95
                                         gboolean         use_cache);
96
static gchar   *virtual_fetch_msg        (Folder                *folder,
97
                                         FolderItem        *item,
98
                                         gint                 num);
99
static MsgInfo *virtual_get_msginfo        (Folder                *folder,
100
                                         FolderItem        *item,
101
                                         gint                 num);
102
static gint    virtual_close                (Folder                *folder,
103
                                         FolderItem        *item);
104
105
static gint    virtual_scan_folder        (Folder                *folder,
106
                                         FolderItem        *item);
107
108
static gint    virtual_rename_folder        (Folder                *folder,
109
                                         FolderItem        *item,
110
                                         const gchar        *name);
111
static gint    virtual_remove_folder        (Folder                *folder,
112
                                         FolderItem        *item);
113
114
static FolderClass virtual_class =
115
{
116
        F_VIRTUAL,
117
118
        virtual_folder_new,
119
        virtual_folder_destroy,
120
121
        NULL,
122
        NULL,
123
124
        virtual_get_msg_list,
125
        virtual_fetch_msg,
126
        virtual_get_msginfo,
127
        NULL,
128
        NULL,
129
        NULL,
130
        NULL,
131
        NULL,
132
        NULL,
133
        NULL,
134
        NULL,
135
        NULL,
136
        NULL,
137
        virtual_close,
138
        virtual_scan_folder,
139
140
        NULL,
141
        virtual_rename_folder,
142
        NULL,
143
        virtual_remove_folder,
144
};
145
146
147
FolderClass *virtual_get_class(void)
148
{
149
        return &virtual_class;
150
}
151
152
static Folder *virtual_folder_new(const gchar *name, const gchar *path)
153
{
154
        Folder *folder;
155
156
        folder = (Folder *)g_new0(VirtualFolder, 1);
157
        virtual_folder_init(folder, name, path);
158
159
        return folder;
160
}
161
162
static void virtual_folder_destroy(Folder *folder)
163
{
164
        folder_local_folder_destroy(LOCAL_FOLDER(folder));
165
}
166
167
static void virtual_folder_init(Folder *folder, const gchar *name,
168
                                const gchar *path)
169
{
170
        folder->klass = virtual_get_class();
171
        folder_local_folder_init(folder, name, path);
172
}
173
174
guint sinfo_hash(gconstpointer key)
175
{
176
        const SearchCacheInfo *sinfo = key;
177
        guint h;
178
179
        h = (guint)sinfo->folder;
180
        h ^= sinfo->msgnum;
181
        h ^= (guint)sinfo->size;
182
        h ^= (guint)sinfo->mtime;
183
        /* h ^= (guint)sinfo->flags.tmp_flags; */
184
        h ^= (guint)sinfo->flags.perm_flags;
185
186
        /* g_print("path: %s, n = %u, hash = %u\n",
187
                   sinfo->folder->path, sinfo->msgnum, h); */
188
189
        return h;
190
}
191
192
gint sinfo_equal(gconstpointer v, gconstpointer v2)
193
{
194
        const SearchCacheInfo *s1 = v;
195
        const SearchCacheInfo *s2 = v2;
196
197
        return (s1->folder == s2->folder && s1->msgnum == s2->msgnum &&
198
                s1->size == s2->size && s1->mtime == s2->mtime &&
199
                /* s1->flags.tmp_flags == s2->flags.tmp_flags && */
200
                s1->flags.perm_flags == s2->flags.perm_flags);
201
}
202
203
#define READ_CACHE_DATA_INT(n, fp)                        \
204
{                                                        \
205
        guint32 idata;                                        \
206
                                                        \
207
        if (fread(&idata, sizeof(idata), 1, fp) != 1) {        \
208
                g_warning("Cache data is corrupted\n");        \
209
                fclose(fp);                                \
210
                return table;                                \
211
        } else                                                \
212
                n = idata;                                \
213
}
214
215
static GHashTable *virtual_read_search_cache(FolderItem *item)
216
{
217
        GHashTable *table;
218
        gchar *path, *file;
219
        FILE *fp;
220
        gchar *id;
221
        gint count = 0;
222
223
        g_return_val_if_fail(item != NULL, NULL);
224
225
        path = folder_item_get_path(item);
226
        file = g_strconcat(path, G_DIR_SEPARATOR_S, SEARCH_CACHE, NULL);
227
        debug_print("reading search cache: %s\n", file);
228
        fp = procmsg_open_data_file(file, SEARCH_CACHE_VERSION, DATA_READ,
229
                                    NULL, 0);
230
        g_free(file);
231
        g_free(path);
232
        if (!fp)
233
                return NULL;
234
235
        table = g_hash_table_new(sinfo_hash, sinfo_equal);
236
237
        while (procmsg_read_cache_data_str(fp, &id) == 0) {
238
                FolderItem *folder;
239
                guint32 msgnum;
240
                off_t size;
241
                time_t mtime;
242
                MsgFlags flags;
243
                gint matched;
244
                SearchCacheInfo *sinfo;
245
246
                folder = folder_find_item_from_identifier(id);
247
                g_free(id);
248
249
                while (fread(&msgnum, sizeof(msgnum), 1, fp) == 1) {
250
                        if (msgnum == 0)
251
                                break;
252
253
                        READ_CACHE_DATA_INT(size, fp);
254
                        READ_CACHE_DATA_INT(mtime, fp);
255
                        READ_CACHE_DATA_INT(flags.tmp_flags, fp);
256
                        READ_CACHE_DATA_INT(flags.perm_flags, fp);
257
                        READ_CACHE_DATA_INT(matched, fp);
258
259
                        if (folder) {
260
                                sinfo = g_new(SearchCacheInfo, 1);
261
                                sinfo->folder = folder;
262
                                sinfo->msgnum = msgnum;
263
                                sinfo->size = size;
264
                                sinfo->mtime = mtime;
265
                                sinfo->flags = flags;
266
                                g_hash_table_insert(table, sinfo,
267
                                                    GINT_TO_POINTER(matched));
268
                                ++count;
269
                        }
270
                }
271
        }
272
273
        debug_print("%d cache items read.\n", count);
274
275
        fclose(fp);
276
        return table;
277
}
278
279
static void virtual_write_search_cache(FILE *fp, FolderItem *item,
280
                                       MsgInfo *msginfo, gint matched)
281
{
282
        if (!item && !msginfo) {
283
                WRITE_CACHE_DATA_INT(0, fp);
284
                return;
285
        }
286
287
        if (item) {
288
                gchar *id;
289
290
                id = folder_item_get_identifier(item);
291
                if (id) {
292
                        WRITE_CACHE_DATA(id, fp);
293
                        g_free(id);
294
                }
295
        }
296
297
        if (msginfo) {
298
                WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
299
                WRITE_CACHE_DATA_INT(msginfo->size, fp);
300
                WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
301
                WRITE_CACHE_DATA_INT
302
                        ((msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK), fp);
303
                WRITE_CACHE_DATA_INT(msginfo->flags.perm_flags, fp);
304
                WRITE_CACHE_DATA_INT(matched, fp);
305
        }
306
}
307
308
static void search_cache_free_func(gpointer key, gpointer value, gpointer data)
309
{
310
        g_free(key);
311
}
312
313
static void virtual_search_cache_free(GHashTable *table)
314
{
315
        if (table) {
316
                g_hash_table_foreach(table, search_cache_free_func, NULL);
317
                g_hash_table_destroy(table);
318
        }
319
}
320
321
static GSList *virtual_search_folder(VirtualSearchInfo *info, FolderItem *item)
322
{
323
        GSList *match_list = NULL;
324
        GSList *mlist;
325
        GSList *cur;
326
        FilterInfo fltinfo;
327
        gint count = 1, total, ncachehit = 0;
328
        GTimeVal tv_prev, tv_cur;
329
330
        g_return_val_if_fail(info != NULL, NULL);
331
        g_return_val_if_fail(info->rule != NULL, NULL);
332
        g_return_val_if_fail(item != NULL, NULL);
333
        g_return_val_if_fail(item->path != NULL, NULL);
334
335
        /* prevent circular reference */
336
        if (item->stype == F_VIRTUAL)
337
                return NULL;
338
339
        g_get_current_time(&tv_prev);
340
        status_print(_("Searching %s ..."), item->path);
341
342
        mlist = folder_item_get_msg_list(item, TRUE);
343
        total = g_slist_length(mlist);
344
345
        memset(&fltinfo, 0, sizeof(FilterInfo));
346
347
        debug_print("start query search: %s\n", item->path);
348
349
        virtual_write_search_cache(info->fp, item, NULL, 0);
350
351
        for (cur = mlist; cur != NULL; cur = cur->next) {
352
                MsgInfo *msginfo = (MsgInfo *)cur->data;
353
                GSList *hlist;
354
355
                g_get_current_time(&tv_cur);
356
                if (tv_cur.tv_sec > tv_prev.tv_sec ||
357
                    tv_cur.tv_usec - tv_prev.tv_usec >
358
                    PROGRESS_UPDATE_INTERVAL * 1000) {
359
                        status_print(_("Searching %s (%d / %d)..."),
360
                                     item->path, count, total);
361
                        tv_prev = tv_cur;
362
                }
363
                ++count;
364
365
                if (info->search_cache_table) {
366
                        gint matched;
367
                        SearchCacheInfo sinfo;
368
369
                        sinfo.folder = item;
370
                        sinfo.msgnum = msginfo->msgnum;
371
                        sinfo.size = msginfo->size;
372
                        sinfo.mtime = msginfo->mtime;
373
                        sinfo.flags = msginfo->flags;
374
375
                        matched = (gint)g_hash_table_lookup
376
                                (info->search_cache_table, &sinfo);
377
                        if (matched == SCACHE_MATCHED) {
378
                                match_list = g_slist_prepend
379
                                        (match_list, msginfo);
380
                                cur->data = NULL;
381
                                virtual_write_search_cache(info->fp, NULL,
382
                                                           msginfo, matched);
383
                                ++ncachehit;
384
                                continue;
385
                        } else if (matched == SCACHE_NOT_MATCHED) {
386
                                virtual_write_search_cache(info->fp, NULL,
387
                                                           msginfo, matched);
388
                                ++ncachehit;
389
                                continue;
390
                        }
391
                }
392
393
                fltinfo.flags = msginfo->flags;
394
                if (info->requires_full_headers) {
395
                        gchar *file;
396
397
                        file = procmsg_get_message_file(msginfo);
398
                        hlist = procheader_get_header_list_from_file(file);
399
                        g_free(file);
400
                } else
401
                        hlist = procheader_get_header_list_from_msginfo
402
                                (msginfo);
403
                if (!hlist)
404
                        continue;
405
406
                if (filter_match_rule(info->rule, msginfo, hlist, &fltinfo)) {
407
                        match_list = g_slist_prepend(match_list, msginfo);
408
                        cur->data = NULL;
409
                        virtual_write_search_cache(info->fp, NULL, msginfo,
410
                                                   SCACHE_MATCHED);
411
                } else {
412
                        virtual_write_search_cache(info->fp, NULL, msginfo,
413
                                                   SCACHE_NOT_MATCHED);
414
                }
415
416
                procheader_header_list_destroy(hlist);
417
        }
418
419
        debug_print("%d cache hits (%d total)\n", ncachehit, total);
420
421
        virtual_write_search_cache(info->fp, NULL, NULL, 0);
422
        procmsg_msg_list_free(mlist);
423
424
        return g_slist_reverse(match_list);
425
}
426
427
static gboolean virtual_search_recursive_func(GNode *node, gpointer data)
428
{
429
        VirtualSearchInfo *info = (VirtualSearchInfo *)data;
430
        FolderItem *item;
431
        GSList *mlist;
432
433
        g_return_val_if_fail(node->data != NULL, FALSE);
434
435
        item = FOLDER_ITEM(node->data);
436
437
        if (!item->path)
438
                return FALSE;
439
        if (info->exclude_trash && item->stype == F_TRASH)
440
                return FALSE;
441
442
        mlist = virtual_search_folder(info, item);
443
        info->mlist = g_slist_concat(info->mlist, mlist);
444
445
        return FALSE;
446
}
447
448
static GSList *virtual_get_msg_list(Folder *folder, FolderItem *item,
449
                                    gboolean use_cache)
450
{
451
        GSList *mlist = NULL;
452
        GSList *flist;
453
        GSList *cur;
454
        FilterRule *rule;
455
        gchar *path;
456
        gchar *rule_file;
457
        gchar *cache_file;
458
        FolderItem *target;
459
        gint new = 0, unread = 0, total = 0;
460
        VirtualSearchInfo info;
461
462
        g_return_val_if_fail(item != NULL, NULL);
463
        g_return_val_if_fail(item->stype == F_VIRTUAL, NULL);
464
465
        path = folder_item_get_path(item);
466
        rule_file = g_strconcat(path, G_DIR_SEPARATOR_S, "filter.xml", NULL);
467
        flist = filter_read_file(rule_file);
468
        g_free(rule_file);
469
470
        g_free(path);
471
472
        if (!flist) {
473
                g_warning("filter rule not found\n");
474
                return NULL;
475
        }
476
477
        rule = (FilterRule *)flist->data;
478
        target = folder_find_item_from_identifier(rule->target_folder);
479
480
        if (!target || target == item) {
481
                g_warning("invalid target folder\n");
482
                goto finish;
483
        }
484
485
        info.rule = rule;
486
        info.mlist = NULL;
487
        if (use_cache)
488
                info.search_cache_table = virtual_read_search_cache(item);
489
        else
490
                info.search_cache_table = NULL;
491
492
        path = folder_item_get_path(item);
493
        cache_file = g_strconcat(path, G_DIR_SEPARATOR_S, SEARCH_CACHE, NULL);
494
        info.fp = procmsg_open_data_file(cache_file, SEARCH_CACHE_VERSION,
495
                                         DATA_WRITE, NULL, 0);
496
        g_free(cache_file);
497
        g_free(path);
498
        if (!info.fp)
499
                goto finish;
500
501
        info.requires_full_headers =
502
                filter_rule_requires_full_headers(rule);
503
504
        if (rule->recursive) {
505
                if (target->stype == F_TRASH)
506
                        info.exclude_trash = FALSE;
507
                else
508
                        info.exclude_trash = TRUE;
509
        } else
510
                info.exclude_trash = FALSE;
511
512
        if (rule->recursive) {
513
                g_node_traverse(target->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
514
                                virtual_search_recursive_func, &info);
515
                mlist = info.mlist;
516
        } else
517
                mlist = virtual_search_folder(&info, target);
518
519
        fclose(info.fp);
520
        virtual_search_cache_free(info.search_cache_table);
521
522
        for (cur = mlist; cur != NULL; cur = cur->next) {
523
                MsgInfo *msginfo = (MsgInfo *)cur->data;
524
525
                if (MSG_IS_NEW(msginfo->flags))
526
                        ++new;
527
                if (MSG_IS_UNREAD(msginfo->flags))
528
                        ++unread;
529
                ++total;
530
        }
531
532
        item->new = new;
533
        item->unread = unread;
534
        item->total = total;
535
        item->updated = TRUE;
536
537
finish:
538
        filter_rule_list_free(flist);
539
        return mlist;
540
}
541
542
static gchar *virtual_fetch_msg(Folder *folder, FolderItem *item, gint num)
543
{
544
        return NULL;
545
}
546
547
static MsgInfo *virtual_get_msginfo(Folder *folder, FolderItem *item, gint num)
548
{
549
        return NULL;
550
}
551
552
static gint virtual_close(Folder *folder, FolderItem *item)
553
{
554
        return 0;
555
}
556
557
static gint virtual_scan_folder(Folder *folder, FolderItem *item)
558
{
559
        return 0;
560
}
561
562
static gint virtual_rename_folder(Folder *folder, FolderItem *item,
563
                                  const gchar *name)
564
{
565
        g_return_val_if_fail(item != NULL, -1);
566
        g_return_val_if_fail(item->stype == F_VIRTUAL, -1);
567
568
        return mh_get_class()->rename_folder(folder, item, name);
569
}
570
571
static gint virtual_remove_folder(Folder *folder, FolderItem *item)
572
{
573
        gchar *path;
574
575
        g_return_val_if_fail(item != NULL, -1);
576
        g_return_val_if_fail(item->stype == F_VIRTUAL, -1);
577
578
        path = folder_item_get_path(item);
579
        if (remove_dir_recursive(path) < 0) {
580
                g_warning("can't remove directory '%s'\n", path);
581
                g_free(path);
582
                return -1;
583
        }
584
585
        g_free(path);
586
        folder_item_remove(item);
587
        return 0;
588
}