Statistics
| Revision:

root / src / news.c @ 528

History | View | Annotate | Download (23.9 kB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program 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
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 <stdio.h>
29
#include <string.h>
30
#include <stdlib.h>
31
#include <dirent.h>
32
#include <unistd.h>
33
#include <time.h>
34
35
#include "news.h"
36
#include "nntp.h"
37
#include "socket.h"
38
#include "recv.h"
39
#include "procmsg.h"
40
#include "procheader.h"
41
#include "folder.h"
42
#include "session.h"
43
#include "codeconv.h"
44
#include "utils.h"
45
#include "prefs_common.h"
46
#include "prefs_account.h"
47
#include "inputdialog.h"
48
#include "alertpanel.h"
49
#if USE_SSL
50
#  include "ssl.h"
51
#endif
52
53
#define NNTP_PORT        119
54
#if USE_SSL
55
#define NNTPS_PORT        563
56
#endif
57
58
static void news_folder_init                 (Folder        *folder,
59
                                          const gchar        *name,
60
                                          const gchar        *path);
61
62
static Folder        *news_folder_new        (const gchar        *name,
63
                                         const gchar        *folder);
64
static void         news_folder_destroy        (Folder                *folder);
65
66
static GSList *news_get_article_list        (Folder                *folder,
67
                                         FolderItem        *item,
68
                                         gboolean         use_cache);
69
static gchar *news_fetch_msg                (Folder                *folder,
70
                                         FolderItem        *item,
71
                                         gint                 num);
72
static MsgInfo *news_get_msginfo        (Folder                *folder,
73
                                         FolderItem        *item,
74
                                         gint                 num);
75
76
static gint news_close                        (Folder                *folder,
77
                                         FolderItem        *item);
78
79
static gint news_scan_group                (Folder                *folder,
80
                                         FolderItem        *item);
81
82
#if USE_SSL
83
static Session *news_session_new         (const gchar        *server,
84
                                          gushort         port,
85
                                          const gchar        *userid,
86
                                          const gchar        *passwd,
87
                                          SSLType         ssl_type);
88
#else
89
static Session *news_session_new         (const gchar        *server,
90
                                          gushort         port,
91
                                          const gchar        *userid,
92
                                          const gchar        *passwd);
93
#endif
94
95
static gint news_get_article_cmd         (NNTPSession        *session,
96
                                          const gchar        *cmd,
97
                                          gint                 num,
98
                                          gchar                *filename);
99
static gint news_get_article                 (NNTPSession        *session,
100
                                          gint                 num,
101
                                          gchar                *filename);
102
#if 0
103
static gint news_get_header                 (NNTPSession        *session,
104
                                          gint                 num,
105
                                          gchar                *filename);
106
#endif
107
108
static gint news_select_group                 (NNTPSession        *session,
109
                                          const gchar        *group,
110
                                          gint                *num,
111
                                          gint                *first,
112
                                          gint                *last);
113
static GSList *news_get_uncached_articles(NNTPSession        *session,
114
                                          FolderItem        *item,
115
                                          gint                 cache_last,
116
                                          gint                *rfirst,
117
                                          gint                *rlast);
118
static MsgInfo *news_parse_xover         (const gchar        *xover_str);
119
static gchar *news_parse_xhdr                 (const gchar        *xhdr_str,
120
                                          MsgInfo        *msginfo);
121
static GSList *news_delete_old_articles         (GSList        *alist,
122
                                          FolderItem        *item,
123
                                          gint                 first);
124
static void news_delete_all_articles         (FolderItem        *item);
125
static void news_delete_expired_caches         (GSList        *alist,
126
                                          FolderItem        *item);
127
128
static FolderClass news_class =
129
{
130
        F_NEWS,
131
132
        news_folder_new,
133
        news_folder_destroy,
134
135
        NULL,
136
        NULL,
137
138
        news_get_article_list,
139
        news_fetch_msg,
140
        news_get_msginfo,
141
        NULL,
142
        NULL,
143
        NULL,
144
        NULL,
145
        NULL,
146
        NULL,
147
        NULL,
148
        NULL,
149
        NULL,
150
        NULL,
151
        news_close,
152
        news_scan_group,
153
154
        NULL,
155
        NULL,
156
        NULL,
157
        NULL
158
};
159
160
161
FolderClass *news_get_class(void)
162
{
163
        return &news_class;
164
}
165
166
static Folder *news_folder_new(const gchar *name, const gchar *path)
167
{
168
        Folder *folder;
169
170
        folder = (Folder *)g_new0(NewsFolder, 1);
171
        news_folder_init(folder, name, path);
172
173
        return folder;
174
}
175
176
static void news_folder_destroy(Folder *folder)
177
{
178
        gchar *dir;
179
180
        dir = folder_get_path(folder);
181
        if (is_dir_exist(dir))
182
                remove_dir_recursive(dir);
183
        g_free(dir);
184
185
        folder_remote_folder_destroy(REMOTE_FOLDER(folder));
186
}
187
188
static void news_folder_init(Folder *folder, const gchar *name,
189
                             const gchar *path)
190
{
191
        folder->klass = news_get_class();
192
        folder_remote_folder_init(folder, name, path);
193
}
194
195
#if USE_SSL
196
static Session *news_session_new(const gchar *server, gushort port,
197
                                 const gchar *userid, const gchar *passwd,
198
                                 SSLType ssl_type)
199
#else
200
static Session *news_session_new(const gchar *server, gushort port,
201
                                 const gchar *userid, const gchar *passwd)
202
#endif
203
{
204
        gchar buf[NNTPBUFSIZE];
205
        Session *session;
206
207
        g_return_val_if_fail(server != NULL, NULL);
208
209
        log_message(_("creating NNTP connection to %s:%d ...\n"), server, port);
210
211
#if USE_SSL
212
        session = nntp_session_new(server, port, buf, userid, passwd, ssl_type);
213
#else
214
        session = nntp_session_new(server, port, buf, userid, passwd);
215
#endif
216
217
        return session;
218
}
219
220
static Session *news_session_new_for_folder(Folder *folder)
221
{
222
        Session *session;
223
        PrefsAccount *ac;
224
        const gchar *userid = NULL;
225
        gchar *passwd = NULL;
226
        gushort port;
227
228
        g_return_val_if_fail(folder != NULL, NULL);
229
        g_return_val_if_fail(folder->account != NULL, NULL);
230
231
        ac = folder->account;
232
        if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
233
                userid = ac->userid;
234
                if (ac->passwd && ac->passwd[0])
235
                        passwd = g_strdup(ac->passwd);
236
                else
237
                        passwd = input_dialog_query_password(ac->nntp_server,
238
                                                             userid);
239
        }
240
241
#if USE_SSL
242
        port = ac->set_nntpport ? ac->nntpport
243
                : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT;
244
        session = news_session_new(ac->nntp_server, port, userid, passwd,
245
                                   ac->ssl_nntp);
246
#else
247
        port = ac->set_nntpport ? ac->nntpport : NNTP_PORT;
248
        session = news_session_new(ac->nntp_server, port, userid, passwd);
249
#endif
250
251
        g_free(passwd);
252
253
        return session;
254
}
255
256
static NNTPSession *news_session_get(Folder *folder)
257
{
258
        RemoteFolder *rfolder = REMOTE_FOLDER(folder);
259
260
        g_return_val_if_fail(folder != NULL, NULL);
261
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
262
        g_return_val_if_fail(folder->account != NULL, NULL);
263
264
        if (!prefs_common.online_mode)
265
                return NULL;
266
267
        if (!rfolder->session) {
268
                rfolder->session = news_session_new_for_folder(folder);
269
                return NNTP_SESSION(rfolder->session);
270
        }
271
272
        if (time(NULL) - rfolder->session->last_access_time <
273
                SESSION_TIMEOUT_INTERVAL) {
274
                return NNTP_SESSION(rfolder->session);
275
        }
276
277
        if (nntp_mode(NNTP_SESSION(rfolder->session), FALSE)
278
            != NN_SUCCESS) {
279
                log_warning(_("NNTP connection to %s:%d has been"
280
                              " disconnected. Reconnecting...\n"),
281
                            folder->account->nntp_server,
282
                            folder->account->set_nntpport ?
283
                            folder->account->nntpport : NNTP_PORT);
284
                session_destroy(rfolder->session);
285
                rfolder->session = news_session_new_for_folder(folder);
286
        }
287
288
        if (rfolder->session)
289
                session_set_access_time(rfolder->session);
290
291
        return NNTP_SESSION(rfolder->session);
292
}
293
294
static GSList *news_get_article_list(Folder *folder, FolderItem *item,
295
                                     gboolean use_cache)
