Statistics
| Revision:

root / libsylph / news.c @ 3192

History | View | Annotate | Download (25.1 KB)

1
/*
2
 * LibSylph -- E-Mail client library
3
 * Copyright (C) 1999-2012 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 <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
#if USE_SSL
48
#  include "ssl.h"
49
#endif
50
#include "socks.h"
51

    
52
#define NNTP_PORT        119
53
#if USE_SSL
54
#define NNTPS_PORT        563
55
#endif
56

    
57
static void news_folder_init                 (Folder        *folder,
58
                                          const gchar        *name,
59
                                          const gchar        *path);
60

    
61
static Folder        *news_folder_new        (const gchar        *name,
62
                                         const gchar        *folder);
63
static void         news_folder_destroy        (Folder                *folder);
64

    
65
static GSList *news_get_article_list        (Folder                *folder,
66
                                         FolderItem        *item,
67
                                         gboolean         use_cache);
68
static gchar *news_fetch_msg                (Folder                *folder,
69
                                         FolderItem        *item,
70
                                         gint                 num);
71
static MsgInfo *news_get_msginfo        (Folder                *folder,
72
                                         FolderItem        *item,
73
                                         gint                 num);
74

    
75
static gint news_close                        (Folder                *folder,
76
                                         FolderItem        *item);
77

    
78
static gint news_scan_group                (Folder                *folder,
79
                                         FolderItem        *item);
80

    
81
#if USE_SSL
82
static Session *news_session_new         (const gchar        *server,
83
                                          gushort         port,
84
                                          SocksInfo        *socks_info,
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
                                          SocksInfo        *socks_info,
92
                                          const gchar        *userid,
93
                                          const gchar        *passwd);
94
#endif
95

    
96
static gint news_get_article_cmd         (NNTPSession        *session,
97
                                          const gchar        *cmd,
98
                                          gint                 num,
99
                                          gchar                *filename);
100
static gint news_get_article                 (NNTPSession        *session,
101
                                          gint                 num,
102
                                          gchar                *filename);
103
#if 0
104
static gint news_get_header                 (NNTPSession        *session,
105
                                          gint                 num,
106
                                          gchar                *filename);
107
#endif
108

    
109
static gint news_select_group                 (NNTPSession        *session,
110
                                          const gchar        *group,
111
                                          gint                *num,
112
                                          gint                *first,
113
                                          gint                *last);
114
static GSList *news_get_uncached_articles(NNTPSession        *session,
115
                                          FolderItem        *item,
116
                                          gint                 cache_last,
117
                                          gint                *rfirst,
118
                                          gint                *rlast);
119
static MsgInfo *news_parse_xover         (const gchar        *xover_str);
120
static gchar *news_parse_xhdr                 (const gchar        *xhdr_str,
121
                                          MsgInfo        *msginfo);
122
static GSList *news_delete_old_articles         (GSList        *alist,
123
                                          FolderItem        *item,
124
                                          gint                 first);
125
static void news_delete_all_articles         (FolderItem        *item);
126
static void news_delete_expired_caches         (GSList        *alist,
127
                                          FolderItem        *item);
128

    
129
static FolderClass news_class =
130
{
131
        F_NEWS,
132

    
133
        news_folder_new,
134
        news_folder_destroy,
135

    
136
        NULL,
137
        NULL,
138

    
139
        news_get_article_list,
140
        NULL,
141
        news_fetch_msg,
142
        news_get_msginfo,
143
        NULL,
144
        NULL,
145
        NULL,
146
        NULL,
147
        NULL,
148
        NULL,
149
        NULL,
150
        NULL,
151
        NULL,
152
        NULL,
153
        NULL,
154
        NULL,
155
        news_close,
156
        news_scan_group,
157

    
158
        NULL,
159
        NULL,
160
        NULL,
161
        NULL
162
};
163

    
164

    
165
FolderClass *news_get_class(void)
166
{
167
        return &news_class;
168
}
169

    
170
static Folder *news_folder_new(const gchar *name, const gchar *path)
171
{
172
        Folder *folder;
173

    
174
        folder = (Folder *)g_new0(NewsFolder, 1);
175
        news_folder_init(folder, name, path);
176

    
177
        return folder;
178
}
179

    
180
static void news_folder_destroy(Folder *folder)
181
{
182
        if (REMOTE_FOLDER(folder)->remove_cache_on_destroy) {
183
                gchar *dir;
184
                gchar *server;
185

    
186
                dir = folder_get_path(folder);
187
                if (is_dir_exist(dir))
188
                        remove_dir_recursive(dir);
189
                g_free(dir);
190

    
191
                server = uriencode_for_filename(folder->account->nntp_server);
192
                dir = g_strconcat(get_news_cache_dir(), G_DIR_SEPARATOR_S,
193
                                  server, NULL);
194
                if (is_dir_exist(dir))
195
                        g_rmdir(dir);
196
                g_free(dir);
197
                g_free(server);
198
        }
199

    
200
        folder_remote_folder_destroy(REMOTE_FOLDER(folder));
201
}
202

    
203
static void news_folder_init(Folder *folder, const gchar *name,
204
                             const gchar *path)
205
{
206
        folder->klass = news_get_class();
207
        folder_remote_folder_init(folder, name, path);
208
}
209

    
210
#if USE_SSL
211
static Session *news_session_new(const gchar *server, gushort port,
212
                                 SocksInfo *socks_info,
213
                                 const gchar *userid, const gchar *passwd,
214
                                 SSLType ssl_type)
215
#else
216
static Session *news_session_new(const gchar *server, gushort port,
217
                                 SocksInfo *socks_info,
218
                                 const gchar *userid, const gchar *passwd)
219
#endif
220
{
221
        gchar buf[NNTPBUFSIZE];
222
        Session *session;
223

    
224
        g_return_val_if_fail(server != NULL, NULL);
225

    
226
        log_message(_("creating NNTP connection to %s:%d ...\n"), server, port);
227

    
228
#if USE_SSL
229
        session = nntp_session_new_full(server, port, socks_info, buf, userid, passwd, ssl_type);
230
#else
231
        session = nntp_session_new_full(server, port, socks_info, buf, userid, passwd);
232
#endif
233

    
234
        return session;
235
}
236

    
237
static Session *news_session_new_for_folder(Folder *folder)
238
{
239
        Session *session;
240
        PrefsAccount *ac;
241
        SocksInfo *socks_info = NULL;
242
        const gchar *userid = NULL;
243
        gchar *passwd = NULL;
244
        gushort port;
245

    
246
        g_return_val_if_fail(folder != NULL, NULL);
247
        g_return_val_if_fail(folder->account != NULL, NULL);
248

    
249
        ac = folder->account;
250
        if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
251
                userid = ac->userid;
252
                if (ac->passwd && ac->passwd[0])
253
                        passwd = g_strdup(ac->passwd);
254
                else
255
                        passwd = input_query_password(ac->nntp_server, userid);
256
        }
257

    
258
        if (ac->use_socks && ac->use_socks_for_recv && ac->proxy_host) {
259
                socks_info = socks_info_new(ac->socks_type, ac->proxy_host, ac->proxy_port, ac->use_proxy_auth ? ac->proxy_name : NULL, ac->use_proxy_auth ? ac->proxy_pass : NULL);
260
        }
261

    
262
#if USE_SSL
263
        port = ac->set_nntpport ? ac->nntpport
264
                : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT;
265
        session = news_session_new(ac->nntp_server, port, socks_info,
266
                                   userid, passwd, ac->ssl_nntp);
267
#else
268
        port = ac->set_nntpport ? ac->nntpport : NNTP_PORT;
269
        session = news_session_new(ac->nntp_server, port, socks_info,
270
                                   userid, passwd);
271
#endif
272

    
273
        if (socks_info)
274
                socks_info_free(socks_info);
275

    
276
        g_free(passwd);
277

    
278
        return session;
279
}
280

    
281
static NNTPSession *news_session_get(Folder *folder)
282
{
283
        RemoteFolder *rfolder = REMOTE_FOLDER(folder);
284

    
285
        g_return_val_if_fail(folder != NULL, NULL);
286
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
287
        g_return_val_if_fail(folder->account != NULL, NULL);
288

    
289
        if (!prefs_common.online_mode)
290
                return NULL;
291

    
292
        if (!rfolder->session) {
293
                rfolder->session = news_session_new_for_folder(folder);
294
                return NNTP_SESSION(rfolder->session);
295
        }
296

    
297
        if (time(NULL) - rfolder->session->last_access_time <
298
                SESSION_TIMEOUT_INTERVAL) {
299
                return NNTP_SESSION(rfolder->session);
300
        }
301

    
302
        if (nntp_mode(NNTP_SESSION(rfolder->session), FALSE)
303
            != NN_SUCCESS) {
304
                log_warning(_("NNTP connection to %s:%d has been"
305
                              " disconnected. Reconnecting...\n"),
306
                            folder->account->nntp_server,
307
                            folder->account->set_nntpport ?
308
                            folder->account->nntpport : NNTP_PORT);
309
                session_destroy(rfolder->session);
310
                rfolder->session = news_session_new_for_folder(folder);
311
        }
312

    
313
        if (rfolder->session)
314
                session_set_access_time(rfolder->session);
315

    
316
        return NNTP_SESSION(rfolder->session);
317
}
318

    
319
static GSList *news_get_article_list(Folder *folder, FolderItem *item,
320
                                     gboolean use_cache)
321
{
322
        GSList *alist;
323
        NNTPSession *session;
324

    
325
        g_return_val_if_fail(folder != NULL, NULL);
326
        g_return_val_if_fail(item != NULL, NULL);
327
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
328

    
329
        session = news_session_get(folder);
330

    
331
        if (!session) {
332
                alist = procmsg_read_cache(item, FALSE);
333
                item->last_num = procmsg_get_last_num_in_msg_list(alist);
334
        } else if (use_cache) {
335
                GSList *newlist;
336
                gint cache_last;
337
                gint first, last;
338

    
339
                alist = procmsg_read_cache(item, FALSE);
340

    
341
                cache_last = procmsg_get_last_num_in_msg_list(alist);
342
                newlist = news_get_uncached_articles
343
                        (session, item, cache_last, &first, &last);
344
                if (newlist)
345
                        item->cache_dirty = TRUE;
346
                if (first == 0 && last == 0) {
347
                        news_delete_all_articles(item);
348
                        procmsg_msg_list_free(alist);
349
                        alist = NULL;
350
                        item->cache_dirty = TRUE;
351
                } else {
352
                        alist = news_delete_old_articles(alist, item, first);
353
                        news_delete_expired_caches(alist, item);
354
                }
355

    
356
                alist = g_slist_concat(alist, newlist);
357

    
358
                item->last_num = last;
359
        } else {
360
                gint last;
361

    
362
                alist = news_get_uncached_articles
363
                        (session, item, 0, NULL, &last);
364
                news_delete_all_articles(item);
365
                item->last_num = last;
366
                item->cache_dirty = TRUE;
367
        }
368

    
369
        procmsg_set_flags(alist, item);
370

    
371
        alist = procmsg_sort_msg_list(alist, item->sort_key, item->sort_type);
372

    
373
        if (item->mark_queue)
374
                item->mark_dirty = TRUE;
375

    
376
        debug_print("cache_dirty: %d, mark_dirty: %d\n",
377
                    item->cache_dirty, item->mark_dirty);
378

    
379
        if (!item->opened) {
380
                if (item->cache_dirty)
381
                        procmsg_write_cache_list(item, alist);
382
                if (item->mark_dirty)
383
                        procmsg_write_flags_list(item, alist);
384
        }
385

    
386
        return alist;
387
}
388

    
389
static gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num)
390
{
391
        gchar *path, *filename;
392
        NNTPSession *session;
393
        gchar nstr[16];
394
        gint ok;
395

    
396
        g_return_val_if_fail(folder != NULL, NULL);
397
        g_return_val_if_fail(item != NULL, NULL);
398

    
399
        path = folder_item_get_path(item);
400
        if (!is_dir_exist(path))
401
                make_dir_hier(path);
402
        filename = g_strconcat(path, G_DIR_SEPARATOR_S, utos_buf(nstr, num),
403
                               NULL);
404
        g_free(path);
405

    
406
        if (is_file_exist(filename) && get_file_size(filename) > 0) {
407
                debug_print(_("article %d has been already cached.\n"), num);
408
                return filename;
409
        }
410

    
411
        session = news_session_get(folder);
412
        if (!session) {
413
                g_free(filename);
414
                return NULL;
415
        }
416

    
417
        ok = news_select_group(session, item->path, NULL, NULL, NULL);
418
        if (ok != NN_SUCCESS) {
419
                if (ok == NN_SOCKET) {
420
                        session_destroy(SESSION(session));
421
                        REMOTE_FOLDER(folder)->session = NULL;
422
                }
423
                g_free(filename);
424
                return NULL;
425
        }
426

    
427
        debug_print(_("getting article %d...\n"), num);
428
        ok = news_get_article(NNTP_SESSION(REMOTE_FOLDER(folder)->session),
429
                              num, filename);
430
        if (ok != NN_SUCCESS) {
431
                g_warning(_("can't read article %d\n"), num);
432
                if (ok == NN_SOCKET) {
433
                        session_destroy(SESSION(session));
434
                        REMOTE_FOLDER(folder)->session = NULL;
435
                }
436
                g_free(filename);
437
                return NULL;
438
        }
439

    
440
        return filename;
441
}
442

    
443
static MsgInfo *news_get_msginfo(Folder *folder, FolderItem *item, gint num)
444
{
445
        MsgInfo *msginfo;
446
        MsgFlags flags = {0, 0};
447
        gchar *file;
448

    
449
        g_return_val_if_fail(folder != NULL, NULL);
450
        g_return_val_if_fail(item != NULL, NULL);
451

    
452
        file = news_fetch_msg(folder, item, num);
453
        if (!file) return NULL;
454

    
455
        msginfo = procheader_parse_file(file, flags, FALSE);
456

    
457
        g_free(file);
458

    
459
        return msginfo;
460
}
461

    
462
static gint news_close(Folder *folder, FolderItem *item)
463
{
464
        return 0;
465
}
466

    
467
static gint news_scan_group(Folder *folder, FolderItem *item)
468
{
469
        NNTPSession *session;
470
        gint num = 0, first = 0, last = 0;
471
        gint new = 0, unread = 0, total = 0;
472
        gint min = 0, max = 0;
473
        gint ok;
474

    
475
        g_return_val_if_fail(folder != NULL, -1);
476
        g_return_val_if_fail(item != NULL, -1);
477

    
478
        session = news_session_get(folder);
479
        if (!session) return -1;
480

    
481
        ok = news_select_group(session, item->path, &num, &first, &last);
482
        if (ok != NN_SUCCESS) {
483
                if (ok == NN_SOCKET) {
484
                        session_destroy(SESSION(session));
485
                        REMOTE_FOLDER(folder)->session = NULL;
486
                }
487
                return -1;
488
        }
489

    
490
        if (num == 0) {
491
                item->new = item->unread = item->total = item->last_num = 0;
492
                return 0;
493
        }
494

    
495
        procmsg_get_mark_sum(item, &new, &unread, &total, &min, &max, first);
496

    
497
        if (max < first || last < min)
498
                new = unread = total = num;
499
        else {
500
                if (min < first)
501
                        min = first;
502

    
503
                if (last < max)
504
                        max = last;
505
                else if (max < last) {
506
                        new += last - max;
507
                        unread += last - max;
508
                }
509

    
510
                if (new > num) new = num;
511
                if (unread > num) unread = num;
512
        }
513

    
514
        item->new = new;
515
        item->unread = unread;
516
        item->total = num;
517
        item->last_num = last;
518

    
519
        return 0;
520
}
521

    
522
static NewsGroupInfo *news_group_info_new(const gchar *name,
523
                                          gint first, gint last, gchar type)
524
{
525
        NewsGroupInfo *ginfo;
526

    
527
        ginfo = g_new(NewsGroupInfo, 1);
528
        ginfo->name = g_strdup(name);
529
        ginfo->first = first;
530
        ginfo->last = last;
531
        ginfo->type = type;
532
        ginfo->subscribed = FALSE;
533

    
534
        return ginfo;
535
}
536

    
537
static void news_group_info_free(NewsGroupInfo *ginfo)
538
{
539
        g_free(ginfo->name);
540
        g_free(ginfo);
541
}
542

    
543
static gint news_group_info_compare(NewsGroupInfo *ginfo1,
544
                                    NewsGroupInfo *ginfo2)
545
{
546
        return g_ascii_strcasecmp(ginfo1->name, ginfo2->name);
547
}
548

    
549
GSList *news_get_group_list(Folder *folder)
550
{
551
        gchar *path, *filename;
552
        FILE *fp;
553
        GSList *list = NULL;
554
        GSList *last = NULL;
555
        gchar buf[NNTPBUFSIZE];
556

    
557
        g_return_val_if_fail(folder != NULL, NULL);
558
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
559

    
560
        path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
561
        if (!is_dir_exist(path))
562
                make_dir_hier(path);
563
        filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
564
        g_free(path);
565

    
566
        if ((fp = g_fopen(filename, "rb")) == NULL) {
567
                NNTPSession *session;
568
                gint ok;
569

    
570
                session = news_session_get(folder);
571
                if (!session) {
572
                        g_free(filename);
573
                        return NULL;
574
                }
575

    
576
                ok = nntp_list(session);
577
                if (ok != NN_SUCCESS) {
578
                        if (ok == NN_SOCKET) {
579
                                session_destroy(SESSION(session));
580
                                REMOTE_FOLDER(folder)->session = NULL;
581
                        }
582
                        g_free(filename);
583
                        return NULL;
584
                }
585
                if (recv_write_to_file(SESSION(session)->sock, filename) < 0) {
586
                        log_warning("can't retrieve newsgroup list\n");
587
                        session_destroy(SESSION(session));
588
                        REMOTE_FOLDER(folder)->session = NULL;
589
                        g_free(filename);
590
                        return NULL;
591
                }
592

    
593
                if ((fp = g_fopen(filename, "rb")) == NULL) {
594
                        FILE_OP_ERROR(filename, "fopen");
595
                        g_free(filename);
596
                        return NULL;
597
                }
598
        }
599

    
600
        while (fgets(buf, sizeof(buf), fp) != NULL) {
601
                gchar *p = buf;
602
                gchar *name;
603
                gint last_num;
604
                gint first_num;
605
                gchar type;
606
                NewsGroupInfo *ginfo;
607

    
608
                p = strchr(p, ' ');
609
                if (!p) {
610
                        strretchomp(buf);
611
                        log_warning("invalid LIST response: %s\n", buf);
612
                        continue;
613
                }
614
                *p = '\0';
615
                p++;
616
                name = buf;
617

    
618
                if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3) {
619
                        strretchomp(p);
620
                        log_warning("invalid LIST response: %s %s\n", name, p);
621
                        continue;
622
                }
623

    
624
                ginfo = news_group_info_new(name, first_num, last_num, type);
625

    
626
                if (!last)
627
                        last = list = g_slist_append(NULL, ginfo);
628
                else {
629
                        last = g_slist_append(last, ginfo);
630
                        last = last->next;
631
                }
632
        }
633

    
634
        fclose(fp);
635
        g_free(filename);
636

    
637
        list = g_slist_sort(list, (GCompareFunc)news_group_info_compare);
638

    
639
        return list;
640
}
641

    
642
void news_group_list_free(GSList *group_list)
643
{
644
        GSList *cur;
645

    
646
        if (!group_list) return;
647

    
648
        for (cur = group_list; cur != NULL; cur = cur->next)
649
                news_group_info_free((NewsGroupInfo *)cur->data);
650
        g_slist_free(group_list);
651
}
652

    
653
void news_remove_group_list_cache(Folder *folder)
654
{
655
        gchar *path, *filename;
656

    
657
        g_return_if_fail(folder != NULL);
658
        g_return_if_fail(FOLDER_TYPE(folder) == F_NEWS);
659

    
660
        path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
661
        filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
662
        g_free(path);
663

    
664
        if (is_file_exist(filename)) {
665
                if (remove(filename) < 0)
666
                        FILE_OP_ERROR(filename, "remove");
667
        }
668
        g_free(filename);
669
}
670

    
671
gint news_post(Folder *folder, const gchar *file)
672
{
673
        FILE *fp;
674
        gint ok;
675

    
676
        g_return_val_if_fail(folder != NULL, -1);
677
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1);
678
        g_return_val_if_fail(file != NULL, -1);
679

    
680
        if ((fp = g_fopen(file, "rb")) == NULL) {
681
                FILE_OP_ERROR(file, "fopen");
682
                return -1;
683
        }
684

    
685
        ok = news_post_stream(folder, fp);
686

    
687
        fclose(fp);
688

    
689
        return ok;
690
}
691

    
692
gint news_post_stream(Folder *folder, FILE *fp)
693
{
694
        NNTPSession *session;
695
        gint ok;
696

    
697
        g_return_val_if_fail(folder != NULL, -1);
698
        g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1);
699
        g_return_val_if_fail(fp != NULL, -1);
700

    
701
        session = news_session_get(folder);
702
        if (!session) return -1;
703

    
704
        ok = nntp_post(session, fp);
705
        if (ok != NN_SUCCESS) {
706
                log_warning(_("can't post article.\n"));
707
                if (ok == NN_SOCKET) {
708
                        session_destroy(SESSION(session));
709
                        REMOTE_FOLDER(folder)->session = NULL;
710
                }
711
                return -1;
712
        }
713

    
714
        return 0;
715
}
716

    
717
static gint news_get_article_cmd(NNTPSession *session, const gchar *cmd,
718
                                 gint num, gchar *filename)
719
{
720
        gchar *msgid;
721
        gint ok;
722

    
723
        ok = nntp_get_article(session, cmd, num, &msgid);
724
        if (ok != NN_SUCCESS)
725
                return ok;
726

    
727
        debug_print("Message-Id = %s, num = %d\n", msgid, num);
728
        g_free(msgid);
729

    
730
        ok = recv_write_to_file(SESSION(session)->sock, filename);
731
        if (ok < 0) {
732
                log_warning(_("can't retrieve article %d\n"), num);
733
                if (ok == -2)
734
                        return NN_SOCKET;
735
                else
736
                        return NN_IOERR;
737
        }
738

    
739
        return NN_SUCCESS;
740
}
741

    
742
static gint news_get_article(NNTPSession *session, gint num, gchar *filename)
743
{
744
        return news_get_article_cmd(session, "ARTICLE", num, filename);
745
}
746

    
747
#if 0
748
static gint news_get_header(NNTPSession *session, gint num, gchar *filename)
749
{
750
        return news_get_article_cmd(session, "HEAD", num, filename);
751
}
752
#endif
753

    
754
/**
755
 * news_select_group:
756
 * @session: Active NNTP session.
757
 * @group: Newsgroup name.
758
 * @num: Estimated number of articles.
759
 * @first: First article number.
760
 * @last: Last article number.
761
 *
762
 * Select newsgroup @group with the GROUP command if it is not already
763
 * selected in @session, or article numbers need to be returned.
764
 *
765
 * Return value: NNTP result code.
766
 **/
