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