296
{
297
        GSList *alist;
298
        NNTPSession *session;
299
300
        g_return_val_if_fail(folder != NULL, NULL);
301
        g_return_val_if_fail(item != NULL, NULL);
302
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
303
304
        session = news_session_get(folder);
305
306
        if (!session) {
307
                alist = procmsg_read_cache(item, FALSE);
308
                item->last_num = procmsg_get_last_num_in_msg_list(alist);
309
        } else if (use_cache) {
310
                GSList *newlist;
311
                gint cache_last;
312
                gint first, last;
313
314
                alist = procmsg_read_cache(item, FALSE);
315
316
                cache_last = procmsg_get_last_num_in_msg_list(alist);
317
                newlist = news_get_uncached_articles
318
                        (session, item, cache_last, &first, &last);
319
                if (newlist)
320
                        item->cache_dirty = TRUE;
321
                if (first == 0 && last == 0) {
322
                        news_delete_all_articles(item);
323
                        procmsg_msg_list_free(alist);
324
                        alist = NULL;
325
                        item->cache_dirty = TRUE;
326
                } else {
327
                        alist = news_delete_old_articles(alist, item, first);
328
                        news_delete_expired_caches(alist, item);
329
                }
330
331
                alist = g_slist_concat(alist, newlist);
332
333
                item->last_num = last;
334
        } else {
335
                gint last;
336
337
                alist = news_get_uncached_articles
338
                        (session, item, 0, NULL, &last);
339
                news_delete_all_articles(item);
340
                item->last_num = last;
341
                item->cache_dirty = TRUE;
342
        }
343
344
        procmsg_set_flags(alist, item);
345
346
        alist = procmsg_sort_msg_list(alist, item->sort_key, item->sort_type);
347
348
        debug_print("cache_dirty: %d, mark_dirty: %d\n",
349
                    item->cache_dirty, item->mark_dirty);
350
351
        return alist;
352
}
353
354
static gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num)
355
{
356
        gchar *path, *filename;
357
        NNTPSession *session;
358
        gint ok;
359
360
        g_return_val_if_fail(folder != NULL, NULL);
361
        g_return_val_if_fail(item != NULL, NULL);
362
363
        path = folder_item_get_path(item);
364
        if (!is_dir_exist(path))
365
                make_dir_hier(path);
366
        filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
367
        g_free(path);
368
369
        if (is_file_exist(filename)) {
370
                debug_print(_("article %d has been already cached.\n"), num);
371
                return filename;
372
        }
373
374
        session = news_session_get(folder);
375
        if (!session) {
376
                g_free(filename);
377
                return NULL;
378
        }
379
380
        ok = news_select_group(session, item->path, NULL, NULL, NULL);
381
        if (ok != NN_SUCCESS) {
382
                if (ok == NN_SOCKET) {
383
                        session_destroy(SESSION(session));
384
                        REMOTE_FOLDER(folder)->session = NULL;
385
                }
386
                g_free(filename);
387
                return NULL;
388
        }
389
390
        debug_print(_("getting article %d...\n"), num);
391
        ok = news_get_article(NNTP_SESSION(REMOTE_FOLDER(folder)->session),
392
                              num, filename);
393
        if (ok != NN_SUCCESS) {
394
                g_warning(_("can't read article %d\n"), num);
395
                if (ok == NN_SOCKET) {
396
                        session_destroy(SESSION(session));
397
                        REMOTE_FOLDER(folder)->session = NULL;
398
                }
399
                g_free(filename);
400
                return NULL;
401
        }
402
403
        return filename;
404
}
405
406
static MsgInfo *news_get_msginfo(Folder *folder, FolderItem *item, gint num)
407
{
408
        MsgInfo *msginfo;
409
        MsgFlags flags = {0, 0};
410
        gchar *file;
411
412
        g_return_val_if_fail(folder != NULL, NULL);
413
        g_return_val_if_fail(item != NULL, NULL);
414
415
        file = news_fetch_msg(folder, item, num);
416
        if (!file) return NULL;
417
418
        msginfo = procheader_parse_file(file, flags, FALSE);
419
420
        g_free(file);
421
422
        return msginfo;
423
}
424
425
static gint news_close(Folder *folder, FolderItem *item)
426
{
427
        return 0;
428
}
429
430
static gint news_scan_group(Folder *folder, FolderItem *item)
431
{
432
        NNTPSession *session;
433
        gint num = 0, first = 0, last = 0;
434
        gint new = 0, unread = 0, total = 0;
435
        gint min = 0, max = 0;
436
        gint ok;
437
438
        g_return_val_if_fail(folder != NULL, -1);
439
        g_return_val_if_fail(item != NULL, -1);
440
441
        session = news_session_get(folder);
442
        if (!session) return -1;
443
444
        ok = news_select_group(session, item->path, &num, &first, &last);
445
        if (ok != NN_SUCCESS) {
446
                if (ok == NN_SOCKET) {
447
                        session_destroy(SESSION(session));
448
                        REMOTE_FOLDER(folder)->session = NULL;
449
                }
450
                return -1;
451
        }
452
453
        if (num == 0) {
454
                item->new = item->unread = item->total = item->last_num = 0;
455
                return 0;
456
        }
457
458
        procmsg_get_mark_sum(item, &new, &unread, &total, &min, &max, first);
459
460
        if (max < first || last < min)
461
                new = unread = total = num;
462
        else {
463
                if (min < first)
464
                        min = first;
465
466
                if (last < max)
467
                        max = last;
468
                else if (max < last) {
469
                        new += last - max;
470
                        unread += last - max;
471
                }
472
473
                if (new > num) new = num;
474
                if (unread > num) unread = num;
475
        }
476
477
        item->new = new;
478
        item->unread = unread;
479
        item->total = num;
480
        item->last_num = last;
481
482
        return 0;
483
}
484
485
static NewsGroupInfo *news_group_info_new(const gchar *name,
486
                                          gint first, gint last, gchar type)
