1a868bd5c9fad7baa440ad659b83e1da3aa306a1
[apps/madmutt.git] / nntp / newsrc.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1998 Brandon Long <blong@fiction.net>
4  * Copyright (C) 1999 Andrej Gritsenko <andrej@lucky.net>
5  * Copyright (C) 2000-2001 Vsevolod Volkov <vvv@mutt.org.ua>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 #ifdef HAVE_CONFIG_H
13 #include <config.h>
14 #endif
15
16 #include <unistd.h>
17 #include <string.h>
18 #include <ctype.h>
19 #include <stdlib.h>
20 #include <libgen.h>
21 #include <sys/stat.h>
22
23 #include <lib-lib/lib-lib.h>
24
25 #include <lib-mime/mime.h>
26
27 #include <lib-ui/curses.h>
28
29 #include "mutt.h"
30 #include "sort.h"
31 #include "mx.h"
32 #include "nntp.h"
33
34 void nntp_add_to_list (NNTP_SERVER * s, NNTP_DATA * d)
35 {
36   string_list_t *l;
37
38   if (!s || !d)
39     return;
40
41   l = p_new(string_list_t, 1);
42   if (s->list)
43     s->tail->next = l;
44   else
45     s->list = l;
46   s->tail = l;
47   l->data = (void *) d;
48 }
49
50 static int nntp_parse_newsrc_line (NNTP_SERVER * news, char *line)
51 {
52   NNTP_DATA *data;
53   char group[LONG_STRING];
54   int x = 1;
55   char *p = line, *b, *h;
56   ssize_t len;
57
58   while (*p) {
59     if (*p++ == ',')
60       x++;
61   }
62
63   p = line;
64   while (*p && (*p != ':' && *p != '!'))
65     p++;
66   if (!*p)
67     return -1;
68   len = p + 1 - line;
69   if (len > sizeof (group))
70     len = sizeof (group);
71   m_strcpy(group, len, line);
72   if ((data = (NNTP_DATA *) hash_find (news->newsgroups, group)) == NULL) {
73     data = xmalloc(sizeof(NNTP_DATA) + m_strlen(group) + 1);
74     data->group = (char *) data + sizeof (NNTP_DATA);
75     strcpy (data->group, group);
76     data->nserv = news;
77     data->deleted = 1;
78     if (news->newsgroups->nelem < news->newsgroups->curnelem * 2)
79       news->newsgroups =
80         hash_resize (news->newsgroups, news->newsgroups->nelem * 2);
81     hash_insert (news->newsgroups, data->group, data, 0);
82     nntp_add_to_list (news, data);
83   }
84   else
85     p_delete(&data->entries);
86
87   data->rc = 1;
88   data->entries = p_new(NEWSRC_ENTRY, x * 2);
89   data->max = x * 2;
90
91   if (*p == ':')
92     data->subscribed = 1;
93   else
94     data->subscribed = 0;
95
96   p++;
97   b = p;
98   x = 0;
99   while (*b) {
100     while (*p && *p != ',' && *p != '\n')
101       p++;
102     if (*p) {
103       *p = '\0';
104       p++;
105     }
106     if ((h = strchr (b, '-'))) {
107       *h = '\0';
108       h++;
109       data->entries[x].first = atoi (b);
110       data->entries[x].last = atoi (h);
111     }
112     else {
113       data->entries[x].first = atoi (b);
114       data->entries[x].last = data->entries[x].first;
115     }
116     b = p;
117     if (data->entries[x].last != 0)
118       x++;
119   }
120   if (x && !data->lastMessage)
121     data->lastMessage = data->entries[x - 1].last;
122   data->num = x;
123   mutt_newsgroup_stat (data);
124
125   return 0;
126 }
127
128 static int slurp_newsrc (NNTP_SERVER * news)
129 {
130   FILE *fp;
131   char *buf;
132   struct stat sb;
133
134   news->stat = stat (news->newsrc, &sb);
135   news->size = sb.st_size;
136   news->mtime = sb.st_mtime;
137
138   if ((fp = safe_fopen (news->newsrc, "r")) == NULL)
139     return -1;
140   /* hmm, should we use dotlock? */
141   if (mx_lock_file (news->newsrc, fileno (fp), 0, 0, 1)) {
142     fclose (fp);
143     return -1;
144   }
145
146   buf = p_new(char, sb.st_size + 1);
147   while (fgets (buf, sb.st_size + 1, fp))
148     nntp_parse_newsrc_line (news, buf);
149   p_delete(&buf);
150
151   mx_unlock_file (news->newsrc, fileno (fp), 0);
152   fclose (fp);
153   return 0;
154 }
155
156 void nntp_cache_expand (char *dst, const char *src)
157 {
158   snprintf (dst, _POSIX_PATH_MAX, "%s/%s", NewsCacheDir, src);
159   mutt_expand_path (dst, _POSIX_PATH_MAX);
160 }
161
162 /* Loads $news_cache_dir/.index into memory, loads newsserver data
163  * and newsgroup cache names */
164 static int nntp_parse_cacheindex (NNTP_SERVER * news)
165 {
166   struct stat st;
167   char buf[HUGE_STRING], *cp;
168   char dir[_POSIX_PATH_MAX], file[_POSIX_PATH_MAX];
169   FILE *idx;
170   NNTP_DATA *data;
171   int l, m, t;
172
173   /* check is server name defined or not */
174   if (!news || !news->conn || !news->conn->account.host)
175     return -1;
176   unset_option (OPTNEWSCACHE);
177   if (!NewsCacheDir || !*NewsCacheDir)
178     return 0;
179
180   m_strcpy(dir, sizeof(dir), NewsCacheDir);
181   mutt_expand_path (dir, sizeof (dir));
182
183   if (lstat (dir, &st) || (st.st_mode & S_IFDIR) == 0) {
184     snprintf (buf, sizeof (buf), _("Directory %s not exist. Create it?"),
185               dir);
186     if (mutt_yesorno (buf, M_YES) != M_YES
187         || mkdir (dir, (S_IRWXU + S_IRWXG + S_IRWXO))) {
188       mutt_error _("Cache directory not created!");
189
190       return -1;
191     }
192     mutt_clear_error ();
193   }
194
195   set_option (OPTNEWSCACHE);
196
197   p_delete(&news->cache);
198   snprintf (buf, sizeof (buf), "%s/.index", dir);
199   if (!(idx = safe_fopen (buf, "a+")))
200     return 0;
201   rewind (idx);
202   while (fgets (buf, sizeof (buf), idx)) {
203     buf[m_strlen(buf) - 1] = 0;  /* strip ending '\n' */
204     if (!m_strncmp(buf, "#: ", 3) &&
205         !m_strcasecmp(buf + 3, news->conn->account.host))
206       break;
207   }
208   while (fgets (buf, sizeof (buf), idx)) {
209     cp = buf;
210     while (*cp && *cp != ' ')
211       cp++;
212     if (!*cp)
213       continue;
214     cp[0] = 0;
215     if (!m_strcmp(buf, "#:"))
216       break;
217     sscanf (cp + 1, "%s %d %d", file, &l, &m);
218     if (!m_strcmp(buf, "ALL")) {
219       news->cache = m_strdup(file);
220       news->newgroups_time = m;
221     }
222     else if (news->newsgroups) {
223       if ((data = (NNTP_DATA *) hash_find (news->newsgroups, buf)) == NULL) {
224         data = xmalloc(sizeof(NNTP_DATA) + m_strlen(buf) + 1);
225         data->group = (char *) data + sizeof (NNTP_DATA);
226         strcpy (data->group, buf);
227         data->nserv = news;
228         data->deleted = 1;
229         if (news->newsgroups->nelem < news->newsgroups->curnelem * 2)
230           news->newsgroups =
231             hash_resize (news->newsgroups, news->newsgroups->nelem * 2);
232         hash_insert (news->newsgroups, data->group, data, 0);
233         nntp_add_to_list (news, data);
234       }
235       data->cache = m_strdup(file);
236       t = 0;
237       if (!data->firstMessage || data->lastMessage < m)
238         t = 1;
239       if (!data->firstMessage)
240         data->firstMessage = l;
241       if (data->lastMessage < m)
242         data->lastMessage = m;
243       data->lastCached = m;
244       if (t || !data->unread)
245         mutt_newsgroup_stat (data);
246     }
247   }
248   fclose (idx);
249   return 0;
250 }
251
252 const char *nntp_format_str (char *dest, ssize_t destlen, char op,
253                              const char *src, const char *fmt,
254                              const char *ifstring, const char *elsestring,
255                              unsigned long data, format_flag flags)
256 {
257   char fn[SHORT_STRING], tmp[SHORT_STRING];
258
259   switch (op) {
260   case 's':
261     m_strcpy(fn, sizeof (fn), NewsServer);
262     m_strtolower(fn);
263     snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
264     snprintf (dest, destlen, tmp, fn);
265     break;
266   }
267   return (src);
268 }
269
270 /* nntp_parse_url: given an NNPT URL, return host, port,
271  * username, password and newsgroup will recognise. */
272 int nntp_parse_url (const char *server, ACCOUNT * act,
273                     char *group, ssize_t group_len)
274 {
275   ciss_url_t url;
276   char *c;
277   int ret = -1;
278
279   /* Defaults */
280   act->flags = 0;
281   act->port = NNTP_PORT;
282   act->type = M_ACCT_TYPE_NNTP;
283
284   c = m_strdup(server);
285   url_parse_ciss (&url, c);
286
287   if (url.scheme == U_NNTP || url.scheme == U_NNTPS) {
288     if (url.scheme == U_NNTPS) {
289       act->flags |= M_ACCT_SSL;
290       act->port = NNTP_SSL_PORT;
291     }
292
293     *group = '\0';
294     if (url.path)
295       m_strcpy(group, group_len, url.path);
296
297     ret = mutt_account_fromurl (act, &url);
298   }
299
300   p_delete(&c);
301   return ret;
302 }
303
304 void nntp_expand_path (char *line, ssize_t len, ACCOUNT * act)
305 {
306   ciss_url_t url;
307
308   url.path = m_strdup(line);
309   mutt_account_tourl (act, &url);
310   url_ciss_tostring (&url, line, len, 0);
311   p_delete(&url.path);
312 }
313
314 /*
315  * Automatically loads a newsrc into memory, if necessary.
316  * Checks the size/mtime of a newsrc file, if it doesn't match, load
317  * again.  Hmm, if a system has broken mtimes, this might mean the file
318  * is reloaded every time, which we'd have to fix.
319  *
320  * a newsrc file is a line per newsgroup, with the newsgroup, then a 
321  * ':' denoting subscribed or '!' denoting unsubscribed, then a 
322  * comma separated list of article numbers and ranges.
323  */
324 NNTP_SERVER *mutt_select_newsserver (char *server)
325 {
326   char file[_POSIX_PATH_MAX];
327   char *buf, *p;
328   string_list_t *list;
329   ACCOUNT act;
330   NNTP_SERVER *serv;
331   CONNECTION *conn;
332
333   p_clear(&act, 1);
334
335   if (!server || !*server) {
336     mutt_error _("No newsserver defined!");
337
338     return NULL;
339   }
340
341   buf = p = p_new(char, m_strlen(server) + 10);
342   if (url_check_scheme (server) == U_UNKNOWN) {
343     strcpy (buf, "nntp://");
344     p = strchr (buf, '\0');
345   }
346   strcpy (p, server);
347
348   if ((nntp_parse_url (buf, &act, file, sizeof (file))) < 0 || *file) {
349     p_delete(&buf);
350     mutt_error (_("%s is an invalid newsserver specification!"), server);
351     return NULL;
352   }
353   p_delete(&buf);
354
355   conn = mutt_conn_find (NULL, &act);
356   if (!conn)
357     return NULL;
358
359   mutt_FormatString (file, sizeof (file), NONULL (NewsRc), nntp_format_str, 0,
360                      0);
361   mutt_expand_path (file, sizeof (file));
362
363   serv = (NNTP_SERVER *) conn->data;
364   if (serv) {
365     struct stat sb;
366
367     /* externally modified? */
368     if (serv->stat != stat (file, &sb) || (!serv->stat &&
369                                            (serv->size != sb.st_size
370                                             || serv->mtime != sb.st_mtime))) {
371       for (list = serv->list; list; list = list->next) {
372         NNTP_DATA *data = (NNTP_DATA *) list->data;
373
374         if (data) {
375           data->subscribed = 0;
376           data->rc = 0;
377           data->num = 0;
378         }
379       }
380       slurp_newsrc (serv);
381       nntp_clear_cacheindex (serv);
382     }
383
384     if (serv->status == NNTP_BYE)
385       serv->status = NNTP_NONE;
386     nntp_check_newgroups (serv, 0);
387     return serv;
388   }
389
390   /* New newsserver */
391   serv = p_new(NNTP_SERVER, 1);
392   serv->conn = conn;
393   serv->newsrc = m_strdup(file);
394   serv->newsgroups = hash_create (1009);
395   slurp_newsrc (serv);          /* load .newsrc */
396   nntp_parse_cacheindex (serv); /* load .index */
397   if (option (OPTNEWSCACHE) && serv->cache && nntp_get_cache_all (serv) >= 0)
398     nntp_check_newgroups (serv, 1);
399   else if (nntp_get_active (serv) < 0) {
400     hash_destroy (&serv->newsgroups, nntp_delete_data);
401     for (list = serv->list; list; list = list->next)
402       list->data = NULL;
403     string_list_wipe(&serv->list);
404     p_delete(&serv->newsrc);
405     p_delete(&serv->cache);
406     p_delete(&serv);
407     return NULL;
408   }
409   nntp_clear_cacheindex (serv);
410   conn->data = (void *) serv;
411
412   return serv;
413 }
414
415 /* 
416  * full status flags are not supported by nntp, but we can fake some
417  * of them.  This is how:
418  * Read = a read message number is in the .newsrc
419  * New = a message is new since we last read this newsgroup
420  * Old = anything else
421  * So, Read is marked as such in the newsrc, old is anything that is 
422  * "skipped" in the newsrc, and new is anything not in the newsrc nor
423  * in the cache. By skipped, I mean before the last unread message
424  */
425 void nntp_get_status (CONTEXT * ctx, HEADER * h, char *group, int article)
426 {
427   NNTP_DATA *data = (NNTP_DATA *) ctx->data;
428   int x;
429
430   if (group)
431     data = (NNTP_DATA *) hash_find (data->nserv->newsgroups, group);
432
433   if (!data) {
434     return;
435   }
436
437   for (x = 0; x < data->num; x++) {
438     if ((article >= data->entries[x].first) &&
439         (article <= data->entries[x].last)) {
440       /* we cannot use mutt_set_flag() because mx_update_context()
441          didn't called yet */
442       h->read = 1;
443       return;
444     }
445   }
446   /* If article was not cached yet, it is new! :) */
447   if (!data->cache || article > data->lastCached)
448     return;
449   /* Old articles are articles which aren't read but an article after them
450    * has been cached */
451   if (option (OPTMARKOLD))
452     h->old = 1;
453 }
454
455 void mutt_newsgroup_stat (NNTP_DATA * data)
456 {
457   int i;
458   unsigned int first, last;
459
460   data->unread = 0;
461   if (data->lastMessage == 0 || data->firstMessage > data->lastMessage)
462     return;
463
464   data->unread = data->lastMessage - data->firstMessage + 1;
465   for (i = 0; i < data->num; i++) {
466     first = data->entries[i].first;
467     if (first < data->firstMessage)
468       first = data->firstMessage;
469     last = data->entries[i].last;
470     if (last > data->lastMessage)
471       last = data->lastMessage;
472     if (first <= last)
473       data->unread -= last - first + 1;
474   }
475 }
476
477 static int puti (char *line, int num)
478 {
479   char *p, s[32];
480
481   for (p = s; num;) {
482     *p++ = '0' + num % 10;
483     num /= 10;
484   }
485   while (p > s)
486     *line++ = *--p, num++;
487   *line = '\0';
488   return num;
489 }
490
491 static void nntp_create_newsrc_line (NNTP_DATA * data, char **buf,
492                                      char **pline, ssize_t * buflen)
493 {
494   char *line = *pline;
495   ssize_t len = *buflen - (*pline - *buf);
496   int x, i;
497
498   if (len < LONG_STRING * 10) {
499     len += *buflen;
500     *buflen *= 2;
501     line = *buf;
502     p_realloc(buf, *buflen);
503     line = *buf + (*pline - line);
504   }
505   strcpy (line, data->group);
506   len -= m_strlen(line) + 1;
507   line += m_strlen(line);
508   *line++ = data->subscribed ? ':' : '!';
509   *line++ = ' ';
510   *line = '\0';
511
512   for (x = 0; x < data->num; x++) {
513     if (len < LONG_STRING) {
514       len += *buflen;
515       *buflen *= 2;
516       *pline = line;
517       line = *buf;
518       p_realloc(buf, *buflen);
519       line = *buf + (*pline - line);
520     }
521     if (x) {
522       *line++ = ',';
523       len--;
524     }
525
526 #if 0
527     if (data->entries[x].first == data->entries[x].last)
528       snprintf (line, len, "%d%n", data->entries[x].first, &i);
529     else
530       snprintf (line, len, "%d-%d%n",
531                 data->entries[x].first, data->entries[x].last, &i);
532     len -= i;
533     line += i;
534 #else
535     i = puti (line, data->entries[x].first);
536     line += i;
537     len -= i;
538     if (data->entries[x].first != data->entries[x].last) {
539       *line++ = '-';
540       len--;
541       i = puti (line, data->entries[x].last);
542       line += i;
543       len -= i;
544     }
545 #endif
546   }
547   *line++ = '\n';
548   *line = '\0';
549   *pline = line;
550 }
551
552 void newsrc_gen_entries (CONTEXT * ctx)
553 {
554   NNTP_DATA *data = (NNTP_DATA *) ctx->data;
555   int series, x;
556   unsigned int last = 0, first = 1;
557   int save_sort = SORT_ORDER;
558
559   if (Sort != SORT_ORDER) {
560     save_sort = Sort;
561     Sort = SORT_ORDER;
562     mutt_sort_headers (ctx, 0);
563   }
564
565   if (!data->max) {
566     data->entries = p_new(NEWSRC_ENTRY, 5);
567     data->max = 5;
568   }
569
570   /*
571    * Set up to fake initial sequence from 1 to the article before the 
572    * first article in our list
573    */
574   data->num = 0;
575   series = 1;
576
577   for (x = 0; x < ctx->msgcount; x++) {
578     if (series) {               /* search for first unread */
579       /*
580        * We don't actually check sequential order, since we mark 
581        * "missing" entries as read/deleted
582        */
583       last = ctx->hdrs[x]->article_num;
584       if (last >= data->firstMessage && !ctx->hdrs[x]->deleted &&
585           !ctx->hdrs[x]->read) {
586         if (data->num >= data->max) {
587           data->max = data->max * 2;
588           p_realloc(&data->entries, data->max);
589         }
590         data->entries[data->num].first = first;
591         data->entries[data->num].last = last - 1;
592         data->num++;
593         series = 0;
594       }
595     }
596     else {                      /* search for first read */
597
598       if (ctx->hdrs[x]->deleted || ctx->hdrs[x]->read) {
599         first = last + 1;
600         series = 1;
601       }
602       last = ctx->hdrs[x]->article_num;
603     }
604   }
605   if (series && first <= data->lastLoaded) {
606     if (data->num >= data->max) {
607       data->max = data->max * 2;
608       p_realloc(&data->entries, data->max);
609     }
610     data->entries[data->num].first = first;
611     data->entries[data->num].last = data->lastLoaded;
612     data->num++;
613   }
614
615   if (save_sort != Sort) {
616     Sort = save_sort;
617     mutt_sort_headers (ctx, 0);
618   }
619 }
620
621 static int mutt_update_list_file (char *filename, char *section,
622                                   const char *key, char *line) {
623   FILE *ifp;
624   FILE *ofp;
625   char buf[HUGE_STRING];
626   char tmpfile[_POSIX_PATH_MAX], link[_POSIX_PATH_MAX];
627   char *c;
628   int ext = 0, done = 0, r = 0, l = 0;
629
630   /* if file not exist, create it */
631   if ((ifp = safe_fopen (filename, "a")))
632     fclose (ifp);
633   if (!(ifp = safe_fopen (filename, "r"))) {
634     mutt_error (_("Unable to open %s for reading"), filename);
635     return -1;
636   }
637   if (mx_lock_file (filename, fileno (ifp), 0, 0, 1)) {
638     fclose (ifp);
639     mutt_error (_("Unable to lock %s"), filename);
640     return -1;
641   }
642   /* use mutt_adv_mktemp() to get a tempfile in the same
643    * directory as filename is so that we can follow symlinks
644    * via rename(2); as dirname(2) may modify its argument,
645    * temporarily use buf as copy of it
646    */
647   m_strcpy(buf, sizeof(buf), filename);
648   m_strcpy(tmpfile, sizeof(tmpfile), basename(filename));
649   mutt_adv_mktemp ((const char*) dirname (buf), tmpfile, sizeof (tmpfile));
650   if (!(ofp = fopen (tmpfile, "w"))) {
651     fclose (ifp);
652     mutt_error (_("Unable to open %s for writing"), tmpfile);
653     return -1;
654   }
655
656   if (section) {
657     while (r != EOF && !done && fgets (buf, sizeof (buf), ifp)) {
658       r = fputs (buf, ofp);
659       c = buf;
660       while (*c && *c != '\n') c++;
661       c[0] = 0; /* strip EOL */
662       if (!strncmp (buf, "#: ", 3) && !m_strcasecmp(buf+3, section))
663         done++;
664     }
665     if (r != EOF && !done) {
666       snprintf (buf, sizeof(buf), "#: %s\n", section);
667       r = fputs (buf, ofp);
668     }
669     done = 0;
670   }
671
672   while (r != EOF && fgets (buf, sizeof (buf), ifp)) {
673     if (ext) {
674       c = buf;
675       while (*c && (*c != '\r') && (*c != '\n')) c++;
676       c--;
677       if (*c != '\\') ext = 0;
678     } else if ((section && !strncmp (buf, "#: ", 3))) {
679       if (!done && line) {
680         fputs (line, ofp);
681         fputc ('\n', ofp);
682       }
683       r = fputs (buf, ofp);
684       done++;
685       break;
686     } else if (key && !strncmp (buf, key, strlen(key)) &&
687                (!*key || buf[strlen(key)] == ' ')) {
688       c = buf;
689       ext = 0;
690       while (*c && (*c != '\r') && (*c != '\n')) c++;
691       c--;
692       if (*c == '\\') ext = 1;
693       if (!done && line) {
694         r = fputs (line, ofp);
695         if (*key)
696           r = fputc ('\n', ofp);
697         done++;
698       }
699     } else {
700       r = fputs (buf, ofp);
701     }
702   }
703
704   while (r != EOF && fgets (buf, sizeof (buf), ifp))
705     r = fputs (buf, ofp);
706
707   /* If there wasn't a line to replace, put it on the end of the file */
708   if (r != EOF && !done && line) {
709     fputs (line, ofp);
710     r = fputc ('\n', ofp);
711   }
712   mx_unlock_file (filename, fileno (ifp), 0);
713   fclose (ofp);
714   fclose (ifp);
715   if (r == EOF) {
716     unlink (tmpfile);
717     mutt_error (_("Can't write %s"), tmpfile);
718     return -1;
719   }
720   link[0] = '\0';
721   if ((l = readlink (filename, link, sizeof (link)-1)) > 0)
722     link[l] = '\0';
723   if (rename (tmpfile, l > 0 ? link : filename) < 0) {
724     unlink (tmpfile);
725     mutt_error (_("Can't rename %s to %s"), tmpfile, l > 0 ? link : filename);
726     return -1;
727   }
728   return 0;
729 }
730
731 int mutt_newsrc_update (NNTP_SERVER * news)
732 {
733   char *buf, *line;
734   NNTP_DATA *data;
735   string_list_t *tmp;
736   int r = -1;
737   ssize_t len, llen;
738
739   if (!news)
740     return -1;
741   llen = len = 10 * LONG_STRING;
742   line = buf = p_new(char, len);
743   /* we will generate full newsrc here */
744   for (tmp = news->list; tmp; tmp = tmp->next) {
745     data = (NNTP_DATA *) tmp->data;
746     if (!data || !data->rc)
747       continue;
748     nntp_create_newsrc_line (data, &buf, &line, &llen);
749     line += m_strlen(line);
750   }
751   /* newrc being fully rewritten */
752   if (news->newsrc &&
753       (r = mutt_update_list_file(news->newsrc, NULL, "", buf)) == 0) {
754     struct stat st;
755
756     stat (news->newsrc, &st);
757     news->size = st.st_size;
758     news->mtime = st.st_mtime;
759   }
760   p_delete(&buf);
761   return r;
762 }
763
764 static FILE *mutt_mkname (char *s)
765 {
766   char buf[_POSIX_PATH_MAX], *pc;
767   int fd;
768   FILE *fp;
769
770   nntp_cache_expand (buf, s);
771   if ((fp = safe_fopen (buf, "w")))
772     return fp;
773
774   nntp_cache_expand (buf, "cache-XXXXXX");
775   pc = buf + m_strlen(buf) - 12; /* positioning to "cache-XXXXXX" */
776   if ((fd = mkstemp (buf)) == -1)
777     return NULL;
778   strcpy (s, pc);               /* generated name */
779   return fdopen (fd, "w");
780 }
781
782 /* Updates info into .index file: ALL or about selected newsgroup */
783 static int nntp_update_cacheindex (NNTP_SERVER * serv, NNTP_DATA * data)
784 {
785   char buf[LONG_STRING];
786   char file[_POSIX_PATH_MAX];
787   const char *key = "ALL";
788
789   if (!serv || !serv->conn || !serv->conn->account.host)
790     return -1;
791
792   if (data && data->group) {
793     key = data->group;
794     snprintf (buf, sizeof (buf), "%s %s %d %d", key, data->cache,
795               data->firstMessage, data->lastLoaded);
796   }
797   else {
798     m_strcpy(file, sizeof(file), serv->cache);
799     snprintf (buf, sizeof (buf), "ALL %s 0 %d", file,
800               (int) serv->newgroups_time);
801   }
802   nntp_cache_expand (file, ".index");
803   return mutt_update_list_file (file, serv->conn->account.host, key, buf);
804 }
805
806 /* Remove cache files of unsubscribed newsgroups */
807 void nntp_clear_cacheindex (NNTP_SERVER * news)
808 {
809   NNTP_DATA *data;
810   string_list_t *tmp;
811
812   if (option (OPTSAVEUNSUB) || !news)
813     return;
814
815   for (tmp = news->list; tmp; tmp = tmp->next) {
816     data = (NNTP_DATA *) tmp->data;
817     if (!data || data->subscribed || !data->cache)
818       continue;
819     nntp_delete_cache (data);
820   }
821   return;
822 }
823
824 int nntp_save_cache_index (NNTP_SERVER * news)
825 {
826   char buf[HUGE_STRING];
827   char file[_POSIX_PATH_MAX];
828   NNTP_DATA *d;
829   FILE *f;
830   string_list_t *l;
831
832   if (!news || !news->newsgroups)
833     return -1;
834   if (!option (OPTNEWSCACHE))
835     return 0;
836
837   if (news->cache) {
838     nntp_cache_expand (file, news->cache);
839     unlink (file);
840     f = safe_fopen (file, "w");
841   }
842   else {
843     m_strcpy(buf, sizeof(buf), news->conn->account.host);
844     f = mutt_mkname (buf);
845     news->cache = m_strdup(buf);
846     nntp_cache_expand (file, buf);
847   }
848   if (!f)
849     return -1;
850
851   for (l = news->list; l; l = l->next) {
852     if ((d = (NNTP_DATA *) l->data) && !d->deleted) {
853       if (d->desc)
854         snprintf (buf, sizeof (buf), "%s %d %d %c %s\n", d->group,
855                   d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n',
856                   d->desc);
857       else
858         snprintf (buf, sizeof (buf), "%s %d %d %c\n", d->group,
859                   d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n');
860       if (fputs (buf, f) == EOF) {
861         fclose (f);
862         unlink (file);
863         return -1;
864       }
865     }
866   }
867   fclose (f);
868
869   if (nntp_update_cacheindex (news, NULL)) {
870     unlink (file);
871     return -1;
872   }
873   return 0;
874 }
875
876 int nntp_save_cache_group (CONTEXT * ctx)
877 {
878   char buf[HUGE_STRING], addr[STRING];
879   char file[_POSIX_PATH_MAX];
880   FILE *f;
881   HEADER *h;
882   struct tm *tm;
883   int i = 0, save = SORT_ORDER;
884   int prev = 0;
885
886   if (!option (OPTNEWSCACHE))
887     return 0;
888   if (!ctx || !ctx->data || ctx->magic != M_NNTP)
889     return -1;
890
891   if (((NNTP_DATA *) ctx->data)->cache) {
892     nntp_cache_expand (file, ((NNTP_DATA *) ctx->data)->cache);
893     unlink (file);
894     f = safe_fopen (file, "w");
895   }
896   else {
897     snprintf (buf, sizeof (buf), "%s-%s",
898               ((NNTP_DATA *) ctx->data)->nserv->conn->account.host,
899               ((NNTP_DATA *) ctx->data)->group);
900     f = mutt_mkname (buf);
901     ((NNTP_DATA *) ctx->data)->cache = m_strdup(buf);
902     nntp_cache_expand (file, buf);
903   }
904   if (!f)
905     return -1;
906
907   if (Sort != SORT_ORDER) {
908     save = Sort;
909     Sort = SORT_ORDER;
910     mutt_sort_headers (ctx, 0);
911   }
912
913   /* Save only $nntp_context messages... */
914   ((NNTP_DATA *) ctx->data)->lastCached = 0;
915   if (NntpContext && ctx->msgcount > NntpContext)
916     i = ctx->msgcount - NntpContext;
917   for (; i < ctx->msgcount; i++) {
918     if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->article_num != prev) {
919       h = ctx->hdrs[i];
920       addr[0] = 0;
921       rfc822_write_address (addr, sizeof (addr), h->env->from, 0);
922       tm = gmtime (&h->date_sent);
923       snprintf (buf, sizeof (buf),
924                 "%d\t%s\t%s\t%d %s %d %02d:%02d:%02d GMT\t%s\t",
925                 h->article_num, h->env->subject, addr, tm->tm_mday,
926                 Months[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour,
927                 tm->tm_min, tm->tm_sec, h->env->message_id);
928       fputs (buf, f);
929       if (h->env->references)
930         mutt_write_references (h->env->references, f);
931       snprintf (buf, sizeof (buf), "\t%zd\t%d\tXref: %s\n",
932                 h->content->length, h->lines, NONULL (h->env->xref));
933       if (fputs (buf, f) == EOF) {
934         fclose (f);
935         unlink (file);
936         return -1;
937       }
938     }
939     prev = ctx->hdrs[i]->article_num;
940   }
941
942   if (save != Sort) {
943     Sort = save;
944     mutt_sort_headers (ctx, 0);
945   }
946   fclose (f);
947
948   if (nntp_update_cacheindex (((NNTP_DATA *) ctx->data)->nserv,
949                               (NNTP_DATA *) ctx->data)) {
950     unlink (file);
951     return -1;
952   }
953   ((NNTP_DATA *) ctx->data)->lastCached =
954     ((NNTP_DATA *) ctx->data)->lastLoaded;
955   return 0;
956 }
957
958 void nntp_delete_cache (NNTP_DATA * data)
959 {
960   char buf[_POSIX_PATH_MAX];
961
962   if (!option (OPTNEWSCACHE) || !data || !data->cache || !data->nserv)
963     return;
964
965   nntp_cache_expand (buf, data->cache);
966   unlink (buf);
967   p_delete(&data->cache);
968   data->lastCached = 0;
969   nntp_cache_expand (buf, ".index");
970   mutt_update_list_file (buf, data->nserv->conn->account.host, data->group,
971                          NULL);
972 }
973
974 NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER * news, char *group)
975 {
976   NNTP_DATA *data;
977
978   if (!news || !news->newsgroups || !group || !*group)
979     return NULL;
980   if (!(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) {
981     data = xmalloc(sizeof(NNTP_DATA) + m_strlen(group) + 1);
982     data->group = (char *) data + sizeof (NNTP_DATA);
983     strcpy (data->group, group);
984     data->nserv = news;
985     data->deleted = 1;
986     if (news->newsgroups->nelem < news->newsgroups->curnelem * 2)
987       news->newsgroups =
988         hash_resize (news->newsgroups, news->newsgroups->nelem * 2);
989     hash_insert (news->newsgroups, data->group, data, 0);
990     nntp_add_to_list (news, data);
991   }
992   if (!data->subscribed) {
993     data->subscribed = 1;
994     data->rc = 1;
995   }
996   return data;
997 }
998
999 NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER * news, char *group)
1000 {
1001   NNTP_DATA *data;
1002
1003   if (!news || !news->newsgroups || !group || !*group ||
1004       !(data = (NNTP_DATA *) hash_find (news->newsgroups, group)))
1005     return NULL;
1006   if (data->subscribed) {
1007     data->subscribed = 0;
1008     if (!option (OPTSAVEUNSUB))
1009       data->rc = 0;
1010   }
1011   return data;
1012 }
1013
1014 NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER * news, char *group)
1015 {
1016   NNTP_DATA *data;
1017
1018   if (!news || !news->newsgroups || !group || !*group ||
1019       !(data = (NNTP_DATA *) hash_find (news->newsgroups, group)))
1020     return NULL;
1021   if (!data->max) {
1022     data->entries = p_new(NEWSRC_ENTRY, 5);
1023     data->max = 5;
1024   }
1025   data->num = 1;
1026   data->entries[0].first = 1;
1027   data->unread = 0;
1028   data->entries[0].last = data->lastMessage;
1029   if (Context && Context->data == data) {
1030     int x;
1031
1032     for (x = 0; x < Context->msgcount; x++)
1033       mutt_set_flag (Context, Context->hdrs[x], M_READ, 1);
1034   }
1035   return data;
1036 }
1037
1038 NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER * news, char *group)
1039 {
1040   NNTP_DATA *data;
1041
1042   if (!news || !news->newsgroups || !group || !*group ||
1043       !(data = (NNTP_DATA *) hash_find (news->newsgroups, group)))
1044     return NULL;
1045   if (!data->max) {
1046     data->entries = p_new(NEWSRC_ENTRY, 5);
1047     data->max = 5;
1048   }
1049   data->num = 1;
1050   data->entries[0].first = 1;
1051   data->entries[0].last = data->firstMessage - 1;
1052   if (Context && Context->data == data) {
1053     int x;
1054
1055     data->unread = Context->msgcount;
1056     for (x = 0; x < Context->msgcount; x++)
1057       mutt_set_flag (Context, Context->hdrs[x], M_READ, 0);
1058   }
1059   else
1060     data->unread = data->lastMessage - data->entries[0].last;
1061   return data;
1062 }
1063
1064 /* this routine gives the first newsgroup with new messages */
1065 void nntp_buffy (char* dst, ssize_t dstlen) {
1066   string_list_t *list;
1067   int count = 0;
1068
1069   /* forward to current group */
1070   for (list = CurrentNewsSrv->list; list; list = list->next) {
1071     NNTP_DATA *data = (NNTP_DATA *) list->data;
1072     if (data && data->subscribed && data->unread && 
1073         Context && Context->magic == M_NNTP &&
1074         m_strcmp(data->group, ((NNTP_DATA *) Context->data)->group) == 0) {
1075       list = list->next;
1076       break;
1077     }
1078   }
1079
1080   *dst = '\0';
1081
1082   while (count < 2) {
1083
1084     if (!list)
1085       list = CurrentNewsSrv->list;
1086
1087     for (; list; list = list->next) {
1088       NNTP_DATA *data = (NNTP_DATA *) list->data;
1089
1090       if (data && data->subscribed && data->unread) {
1091         if (Context && Context->magic == M_NNTP &&
1092             !m_strcmp(data->group, ((NNTP_DATA *) Context->data)->group)) {
1093           unsigned int i, unread = 0;
1094
1095           for (i = 0; i < Context->msgcount; i++)
1096             if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted)
1097               unread++;
1098           if (!unread)
1099             continue;
1100         }
1101         m_strcpy(dst, dstlen, data->group);
1102         break;
1103       }
1104     }
1105     /* done if found */
1106     if (dst && *dst)
1107       return;
1108     count++;
1109   }
1110   *dst = '\0';
1111 }