767
static gint news_select_group(NNTPSession *session, const gchar *group,
768
                              gint *num, gint *first, gint *last)
769
{
770
        gint ok;
771
        gint num_, first_, last_;
772

    
773
        if (!num || !first || !last) {
774
                if (session->group &&
775
                    g_ascii_strcasecmp(session->group, group) == 0)
776
                        return NN_SUCCESS;
777
                num = &num_;
778
                first = &first_;
779
                last = &last_;
780
        }
781

    
782
        g_free(session->group);
783
        session->group = NULL;
784

    
785
        ok = nntp_group(session, group, num, first, last);
786
        if (ok == NN_SUCCESS)
787
                session->group = g_strdup(group);
788
        else
789
                log_warning(_("can't select group: %s\n"), group);
790

    
791
        return ok;
792
}
793

    
794
static GSList *news_get_uncached_articles(NNTPSession *session,
795
                                          FolderItem *item, gint cache_last,
796
                                          gint *rfirst, gint *rlast)
797
{
798
        gint ok;
799
        gint num = 0, first = 0, last = 0, begin = 0, end = 0;
800
        gchar buf[NNTPBUFSIZE];
801
        GSList *newlist = NULL;
802
        GSList *llast = NULL;
803
        MsgInfo *msginfo;
804
        gint max_articles;
805

    
806
        if (rfirst) *rfirst = -1;
807
        if (rlast)  *rlast  = -1;
808

    
809
        g_return_val_if_fail(session != NULL, NULL);
810
        g_return_val_if_fail(item != NULL, NULL);
811
        g_return_val_if_fail(item->folder != NULL, NULL);
812
        g_return_val_if_fail(item->folder->account != NULL, NULL);
813
        g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, NULL);