487
{
488
        NewsGroupInfo *ginfo;
489
490
        ginfo = g_new(NewsGroupInfo, 1);
491
        ginfo->name = g_strdup(name);
492
        ginfo->first = first;
493
        ginfo->last = last;
494
        ginfo->type = type;
495
496
        return ginfo;
497
}
498
499
static void news_group_info_free(NewsGroupInfo *ginfo)
500
{
501
        g_free(ginfo->name);
502
        g_free(ginfo);
503
}
504
505
static gint news_group_info_compare(NewsGroupInfo *ginfo1,
506
                                    NewsGroupInfo *ginfo2)
507
{
508
        return g_ascii_strcasecmp(ginfo1->name, ginfo2->name);
509
}
510
511
GSList *news_get_group_list(Folder *folder)
512
{
513
        gchar *path, *filename;
514
        FILE *fp;
515
        GSList *list = NULL;
516
        GSList *last = NULL;
517
        gchar buf[NNTPBUFSIZE];
518
519
        g_return_val_if_fail(folder != NULL, NULL);
520
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
521
522
        path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
523
        if (!is_dir_exist(path))
524
                make_dir_hier(path);
525
        filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
526
        g_free(path);
527
528
        if ((fp = g_fopen(filename, "rb")) == NULL) {
529
                NNTPSession *session;
530
                gint ok;
531
532
                session = news_session_get(folder);
533
                if (!session) {
534
                        g_free(filename);
535
                        return NULL;
536
                }
537
538
                ok = nntp_list(session);
539
                if (ok != NN_SUCCESS) {
540
                        if (ok == NN_SOCKET) {
541
                                session_destroy(SESSION(session));
542
                                REMOTE_FOLDER(folder)->session = NULL;
543
                        }
544
                        g_free(filename);
545
                        return NULL;
546
                }
547
                if (recv_write_to_file(SESSION(session)->sock, filename) < 0) {
548
                        log_warning(_("can't retrieve newsgroup list\n"));
549
                        session_destroy(SESSION(session));
550
                        REMOTE_FOLDER(folder)->session = NULL;
551
                        g_free(filename);
552
                        return NULL;
553
                }
554
555
                if ((fp = g_fopen(filename, "rb")) == NULL) {
556
                        FILE_OP_ERROR(filename, "fopen");
557
                        g_free(filename);
558
                        return NULL;
559
                }
560
        }
561
562
        while (fgets(buf, sizeof(buf), fp) != NULL) {
563
                gchar *p = buf;
564
                gchar *name;
565
                gint last_num;
566
                gint first_num;
567
                gchar type;
568
                NewsGroupInfo *ginfo;
569
570
                p = strchr(p, ' ');
571
                if (!p) continue;
572
                *p = '\0';
573
                p++;
574
                name = buf;
575
576
                if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3)
577
                        continue;
578
579
                ginfo = news_group_info_new(name, first_num, last_num, type);
580
581
                if (!last)
582
                        last = list = g_slist_append(NULL, ginfo);
583
                else {
584
                        last = g_slist_append(last, ginfo);
585
                        last = last->next;
586
                }
587
        }