814

    
815
        ok = news_select_group(session, item->path, &num, &first, &last);
816
        if (ok != NN_SUCCESS) {
817
                if (ok == NN_SOCKET) {
818
                        session_destroy(SESSION(session));
819
                        REMOTE_FOLDER(item->folder)->session = NULL;
820
                }
821
                return NULL;
822
        }
823

    
824
        /* calculate getting overview range */
825
        if (first > last) {
826
                log_warning(_("invalid article range: %d - %d\n"),
827
                            first, last);
828
                return NULL;
829
        }
830

    
831
        if (rfirst) *rfirst = first;
832
        if (rlast)  *rlast  = last;
833

    
834
        if (cache_last < first)
835
                begin = first;
836
        else if (last < cache_last)
837
                begin = first;
838
        else if (last == cache_last) {
839
                debug_print(_("no new articles.\n"));
840
                return NULL;
841
        } else
842
                begin = cache_last + 1;
843
        end = last;
844

    
845
        max_articles = item->folder->account->max_nntp_articles;
846
        if (max_articles > 0 && end - begin + 1 > max_articles)
847
                begin = end - max_articles + 1;
848

    
849
        log_message(_("getting xover %d - %d in %s...\n"),
850
                    begin, end, item->path);
851
        ok = nntp_xover(session, begin, end);