588
589
        fclose(fp);
590
        g_free(filename);
591
592
        list = g_slist_sort(list, (GCompareFunc)news_group_info_compare);
593
594
        return list;
595
}
596
597
void news_group_list_free(GSList *group_list)
598
{
599
        GSList *cur;
600
601
        if (!group_list) return;
602
603
        for (cur = group_list; cur != NULL; cur = cur->next)
604
                news_group_info_free((NewsGroupInfo *)cur->data);
605
        g_slist_free(group_list);
606
}
607
608
void news_remove_group_list_cache(Folder *folder)
609
{
610
        gchar *path, *filename;
611
612
        g_return_if_fail(folder != NULL);
613
        g_return_if_fail(FOLDER_TYPE(folder) == F_NEWS);
614
615
        path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
616
        filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
617
        g_free(path);
618
619
        if (is_file_exist(filename)) {
620
                if (remove(filename) < 0)
621
                        FILE_OP_ERROR(filename, "remove");
622
        }
623
        g_free(filename);
624
}
625
626
gint news_post(Folder *folder, const gchar *file)
627
{
628
        FILE *fp;
629
        gint ok;
630
631
        g_return_val_if_fail(folder != NULL, -1);
632
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1);
633
        g_return_val_if_fail(file != NULL, -1);
634
635
        if ((fp = g_fopen(file, "rb")) == NULL) {
636
                FILE_OP_ERROR(file, "fopen");
637
                return -1;
638
        }
639
640
        ok = news_post_stream(folder, fp);
641
642
        fclose(fp);
643
644
        return ok;
645
}
646
647
gint news_post_stream(Folder *folder, FILE *fp)
648
{
649
        NNTPSession *session;
650
        gint ok;
651
652
        g_return_val_if_fail(folder != NULL, -1);
653
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1);
654
        g_return_val_if_fail(fp != NULL, -1);
655
656
        session = news_session_get(folder);
657
        if (!session) return -1;
658
659
        ok = nntp_post(session, fp);
660
        if (ok != NN_SUCCESS) {
661
                log_warning(_("can't post article.\n"));
662
                if (ok == NN_SOCKET) {
663
                        session_destroy(SESSION(session));
664
                        REMOTE_FOLDER(folder)->session = NULL;
665
                }
666
                return -1;
667
        }
668
669
        return 0;
670
}
671
672
static gint news_get_article_cmd(NNTPSession *session, const gchar *cmd,
673
                                 gint num, gchar *filename)
674
{
675
        gchar *msgid;
676
        gint ok;
677
678
        ok = nntp_get_article(session, cmd, num, &msgid);
679
        if (ok != NN_SUCCESS)
680
                return ok;
681
682
        debug_print("Message-Id = %s, num = %d\n", msgid, num);
683
        g_free(msgid);
684
685
        ok = recv_write_to_file(SESSION(session)->sock, filename);
686
        if (ok < 0) {
687
                log_warning(_("can't retrieve article %d\n"), num);
688
                if (ok == -2)
689
                        return NN_SOCKET;
690
                else
691
                        return NN_IOERR;
692
        }
693
694
        return NN_SUCCESS;
695
}
696
697
static gint news_get_article(NNTPSession *session, gint num, gchar *filename)
698
{
699
        return news_get_article_cmd(session, "ARTICLE", num, filename);
700
}
701
702
#if 0
703
static gint news_get_header(NNTPSession *session, gint num, gchar *filename)
704
{
705
        return news_get_article_cmd(session, "HEAD", num, filename);
706
}
707
#endif
708
709
/**
710
 * news_select_group:
711
 * @session: Active NNTP session.
712
 * @group: Newsgroup name.
713
 * @num: Estimated number of articles.
714
 * @first: First article number.
715
 * @last: Last article number.
716
 *
717
 * Select newsgroup @group with the GROUP command if it is not already
718
 * selected in @session, or article numbers need to be returned.
719
 *
720
 * Return value: NNTP result code.
721
 **/