852
        if (ok != NN_SUCCESS) {
853
                log_warning(_("can't get xover\n"));
854
                if (ok == NN_SOCKET) {
855
                        session_destroy(SESSION(session));
856
                        REMOTE_FOLDER(item->folder)->session = NULL;
857
                }
858
                return NULL;
859
        }
860

    
861
        for (;;) {
862
                if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
863
                        log_warning(_("error occurred while getting xover.\n"));
864
                        session_destroy(SESSION(session));
865
                        REMOTE_FOLDER(item->folder)->session = NULL;
866
                        return newlist;
867
                }
868

    
869
                if (buf[0] == '.' && buf[1] == '\r') break;
870

    
871
                msginfo = news_parse_xover(buf);
872
                if (!msginfo) {
873
                        log_warning(_("invalid xover line: %s\n"), buf);
874
                        continue;
875
                }
876

    
877
                msginfo->folder = item;
878
                msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
879
                msginfo->flags.tmp_flags = MSG_NEWS;
880
                msginfo->newsgroups = g_strdup(item->path);
881

    
882
                if (!newlist)
883
                        llast = newlist = g_slist_append(newlist, msginfo);
884
                else {
885
                        llast = g_slist_append(llast, msginfo);
886
                        llast = llast->next;
887
                }
888
        }