722
static gint news_select_group(NNTPSession *session, const gchar *group,
723
                              gint *num, gint *first, gint *last)
724
{
725
        gint ok;
726
        gint num_, first_, last_;
727
728
        if (!num || !first || !last) {
729
                if (session->group &&
730
                    g_ascii_strcasecmp(session->group, group) == 0)
731
                        return NN_SUCCESS;
732
                num = &num_;
733
                first = &first_;
734
                last = &last_;
735
        }
736
737
        g_free(session->group);
738
        session->group = NULL;
739
740
        ok = nntp_group(session, group, num, first, last);
741
        if (ok == NN_SUCCESS)
742
                session->group = g_strdup(group);
743
        else
744
                log_warning(_("can't select group: %s\n"), group);
745
746
        return ok;
747
}
748
749
static GSList *news_get_uncached_articles(NNTPSession *session,
750
                                          FolderItem *item, gint cache_last,
751
                                          gint *rfirst, gint *rlast)
752
{
753
        gint ok;
754
        gint num = 0, first = 0, last = 0, begin = 0, end = 0;
755
        gchar buf[NNTPBUFSIZE];
756
        GSList *newlist = NULL;
757
        GSList *llast = NULL;
758
        MsgInfo *msginfo;
759
        gint max_articles;
760
761
        if (rfirst) *rfirst = -1;
762
        if (rlast)  *rlast  = -1;
763
764
        g_return_val_if_fail(session != NULL, NULL);
765
        g_return_val_if_fail(item != NULL, NULL);
766
        g_return_val_if_fail(item->folder != NULL, NULL);
767
        g_return_val_if_fail(item->folder->account != NULL, NULL);
768
        g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, NULL);
769
770
        ok = news_select_group(session, item->path, &num, &first, &last);
771
        if (ok != NN_SUCCESS) {
772
                if (ok == NN_SOCKET) {
773
                        session_destroy(SESSION(session));
774
                        REMOTE_FOLDER(item->folder)->session = NULL;
775
                }
776
                return NULL;
777
        }
778
779
        /* calculate getting overview range */
780
        if (first > last) {
781
                log_warning(_("invalid article range: %d - %d\n"),
782
                            first, last);
783
                return NULL;
784
        }
785
786
        if (rfirst) *rfirst = first;
787
        if (rlast)  *rlast  = last;
788
789
        if (cache_last < first)
790
                begin = first;
791
        else if (last < cache_last)
792
                begin = first;
793
        else if (last == cache_last) {
794
                debug_print(_("no new articles.\n"));
795
                return NULL;
796
        } else
797
                begin = cache_last + 1;
798
        end = last;
799
800
        max_articles = item->folder->account->max_nntp_articles;
801
        if (max_articles > 0 && end - begin + 1 > max_articles)
802
                begin = end - max_articles + 1;
803
804
        log_message(_("getting xover %d - %d in %s...\n"),
805
                    begin, end, item->path);
806
        ok = nntp_xover(session, begin, end);
807
        if (ok != NN_SUCCESS) {
808
                log_warning(_("can't get xover\n"));
809
                if (ok == NN_SOCKET) {
810
                        session_destroy(SESSION(session));
811
                        REMOTE_FOLDER(item->folder)->session = NULL;
812
                }
813
                return NULL;
814
        }
815
816
        for (;;) {
817
                if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
818
                        log_warning(_("error occurred while getting xover.\n"));
819
                        session_destroy(SESSION(session));
820
                        REMOTE_FOLDER(item->folder)->session = NULL;
821
                        return newlist;
822
                }
823
824
                if (buf[0] == '.' && buf[1] == '\r') break;
825
826
                msginfo = news_parse_xover(buf);
827
                if (!msginfo) {
828
                        log_warning(_("invalid xover line: %s\n"), buf);
829
                        continue;
830
                }
831
832
                msginfo->folder = item;
833
                msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
834
                msginfo->flags.tmp_flags = MSG_NEWS;
835
                msginfo->newsgroups = g_strdup(item->path);
836
837
                if (!newlist)
838
                        llast = newlist = g_slist_append(newlist, msginfo);
839
                else {
840
                        llast = g_slist_append(llast, msginfo);
841
                        llast = llast->next;
842
                }
843
        }
844
845
        ok = nntp_xhdr(session, "to", begin, end);
846
        if (ok != NN_SUCCESS) {
847
                log_warning(_("can't get xhdr\n"));
848
                if (ok == NN_SOCKET) {
849
                        session_destroy(SESSION(session));
850
                        REMOTE_FOLDER(item->folder)->session = NULL;
851
                }
852
                return newlist;
853
        }
854
855
        llast = newlist;
856
857
        for (;;) {
858
                if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
859
                        log_warning(_("error occurred while getting xhdr.\n"));
860
                        session_destroy(SESSION(session));
861
                        REMOTE_FOLDER(item->folder)->session = NULL;
862
                        return newlist;
863
                }
864
865
                if (buf[0] == '.' && buf[1] == '\r') break;
866
                if (!llast) {
867
                        g_warning("llast == NULL\n");
868
                        continue;
869
                }
870
871
                msginfo = (MsgInfo *)llast->data;
872
                msginfo->to = news_parse_xhdr(buf, msginfo);
873
874
                llast = llast->next;
875
        }
876
877
        ok = nntp_xhdr(session, "cc", begin, end);
878
        if (ok != NN_SUCCESS) {
879
                log_warning(_("can't get xhdr\n"));
880
                if (ok == NN_SOCKET) {
881
                        session_destroy(SESSION(session));
882
                        REMOTE_FOLDER(item->folder)->session = NULL;
883
                }
884
                return newlist;
885
        }
886
887
        llast = newlist;
888
889
        for (;;) {
890
                if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
891
                        log_warning(_("error occurred while getting xhdr.\n"));
892
                        session_destroy(SESSION(session));
893
                        REMOTE_FOLDER(item->folder)->session = NULL;
894
                        return newlist;
895
                }
896
897
                if (buf[0] == '.' && buf[1] == '\r') break;
898
                if (!llast) {
899
                        g_warning("llast == NULL\n");
900
                        continue;
901
                }
902
903
                msginfo = (MsgInfo *)llast->data;
904
                msginfo->cc = news_parse_xhdr(buf, msginfo);
905
906
                llast = llast->next;
907
        }
908
909
        session_set_access_time(SESSION(session));
910
911
        return newlist;