889

    
890
        ok = nntp_xhdr(session, "to", begin, end);
891
        if (ok != NN_SUCCESS) {
892
                log_warning(_("can't get xhdr\n"));
893
                if (ok == NN_SOCKET) {
894
                        session_destroy(SESSION(session));
895
                        REMOTE_FOLDER(item->folder)->session = NULL;
896
                }
897
                return newlist;
898
        }
899

    
900
        llast = newlist;
901

    
902
        for (;;) {
903
                if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
904
                        log_warning(_("error occurred while getting xhdr.\n"));
905
                        session_destroy(SESSION(session));
906
                        REMOTE_FOLDER(item->folder)->session = NULL;
907
                        return newlist;
908
                }
909

    
910
                if (buf[0] == '.' && buf[1] == '\r') break;
911
                if (!llast) {
912
                        g_warning("llast == NULL\n");
913
                        continue;
914
                }
915

    
916
                msginfo = (MsgInfo *)llast->data;
917
                msginfo->to = news_parse_xhdr(buf, msginfo);
918

    
919
                llast = llast->next;
920
        }
921

    
922
        ok = nntp_xhdr(session, "cc", begin, end);
923
        if (ok != NN_SUCCESS) {
924
                log_warning(_("can't get xhdr\n"));
925
                if (ok == NN_SOCKET) {
926
                        session_destroy(SESSION(session));
927
                        REMOTE_FOLDER(item->folder)->session = NULL;
928
                }
929
                return newlist;
930
        }
931

    
932
        llast = newlist;
933

    
934
        for (;;) {
935
                if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
936
                        log_warning(_("error occurred while getting xhdr.\n"));
937
                        session_destroy(SESSION(session));
938
                        REMOTE_FOLDER(item->folder)->session = NULL;
939
                        return newlist;
940
                }
941

    
942
                if (buf[0] == '.' && buf[1] == '\r') break;
943
                if (!llast) {
944
                        g_warning("llast == NULL\n");
945
                        continue;
946
                }
947

    
948
                msginfo = (MsgInfo *)llast->data;
949
                msginfo->cc = news_parse_xhdr(buf, msginfo);
950

    
951
                llast = llast->next;
952
        }
953

    
954
        session_set_access_time(SESSION(session));
955

    
956
        return newlist;
957
}
958

    
959
#define PARSE_ONE_PARAM(p, srcp) \
960
{ \
961
        p = strchr(srcp, '\t'); \
962
        if (!p) return NULL; \
963
        else \
964
                *p++ = '\0'; \
965
}
966

    
967
static MsgInfo *news_parse_xover(const gchar *xover_str)
968
{
969
        MsgInfo *msginfo;
970
        gchar *subject, *sender, *size, *line, *date, *msgid, *ref, *tmp;
971
        gchar *p;
972
        gint num, size_int, line_int;
973
        gchar *xover_buf;
974

    
975
        Xstrdup_a(xover_buf, xover_str, return NULL);
976

    
977
        PARSE_ONE_PARAM(subject, xover_buf);
978
        PARSE_ONE_PARAM(sender, subject);
979
        PARSE_ONE_PARAM(date, sender);
980
        PARSE_ONE_PARAM(msgid, date);
981
        PARSE_ONE_PARAM(ref, msgid);
982
        PARSE_ONE_PARAM(size, ref);
983
        PARSE_ONE_PARAM(line, size);
984

    
985
        tmp = strchr(line, '\t');
986
        if (!tmp) tmp = strchr(line, '\r');
987
        if (!tmp) tmp = strchr(line, '\n');
988
        if (tmp) *tmp = '\0';
989

    
990
        num = atoi(xover_str);
991
        size_int = atoi(size);
992
        line_int = atoi(line);
993

    
994
        /* set MsgInfo */
995
        msginfo = g_new0(MsgInfo, 1);
996
        msginfo->msgnum = num;
997
        msginfo->size = size_int;
998

    
999
        msginfo->date = g_strdup(date);
1000
        msginfo->date_t = procheader_date_parse(NULL, date, 0);
1001

    
1002
        msginfo->from = conv_unmime_header(sender, NULL);
1003
        msginfo->fromname = procheader_get_fromname(msginfo->from);
1004

    
1005
        msginfo->subject = conv_unmime_header(subject, NULL);
1006

    
1007
        extract_parenthesis(msgid, '<', '>');
1008
        remove_space(msgid);
1009
        if (*msgid != '\0')
1010
                msginfo->msgid = g_strdup(msgid);
1011

    
1012
        eliminate_parenthesis(ref, '(', ')');
1013
        if ((p = strrchr(ref, '<')) != NULL) {
1014
                extract_parenthesis(p, '<', '>');
1015
                remove_space(p);
1016
                if (*p != '\0')
1017
                        msginfo->inreplyto = g_strdup(p);
1018
        }
1019

    
1020
        return msginfo;
1021
}
1022

    
1023
static gchar *news_parse_xhdr(const gchar *xhdr_str, MsgInfo *msginfo)
1024
{
1025
        gchar *p;
1026
        gchar *tmp;
1027
        gint num;
1028

    
1029
        p = strchr(xhdr_str, ' ');
1030
        if (!p)
1031
                return NULL;
1032
        else
1033
                p++;
1034

    
1035
        num = atoi(xhdr_str);
1036
        if (msginfo->msgnum != num) return NULL;
1037

    
1038
        tmp = strchr(p, '\r');
1039
        if (!tmp) tmp = strchr(p, '\n');
1040

    
1041
        if (tmp)
1042
                return g_strndup(p, tmp - p);
1043
        else
1044
                return g_strdup(p);
1045
}
1046

    
1047
static GSList *news_delete_old_articles(GSList *alist, FolderItem *item,
1048
                                        gint first)