912
}
913
914
#define PARSE_ONE_PARAM(p, srcp) \
915
{ \
916
        p = strchr(srcp, '\t'); \
917
        if (!p) return NULL; \
918
        else \
919
                *p++ = '\0'; \
920
}
921
922
static MsgInfo *news_parse_xover(const gchar *xover_str)
923
{
924
        MsgInfo *msginfo;
925
        gchar *subject, *sender, *size, *line, *date, *msgid, *ref, *tmp;
926
        gchar *p;
927
        gint num, size_int, line_int;
928
        gchar *xover_buf;
929
930
        Xstrdup_a(xover_buf, xover_str, return NULL);
931
932
        PARSE_ONE_PARAM(subject, xover_buf);
933
        PARSE_ONE_PARAM(sender, subject);
934
        PARSE_ONE_PARAM(date, sender);
935
        PARSE_ONE_PARAM(msgid, date);
936
        PARSE_ONE_PARAM(ref, msgid);
937
        PARSE_ONE_PARAM(size, ref);
938
        PARSE_ONE_PARAM(line, size);
939
940
        tmp = strchr(line, '\t');
941
        if (!tmp) tmp = strchr(line, '\r');
942
        if (!tmp) tmp = strchr(line, '\n');
943
        if (tmp) *tmp = '\0';
944
945
        num = atoi(xover_str);
946
        size_int = atoi(size);
947
        line_int = atoi(line);
948
949
        /* set MsgInfo */
950
        msginfo = g_new0(MsgInfo, 1);
951
        msginfo->msgnum = num;
952
        msginfo->size = size_int;
953
954
        msginfo->date = g_strdup(date);
955
        msginfo->date_t = procheader_date_parse(NULL, date, 0);
956
957
        msginfo->from = conv_unmime_header(sender, NULL);
958
        msginfo->fromname = procheader_get_fromname(msginfo->from);
959
960
        msginfo->subject = conv_unmime_header(subject, NULL);
961
962
        extract_parenthesis(msgid, '<', '>');
963
        remove_space(msgid);
964
        if (*msgid != '\0')
965
                msginfo->msgid = g_strdup(msgid);
966
967
        eliminate_parenthesis(ref, '(', ')');
968
        if ((p = strrchr(ref, '<')) != NULL) {
969
                extract_parenthesis(p, '<', '>');
970
                remove_space(p);
971
                if (*p != '\0')
972
                        msginfo->inreplyto = g_strdup(p);
973
        }
974
975
        return msginfo;
976
}
977
978
static gchar *news_parse_xhdr(const gchar *xhdr_str, MsgInfo *msginfo)
979
{
980
        gchar *p;
981
        gchar *tmp;
982
        gint num;
983
984
        p = strchr(xhdr_str, ' ');
985
        if (!p)
986
                return NULL;
987
        else
988
                p++;
989
990
        num = atoi(xhdr_str);
991
        if (msginfo->msgnum != num) return NULL;
992
993
        tmp = strchr(p, '\r');
994
        if (!tmp) tmp = strchr(p, '\n');
995
996
        if (tmp)
997
                return g_strndup(p, tmp - p);
998
        else
999
                return g_strdup(p);
1000
}
1001
1002
static GSList *news_delete_old_articles(GSList *alist, FolderItem *item,
1003
                                        gint first)
1004
{
1005
        GSList *cur, *next;
1006
        MsgInfo *msginfo;
1007
        gchar *dir;
1008
1009
        g_return_val_if_fail(item != NULL, alist);
1010
        g_return_val_if_fail(item->folder != NULL, alist);
1011
        g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, alist);
1012
1013
        if (first < 2) return alist;
1014
1015
        debug_print("Deleting cached articles 1 - %d ...\n", first - 1);
1016
1017
        dir = folder_item_get_path(item);
1018
        remove_numbered_files(dir, 1, first - 1);
1019
        g_free(dir);
1020
1021
        for (cur = alist; cur != NULL; ) {
1022
                next = cur->next;
1023
1024
                msginfo = (MsgInfo *)cur->data;
1025
                if (msginfo && msginfo->msgnum < first) {
1026
                        procmsg_msginfo_free(msginfo);
1027
                        alist = g_slist_remove(alist, msginfo);
1028
                        item->cache_dirty = TRUE;
1029
                }
1030
1031
                cur = next;
1032
        }
1033
1034
        return alist;
1035
}
1036
1037
static void news_delete_all_articles(FolderItem *item)
1038
{
1039
        gchar *dir;
1040
1041
        g_return_if_fail(item != NULL);
1042
        g_return_if_fail(item->folder != NULL);
1043
        g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS);
1044
1045
        debug_print("Deleting all cached articles...\n");
1046
1047
        dir = folder_item_get_path(item);
1048
        remove_all_numbered_files(dir);
1049
        g_free(dir);
1050
}
1051
1052
static void news_delete_expired_caches(GSList *alist, FolderItem *item)
1053
{
1054
        gchar *dir;
1055
1056
        g_return_if_fail(item != NULL);
1057
        g_return_if_fail(item->folder != NULL);
1058
        g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS);
1059
1060
        debug_print("Deleting expired cached articles...\n");
1061
1062
        dir = folder_item_get_path(item);
1063
        remove_expired_files(dir, 24 * 7);
1064
        g_free(dir);
1065
}