1049
{
1050
        GSList *cur, *next;
1051
        MsgInfo *msginfo;
1052
        gchar *dir;
1053

    
1054
        g_return_val_if_fail(item != NULL, alist);
1055
        g_return_val_if_fail(item->folder != NULL, alist);
1056
        g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, alist);
1057

    
1058
        if (first < 2) return alist;
1059

    
1060
        debug_print("Deleting cached articles 1 - %d ...\n", first - 1);
1061

    
1062
        dir = folder_item_get_path(item);
1063
        remove_numbered_files(dir, 1, first - 1);
1064
        g_free(dir);
1065

    
1066
        for (cur = alist; cur != NULL; ) {
1067
                next = cur->next;
1068

    
1069
                msginfo = (MsgInfo *)cur->data;
1070
                if (msginfo && msginfo->msgnum < first) {
1071
                        procmsg_msginfo_free(msginfo);
1072
                        alist = g_slist_remove(alist, msginfo);
1073
                        item->cache_dirty = TRUE;
1074
                }
1075

    
1076
                cur = next;
1077
        }
1078

    
1079
        return alist;
1080
}
1081

    
1082
static void news_delete_all_articles(FolderItem *item)
1083
{
1084
        gchar *dir;
1085

    
1086
        g_return_if_fail(item != NULL);
1087
        g_return_if_fail(item->folder != NULL);
1088
        g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS);
1089

    
1090
        debug_print("Deleting all cached articles...\n");
1091

    
1092
        dir = folder_item_get_path(item);
1093
        remove_all_numbered_files(dir);
1094
        g_free(dir);
1095
}
1096

    
1097
static void news_delete_expired_caches(GSList *alist, FolderItem *item)
1098
{
1099
        gchar *dir;
1100

    
1101
        g_return_if_fail(item != NULL);
1102
        g_return_if_fail(item->folder != NULL);
1103
        g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS);
1104

    
1105
        debug_print("Deleting expired cached articles...\n");
1106

    
1107
        dir = folder_item_get_path(item);
1108
        remove_expired_files(dir, 24 * 7);
1109
        g_free(dir);
1110
}