1 /*
2 * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved.
3 * Copyright (c) 1996-2005 Michael T Pins. All rights reserved.
4 *
5 * .newsrc parsing and update.
6 */
7
8 #include <stdlib.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include "config.h"
12 #include "global.h"
13 #include "articles.h"
14 #include "active.h"
15 #include "db.h"
16 #include "newsrc.h"
17 #include "nn.h"
18 #include "options.h"
19 #include "regexp.h"
20 #include "nn_term.h"
21
22 /* newsrc.c */
23
24 struct rc_info;
25
26 static int dump_file(char *path, int mode);
27 static void dump_newsrc(void);
28 static void dump_select(void);
29 static time_t get_last_new(void);
30 static void update_last_new(group_header * lastg);
31 static article_number get_last_article(group_header * gh);
32 static void init_rctest(group_header * gh, register struct rc_info * r);
33 static attr_type rctest(register article_header * ah, register struct rc_info * r);
34 static void append_range(int pp, char delim, article_number rmin, article_number rmax);
35 static void begin_rc_update(register group_header * gh);
36 static void end_rc_update(register group_header * gh);
37 static void mark_article(register article_header * ah, attr_type how);
38
39
40 #ifdef sel
41 /* Remove Gould SELbus software name collision */
42 #undef sel
43 #endif
44
45 #define TR(arg) tprintf arg
46
47 extern char *news_lib_directory, *db_directory;
48 extern char *home_directory;
49 extern char *pname;
50
51 extern int verbose;
52 extern int silent;
53
54 extern long n_selected;
55 extern char *tmp_directory;
56
57 int keep_rc_backup = 1;
58 char *bak_suffix = ".bak";
59 char *initial_newsrc_path = ".defaultnewsrc";
60
61 int no_update = 0;
62 int use_selections = 1;
63 int quick_unread_count = 1; /* make a quick count of unread art. */
64 int newsrc_update_freq = 1; /* how often to write .newsrc */
65 char *newsrc_file = NULL;
66
67 #define RCX_NEVER 0 /* ignore missing groups */
68 #define RCX_HEAD 1 /* prepend missing groups to .newsrc when
69 * read */
70 #define RCX_TAIL 2 /* append missing groups to .newsrc when read */
71 #define RCX_TIME 3 /* append NEW groups as they arrive */
72 #define RCX_TIME_CONF 4 /* append NEW groups with confirmation */
73 #define RCX_RNLAST 5 /* .rnlast compatible functionality */
74
75 int new_group_action = RCX_TIME; /* append new groups to
76 * .newsrc */
77 int keep_unsubscribed = 1; /* keep unsubscribed groups in
78 * .newsrc */
79 int keep_unsub_long = 0; /* keep unread in unsubscribed groups */
80
81 int tidy_newsrc = 0;/* remove obsolete groups from .newsrc */
82
83 int auto_junk_seen = 1; /* junk seen articles ... */
84 int conf_junk_seen = 0; /* ... if confirmed by user ... */
85 int retain_seen_status = 0; /* ... or remember seen articles. */
86
87 long unread_articles;/* estimate of unread articles */
88 int unread_groups;
89
90 group_header *rc_sequence = NULL;
91
92 static char *sel_path = NULL;
93
94
95 /* delimitors on newsrc lines */
96
97 #define RC_SUBSCR ':' /* subscription to group */
98 #define RC_UNSUBSCR '!' /* no subscription to group */
99
100 #define RC_DELIM ',' /* separator on rc lines */
101 #define RC_RANGE '-' /* range */
102
103 /* delimitors on select lines */
104
105 #define SEL_RANGE '-' /* range */
106 #define SEL_SELECT ',' /* following articles are selected */
107 #define SEL_LEAVE '+' /* following articles are left over */
108 #define SEL_SEEN ';' /* following articles are seen */
109 #define SEL_UNREAD '~' /* in digests */
110 #define SEL_DIGEST '(' /* start digest list */
111 #define SEL_END_DIGEST ')' /* end digest list */
112 #define SEL_NEW '&' /* new group (group.name&nnn) */
113
114 #define END_OF_LIST 0x7fffffffL /* Greater than any article number */
115
116 /* line buffers */
117
118 #define RC_LINE_MAX 32768
119
120 static char rcbuf[RC_LINE_MAX];
121 static char selbuf[RC_LINE_MAX];
122
123 static group_header *rc_seq_tail = NULL;
124
125 static int newsrc_update_count = 0, select_update_count = 0;
126
127 #define DM_NEWSRC 0
128 #define DM_SELECT 1
129 #define DM_ORIG_NEWSRC 2
130 #define DM_ORIG_SELECT 3
131
132 static int
dump_file(char * path,int mode)133 dump_file(char *path, int mode)
134 {
135 FILE *f = NULL;
136 register group_header *gh;
137 char *line = NULL;
138
139 Loop_Groups_Newsrc(gh) {
140 switch (mode) {
141 case DM_NEWSRC:
142 if (tidy_newsrc) {
143 if ((gh->master_flag & M_VALID) == 0)
144 continue;
145 if (!keep_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED))
146 continue;
147 }
148 line = gh->newsrc_line;
149 break;
150 case DM_SELECT:
151 if (tidy_newsrc && (gh->master_flag & M_VALID) == 0)
152 continue;
153 if (gh->group_flag & G_UNSUBSCRIBED)
154 continue;
155 line = gh->select_line;
156 break;
157 case DM_ORIG_NEWSRC:
158 line = gh->newsrc_orig;
159 break;
160 case DM_ORIG_SELECT:
161 line = gh->select_orig;
162 break;
163 }
164 if (line == NULL)
165 continue;
166 if (f == NULL)
167 f = open_file(path, OPEN_CREATE | MUST_EXIST);
168 fputs(line, f);
169 }
170 if (f != NULL)
171 if (fclose(f) == EOF)
172 return -1;
173 return 0;
174 }
175
176
177 static void
dump_newsrc(void)178 dump_newsrc(void)
179 {
180 char bak[FILENAME];
181 static int first = 1;
182
183 if (no_update)
184 return;
185 if (++newsrc_update_count < newsrc_update_freq)
186 return;
187
188 if (first && keep_rc_backup) {
189 sprintf(bak, "%s%s", newsrc_file, bak_suffix);
190 if (dump_file(bak, DM_ORIG_NEWSRC))
191 nn_exitmsg(1, "Cannot backup %s", newsrc_file);
192 first = 0;
193 }
194 if (dump_file(newsrc_file, DM_NEWSRC)) {
195 char temp[FILENAME];
196 sprintf(temp, "%s/newsrc-%d", tmp_directory, process_id);
197 if (dump_file(temp, DM_NEWSRC))
198 nn_exitmsg(1, "Cannot update %s -- restore %s file!!!",
199 newsrc_file, bak_suffix);
200 else
201 nn_exitmsg(1, "Cannot update %s -- saved in %s", newsrc_file, temp);
202 }
203 newsrc_update_count = 0;
204 }
205
206 static void
dump_select(void)207 dump_select(void)
208 {
209 char bak[FILENAME];
210 static int first = 1;
211
212 if (no_update)
213 return;
214 if (++select_update_count < newsrc_update_freq)
215 return;
216
217 if (first && keep_rc_backup) {
218 sprintf(bak, "%s%s", sel_path, bak_suffix);
219 dump_file(bak, DM_ORIG_SELECT);
220 first = 0;
221 }
222 dump_file(sel_path, DM_SELECT);
223
224 select_update_count = 0;
225 }
226
227 #define RN_LAST_GROUP_READ 0
228 #define RN_LAST_TIME_RUN 1
229 #define RN_LAST_ACTIVE_SIZE 2
230 #define RN_LAST_CREATION_TIME 3
231 #define RN_LAST_NEW_GROUP 4
232 #define RN_ACTIVE_TIMES_OFFSET 5
233
234 #define MAX_RNLAST_LINE 6
235
236 static char *rnlast_line[MAX_RNLAST_LINE];
237 #define rnlast_path relative(home_directory, ".rnlast")
238
239 static time_t
get_last_new(void)240 get_last_new(void)
241 {
242 FILE *lf;
243 char buf[FILENAME];
244 register int i;
245 time_t t;
246
247 if (new_group_action == RCX_RNLAST) {
248 lf = open_file(rnlast_path, OPEN_READ);
249 if (lf == NULL)
250 goto no_file;
251
252 for (i = 0; i < MAX_RNLAST_LINE; i++) {
253 if (fgets(buf, FILENAME, lf) == NULL)
254 break;
255 rnlast_line[i] = copy_str(buf);
256 }
257 if (i != MAX_RNLAST_LINE) {
258 while (i <= MAX_RNLAST_LINE) {
259 rnlast_line[i] = copy_str("");
260 i++;
261 }
262 fclose(lf);
263 return (time_t) atol(rnlast_line[RN_LAST_TIME_RUN]);
264 }
265 fclose(lf);
266 return (time_t) atol(rnlast_line[RN_LAST_CREATION_TIME]);
267 }
268 lf = open_file(relative(nn_directory, "LAST"), OPEN_READ);
269 if (lf == NULL)
270 goto no_file;
271 if (fgets(buf, FILENAME, lf) == NULL)
272 goto no_file;
273
274 fclose(lf);
275 return (time_t) atol(buf);
276
277 no_file:
278 if (lf != NULL)
279 fclose(lf);
280 t = file_exist(newsrc_file, (char *) NULL);
281 return t > 0 ? t : (time_t) (-1);
282 }
283
284 static void
update_last_new(group_header * lastg)285 update_last_new(group_header * lastg)
286 {
287 FILE *lf;
288 register int i;
289 struct stat st;
290
291 if (no_update)
292 return;
293
294 if (new_group_action == RCX_RNLAST) {
295 if (rnlast_line[0] == NULL)
296 for (i = 0; i < MAX_RNLAST_LINE; i++)
297 rnlast_line[i] = copy_str("");
298 lf = open_file(rnlast_path, OPEN_CREATE | MUST_EXIST);
299 fputs(rnlast_line[RN_LAST_GROUP_READ], lf); /* as good as any */
300 fprintf(lf, "%ld\n", (long) cur_time()); /* RN_LAST_TIME_RUN */
301 fprintf(lf, "%ld\n", (long) master.last_size); /* RN_LAST_ACTIVE_SIZE */
302
303 fprintf(lf, "%ld\n", (long) lastg->creation_time); /* RN_LAST_CREATION_TIME */
304 fprintf(lf, "%s\n", lastg->group_name); /* RN_LAST_NEW_GROUP */
305
306 if (stat(relative(news_lib_directory, "active.times"), &st) == 0)
307 fprintf(lf, "%ld\n", (long) st.st_size);
308 else /* can't be perfect -- don't update */
309 fputs(rnlast_line[RN_ACTIVE_TIMES_OFFSET], lf);
310 for (i = 0; i < MAX_RNLAST_LINE; i++)
311 freeobj(rnlast_line[i]);
312 } else {
313 lf = open_file(relative(nn_directory, "LAST"), OPEN_CREATE | MUST_EXIST);
314 fprintf(lf, "%ld\n%s\n", (long) lastg->creation_time, lastg->group_name);
315 }
316
317 fclose(lf);
318 }
319
320 static article_number
get_last_article(group_header * gh)321 get_last_article(group_header * gh)
322 {
323 register char *line;
324
325 if ((line = gh->newsrc_line) == NULL)
326 return -1;
327
328 line += gh->group_name_length + 1;
329 while (*line && isspace(*line))
330 line++;
331 if (*line == NUL)
332 return -1;
333
334 if (line[0] == '1') {
335 if (line[1] == RC_RANGE)
336 return atol(line + 2);
337 if (!isdigit(line[1]))
338 return 1;
339 }
340 return 0;
341 }
342
343
344 void
visit_rc_file(void)345 visit_rc_file(void)
346 {
347 FILE *rc, *sel;
348 register group_header *gh;
349 int subscr;
350 register char *bp;
351 register int c;
352 char bak[FILENAME];
353 time_t last_new_group = 0, rc_age, newsrc_age;
354 group_header *last_new_gh = NULL;
355
356 if (newsrc_file == NULL)
357 newsrc_file = home_relative(".newsrc");
358
359 sel_path = mk_file_name(nn_directory, "select");
360
361 Loop_Groups_Header(gh) {
362 gh->newsrc_line = NULL;
363 gh->newsrc_orig = NULL;
364 gh->select_line = NULL;
365 gh->select_orig = NULL;
366 }
367
368 if ((rc_age = file_exist(relative(nn_directory, "rc"), (char *) NULL))) {
369 if (who_am_i != I_AM_NN)
370 nn_exitmsg(1, "A release 6.3 rc file exists. Run nn to upgrade");
371
372 sprintf(bak, "%s/upgrade_rc", lib_directory);
373
374 if ((newsrc_age = file_exist(newsrc_file, (char *) NULL)) == 0) {
375 display_file("adm.upgrade1", CLEAR_DISPLAY);
376 } else {
377 if (rc_age + 60 > newsrc_age) {
378 /* rc file is newest (or .newsrc does not exist) */
379 display_file("adm.upgrade2", CLEAR_DISPLAY);
380 prompt("Convert rc file to .newsrc now? ");
381 if (yes(1) <= 0)
382 nn_exit(0);
383 } else {
384 /* .newsrc file is newest */
385 display_file("adm.upgrade3", CLEAR_DISPLAY);
386 prompt("Use current .newsrc file? ");
387 if (yes(1) > 0) {
388 strcat(bak, " n");
389 } else {
390 display_file("adm.upgrade4", CLEAR_DISPLAY);
391 prompt("Convert rc file to .newsrc? ");
392 if (yes(1) <= 0)
393 nn_exitmsg(0, "Then you will have to upgrade manually");
394 }
395 }
396 }
397
398 tprintf("\r\n\n");
399 system(bak);
400 any_key(prompt_line);
401 }
402 rc = open_file(newsrc_file, OPEN_READ);
403 if (rc != NULL) {
404 fseek(rc, 0, 2);
405 if (ftell(rc))
406 rewind(rc);
407 else {
408 fclose(rc);
409 rc = NULL;
410 }
411 }
412 if (rc == NULL) {
413 sprintf(bak, "%s%s", newsrc_file, bak_suffix ? bak_suffix : ".bak");
414 if ((rc = open_file(bak, OPEN_READ))) {
415 int ans;
416 time_t rc_mtime;
417
418 tprintf("\nThere is no .newsrc file - restore backup? (y) ");
419 if ((ans = yes(0)) < 0)
420 nn_exit(1);
421 if (!ans) {
422 tprintf("\nConfirm building .newsrc from scratch: (y/n) ");
423 if ((ans = yes(1)) <= 0)
424 nn_exit(1);
425 fclose(rc);
426 rc = NULL;
427 } else {
428 switch (new_group_action) {
429 case RCX_TIME:
430 case RCX_TIME_CONF:
431 case RCX_RNLAST:
432 last_new_group = get_last_new();
433 rc_mtime = (rc ? m_time(rc) : 0);
434 if (last_new_group < 0 || rc_mtime < last_new_group)
435 last_new_group = rc_mtime;
436 }
437 }
438 }
439 }
440 if (rc == NULL) {
441
442 last_new_group = -1; /* ignore LAST & .rnlast if no .newsrc */
443 rc = open_file_search_path(initial_newsrc_path, OPEN_READ);
444 if (rc == NULL)
445 goto new_user;
446 /* ignore groups not in the initial newsrc file */
447 last_new_gh = ACTIVE_GROUP(master.number_of_groups - 1);
448 last_new_group = last_new_gh->creation_time;
449 }
450 while (fgets(rcbuf, RC_LINE_MAX, rc) != NULL) {
451 gh = NULL;
452 subscr = 0;
453 for (bp = rcbuf; ((c = *bp)); bp++) {
454 if (isspace(c))
455 break; /* not a valid line */
456
457 if (c == RC_UNSUBSCR || c == RC_SUBSCR) {
458 subscr = (c == RC_SUBSCR);
459 *bp = NUL;
460 gh = lookup_no_alias(rcbuf);
461 *bp = c;
462 break;
463 }
464 }
465
466 if (gh == NULL) {
467 gh = newobj(group_header, 1);
468 gh->group_flag |= G_FAKED;
469 gh->master_flag |= M_VALID;
470 }
471 if (gh->master_flag & M_ALIASED)
472 continue;
473
474 if (rc_seq_tail == NULL)
475 rc_sequence = rc_seq_tail = gh;
476 else {
477 rc_seq_tail->newsrc_seq = gh;
478 rc_seq_tail = gh;
479 }
480
481 gh->newsrc_orig = gh->newsrc_line = copy_str(rcbuf);
482 if (gh->group_flag & G_FAKED)
483 gh->group_name = gh->newsrc_line;
484 else if (!subscr)
485 gh->group_flag |= G_UNSUBSCRIBED;
486 }
487 fclose(rc);
488
489 new_user:
490 Loop_Groups_Header(gh) {
491 if (gh->master_flag & M_IGNORE_GROUP)
492 continue;
493 if (gh->group_flag & G_UNSUBSCRIBED)
494 continue;
495 if (gh->newsrc_line == NULL) {
496 char buf[FILENAME];
497
498 /* NEW GROUP - ADD TO NEWSRC AS APPROPRIATE */
499
500 switch (new_group_action) {
501 case RCX_NEVER:
502 /* no not add new groups */
503 gh->group_flag |= G_DONE | G_UNSUBSCRIBED;
504 continue;
505
506 case RCX_HEAD:
507 /* insert at top */
508 gh->newsrc_seq = rc_sequence;
509 rc_sequence = gh;
510 break;
511
512
513 case RCX_TIME:
514 case RCX_TIME_CONF:
515 case RCX_RNLAST:
516 if (last_new_group == 0) {
517 last_new_group = get_last_new();
518 if (last_new_group < 0 && new_group_action != RCX_RNLAST) {
519 /* maybe this is a first time rn convert ? */
520 int nga = new_group_action;
521 new_group_action = RCX_RNLAST;
522 last_new_group = get_last_new();
523 new_group_action = nga;
524 }
525 }
526 if (gh->creation_time <= last_new_group) {
527 /* old groups not in .newsrc are unsubscribed */
528 gh->group_flag |= G_UNSUBSCRIBED;
529 continue;
530 }
531 if (last_new_gh == NULL || last_new_gh->creation_time <= gh->creation_time)
532 last_new_gh = gh;
533
534 if (new_group_action != RCX_TIME && !no_update) {
535 tprintf("\nNew group: %s -- append to .newsrc? (y)",
536 gh->group_name);
537 if (yes(0) <= 0) {
538 gh->group_flag |= G_DONE | G_UNSUBSCRIBED;
539 continue;
540 }
541 }
542 sprintf(buf, "%s:\n", gh->group_name);
543 /* to avoid fooling the LAST mechanism, we must fake */
544 /* that the group was also in the original .newsrc */
545
546 gh->newsrc_orig = gh->newsrc_line = copy_str(buf);
547 newsrc_update_count++;
548
549 /* FALLTHRU */
550 case RCX_TAIL:
551 /* insert at bottom */
552 if (rc_seq_tail == NULL)
553 rc_sequence = rc_seq_tail = gh;
554 else {
555 rc_seq_tail->newsrc_seq = gh;
556 rc_seq_tail = gh;
557 }
558 break;
559 }
560
561 gh->last_article = -1;
562 } else
563 gh->last_article = get_last_article(gh);
564
565 if (gh->last_article < 0) {
566 gh->group_flag |= G_NEW;
567 gh->last_article = gh->first_db_article - 1;
568 } else if (gh->first_db_article > gh->last_article)
569 gh->last_article = gh->first_db_article - 1;
570
571 if (gh->last_article < 0)
572 gh->last_article = 0;
573 gh->first_article = gh->last_article;
574 }
575
576 if (rc_seq_tail)
577 rc_seq_tail->newsrc_seq = NULL;
578
579 if (last_new_gh != NULL)
580 update_last_new(last_new_gh);
581
582 if (!use_selections)
583 return;
584
585 sel = open_file(sel_path, OPEN_READ);
586 if (sel == NULL)
587 return;
588
589 while (fgets(selbuf, RC_LINE_MAX, sel) != NULL) {
590 for (bp = selbuf; ((c = *bp)); bp++)
591 if (c == SP || c == SEL_NEW)
592 break;
593
594 if (c == NUL)
595 continue;
596 *bp = NUL;
597 gh = lookup_no_alias(selbuf);
598 if (gh == NULL || gh->master_flag & M_ALIASED)
599 continue;
600 *bp = c;
601 if (c == SEL_NEW)
602 gh->group_flag |= G_NEW;
603 gh->select_orig = gh->select_line = copy_str(selbuf);
604 }
605 fclose(sel);
606 }
607
608 /*
609 * prepare to use newsrc & select information for a specific group
610 */
611
612 static struct rc_info {
613 char *rc_p; /* pointer into newsrc_line */
614 article_number rc_min; /* current newsrc range min */
615 article_number rc_max; /* current newsrc range max */
616 char rc_delim; /* delimiter character */
617
618 char *sel_p; /* pointer into select_line */
619 char *sel_initp; /* rc_p after initialization */
620 article_number sel_min; /* current select range min */
621 article_number sel_max; /* current select range max */
622 article_number sel_digest; /* current digest */
623 attr_type sel_type; /* current select range type */
624 char sel_delim; /* delimiter character */
625 } orig, cur;
626
627 static int rctest_mode;
628
629 static void
init_rctest(group_header * gh,register struct rc_info * r)630 init_rctest(group_header * gh, register struct rc_info * r)
631 {
632 if (r->rc_p == NULL) {
633 r->rc_min = r->rc_max = END_OF_LIST;
634 } else {
635 r->rc_min = r->rc_max = -1;
636 r->rc_delim = SP;
637 r->rc_p += gh->group_name_length + 1;
638 }
639
640 r->sel_digest = 0;
641 if (r->sel_p == NULL) {
642 r->sel_min = r->sel_max = END_OF_LIST;
643 } else {
644 r->sel_p += gh->group_name_length + 1;
645 r->sel_min = r->sel_max = -1;
646 r->sel_delim = SP;
647 }
648 }
649
650 void
use_newsrc(register group_header * gh,int mode)651 use_newsrc(register group_header * gh, int mode)
652 {
653 orig.rc_p = gh->newsrc_orig;
654 orig.sel_p = gh->select_orig;
655 cur.rc_p = gh->newsrc_line;
656 cur.sel_p = gh->select_line;
657
658 rctest_mode = mode;
659
660 switch (mode) {
661 case 0:
662 init_rctest(gh, &cur);
663 break;
664 case 1:
665 init_rctest(gh, &orig);
666 break;
667 case 2:
668 init_rctest(gh, &cur);
669 init_rctest(gh, &orig);
670 break;
671 }
672 }
673
674 /*
675 #define TRC(wh) TR( ("r%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, rc_min, rc_max, rc_delim) )
676 #define TSEL(wh) TR( ("s%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, sel_min, sel_max, sel_delim) )
677 */
678 #define TRC(wh)
679 #define TSEL(wh)
680
681 static attr_type
rctest(register article_header * ah,register struct rc_info * r)682 rctest(register article_header * ah, register struct rc_info * r)
683 {
684 register char *p;
685 register int c;
686 register article_number n = ah->a_number, x;
687
688 while (n > r->rc_max) {
689 /* get next interval from newsrc line */
690 r->rc_min = -1;
691 x = 0;
692 p = r->rc_p;
693 TRC(1);
694
695 if (*p == RC_DELIM)
696 p++;
697 if (*p == NUL || *p == NL)
698 r->rc_min = r->rc_max = END_OF_LIST;
699 else {
700 for (; ((c = *p)) && c != RC_DELIM && c != NL; p++) {
701 if (c == RC_RANGE) {
702 if (r->rc_min < 0)
703 r->rc_min = x;
704 else
705 msg("syntax error in rc file");
706 x = 0;
707 continue;
708 }
709 if (isascii(*p) && isdigit(*p))
710 x = x * 10 + c - '0';
711 }
712 r->rc_max = x;
713 if (r->rc_min < 0)
714 r->rc_min = x;
715 r->rc_p = p;
716 }
717 }
718 TRC(2);
719
720 if (n >= r->rc_min && n <= r->rc_max)
721 return A_READ;
722
723 p = r->sel_p;
724 if (r->sel_digest != 0) {
725 if (n == r->sel_digest && (ah->flag & A_DIGEST)) {
726 if (*(r->sel_p) == SEL_END_DIGEST)
727 return A_READ;
728 n = ah->fpos;
729 } else {
730 if (n < r->sel_digest)
731 return 0;
732 while (*p && *p++ != SEL_END_DIGEST);
733 r->sel_digest = 0;
734 r->sel_min = r->sel_max = -1;
735 }
736 }
737 while (n > r->sel_max) {
738 r->sel_min = -1;
739 r->sel_type = A_SELECT;
740 x = 0;
741 TSEL(3);
742
743 for (;;) {
744 switch (*p) {
745 case SEL_SELECT:
746 r->sel_type = A_SELECT;
747 p++;
748 continue;
749 case SEL_LEAVE:
750 r->sel_type = A_LEAVE;
751 p++;
752 continue;
753 case SEL_SEEN:
754 r->sel_type = A_SEEN;
755 p++;
756 continue;
757 case SEL_UNREAD:
758 r->sel_type = 0;
759 p++;
760 continue;
761 case SEL_DIGEST:
762 while (*p && *p++ != SEL_END_DIGEST);
763 continue;
764 case SEL_END_DIGEST:
765 if (r->sel_digest) {
766 if (r->sel_digest == ah->a_number) {
767 r->sel_p = p;
768 return A_READ;
769 }
770 r->sel_digest = 0;
771 }
772 p++;
773 r->sel_type = A_SELECT;
774 continue;
775 default:
776 break;
777 }
778 break;
779 }
780
781 if (*p == NUL || *p == NL) {
782 r->sel_min = r->sel_max = END_OF_LIST;
783 break;
784 }
785 for (; ((c = *p)); p++) {
786 switch (c) {
787 case '0':
788 case '1':
789 case '2':
790 case '3':
791 case '4':
792 case '5':
793 case '6':
794 case '7':
795 case '8':
796 case '9':
797 x = x * 10 + c - '0';
798 continue;
799
800 case SEL_SELECT:
801 case SEL_LEAVE:
802 case SEL_SEEN:
803 case SEL_UNREAD:
804 break;
805
806 case SEL_RANGE:
807 if (r->sel_min < 0)
808 r->sel_min = x;
809 else
810 msg("syntax error in sel file");
811 x = 0;
812 continue;
813
814 case SEL_DIGEST:
815 n = ah->a_number;
816 if (n > x) {
817 while (*p && (*p++ != SEL_END_DIGEST));
818 x = -1;
819 break;
820 }
821 p++;
822 r->sel_digest = x;
823 if (n < r->sel_digest) {
824 r->sel_p = p;
825 return 0;
826 }
827 n = ah->fpos;
828 x = -1;
829 break;
830
831 case NL:
832 if (r->sel_digest == 0)
833 break;
834 /* FALLTHRU */
835 case SEL_END_DIGEST:
836 if (r->sel_digest == ah->a_number) {
837 r->sel_p = p;
838 return (ah->fpos == x) ? r->sel_type : A_READ;
839 }
840 r->sel_digest = 0;
841 x = -1;
842 break;
843 }
844 break;
845 }
846 r->sel_max = x;
847 if (r->sel_min < 0)
848 r->sel_min = x;
849 r->sel_p = p;
850 }
851
852 if (n >= r->sel_min && n <= r->sel_max)
853 return r->sel_type;
854
855 if (r->sel_digest)
856 return A_READ; /* only read articles are not listed */
857
858 return 0; /* unread, unseen, unselected */
859 }
860
861 attr_type
test_article(article_header * ah)862 test_article(article_header * ah)
863 {
864 attr_type a;
865
866 switch (rctest_mode) {
867 case 0:
868 return rctest(ah, &cur);
869 case 1:
870 return rctest(ah, &orig);
871 case 2:
872 a = rctest(ah, &cur);
873 if (a != A_READ)
874 return a;
875 if (rctest(ah, &orig) == A_READ)
876 return A_KILL;
877 return a;
878 }
879 return 0;
880 }
881
882 /*
883 * We only mark the articles that should remain unread
884 */
885 static char *rc_p; /* pointer into newsrc_line */
886 static article_number rc_min; /* current newsrc range min */
887 static char rc_delim; /* delimiter character */
888
889 static char *sel_p; /* pointer into select_line */
890 static char *sel_initp; /* rc_p after initialization */
891 static article_number sel_min; /* current select range min */
892 static article_number sel_max; /* current select range max */
893 static article_number sel_digest; /* current digest */
894 static char sel_delim; /* delimiter character */
895
896 static void
append(int x,char * fmt,...)897 append(int x, char *fmt,...)
898 {
899 va_list ap;
900 register char *p;
901 char **pp;
902
903 va_start(ap, fmt);
904 pp = x ? &sel_p : &rc_p;
905 p = *pp;
906 if (p > (x ? &selbuf[RC_LINE_MAX - 16] : &rcbuf[RC_LINE_MAX - 16])) {
907 msg("%s line too long", x ? "select" : ".newsrc");
908 va_end(ap);
909 return;
910 }
911 vsprintf(p, fmt, ap);
912 va_end(ap);
913
914 while (*p)
915 p++;
916 *p = NL;
917 p[1] = NUL;
918 *pp = p;
919 }
920
921 static void
append_range(int pp,char delim,article_number rmin,article_number rmax)922 append_range(int pp, char delim, article_number rmin, article_number rmax)
923 {
924 if (rmin == rmax)
925 append(pp, "%c%ld", delim, (long) rmin);
926 else
927 append(pp, "%c%ld%c%ld", delim, (long) rmin, RC_RANGE, (long) rmax);
928 }
929
930 static int32 mark_counter;
931
932 static void
begin_rc_update(register group_header * gh)933 begin_rc_update(register group_header * gh)
934 {
935 add_unread(gh, -1);
936 mark_counter = 0;
937
938 rc_p = rcbuf;
939 rc_min = 1;
940 append(0, "%s%c", gh->group_name,
941 gh->group_flag & G_UNSUBSCRIBED ? RC_UNSUBSCR : RC_SUBSCR);
942 rc_delim = SP;
943 sel_p = selbuf;
944 sel_min = 0;
945 sel_max = 0;
946 sel_digest = 0;
947 sel_delim = SP;
948 append(1, "%s%c", gh->group_name,
949 (gh->group_flag & G_NEW) ? SEL_NEW : SP);
950 /* sel_initp == sep_p => empty list */
951 sel_initp = (gh->group_flag & G_NEW) ? NULL : sel_p;
952 }
953
954 static void
end_rc_update(register group_header * gh)955 end_rc_update(register group_header * gh)
956 {
957 if (rc_min <= gh->last_db_article)
958 append_range(0, rc_delim, rc_min, gh->last_db_article);
959
960 if (gh->newsrc_line != NULL && strcmp(rcbuf, gh->newsrc_line)) {
961 if (gh->newsrc_orig != gh->newsrc_line)
962 freeobj(gh->newsrc_line);
963 gh->newsrc_line = NULL;
964 }
965 if (gh->newsrc_line == NULL) {
966 gh->newsrc_line = copy_str(rcbuf);
967 dump_newsrc();
968 }
969 if (sel_digest)
970 append(1, "%c", SEL_END_DIGEST);
971 else if (sel_min)
972 append_range(1, sel_delim, sel_min, sel_max);
973
974 if (gh->select_line) {
975 if (strcmp(selbuf, gh->select_line) == 0)
976 goto out;
977 } else if (sel_p == sel_initp)
978 goto out;
979
980 if (gh->select_line && gh->select_orig != gh->select_line)
981 freeobj(gh->select_line);
982
983 gh->select_line = (sel_p == sel_initp) ? NULL : copy_str(selbuf);
984 dump_select();
985
986 out:
987 if ((gh->last_article = get_last_article(gh)) < 0)
988 gh->last_article = 0;
989 if (gh->last_article < gh->first_article)
990 gh->first_article = gh->last_article;
991
992 gh->group_flag |= G_READ; /* should not call update_group again */
993 if (mark_counter > 0) {
994 gh->unread_count = mark_counter;
995 add_unread(gh, 0);
996 }
997 }
998
999 static void
mark_article(register article_header * ah,attr_type how)1000 mark_article(register article_header * ah, attr_type how)
1001 {
1002 register article_number anum;
1003 char delim = 0;
1004
1005 switch (how) {
1006 case A_SELECT:
1007 delim = SEL_SELECT;
1008 break;
1009 case A_SEEN:
1010 delim = SEL_SEEN;
1011 break;
1012 case A_LEAVE:
1013 case A_LEAVE_NEXT:
1014 delim = SEL_LEAVE;
1015 break;
1016 case 0:
1017 delim = SEL_UNREAD;
1018 break;
1019 }
1020
1021 mark_counter++;
1022 anum = ah->a_number;
1023
1024 if (rc_min < anum) {
1025 append_range(0, rc_delim, rc_min, anum - 1);
1026 rc_delim = RC_DELIM;
1027
1028 if ((ah->flag & A_DIGEST) == 0
1029 && sel_min && delim == sel_delim && sel_max == (rc_min - 1))
1030 sel_max = anum - 1; /* expand select range over read articles */
1031 }
1032 rc_min = anum + 1;
1033
1034 if (ah->flag & A_DIGEST) {
1035 if (sel_digest != anum) {
1036 if (sel_digest) {
1037 append(1, "%c", SEL_END_DIGEST);
1038 } else if (sel_min) {
1039 append_range(1, sel_delim, sel_min, sel_max);
1040 sel_min = 0;
1041 }
1042 append(1, "%c%ld%c", SEL_SELECT, (long) anum, SEL_DIGEST);
1043 sel_digest = anum;
1044 }
1045 append(1, "%c%ld", delim, ah->fpos);
1046 return;
1047 }
1048 if (sel_digest) {
1049 append(1, "%c", SEL_END_DIGEST);
1050 sel_digest = 0;
1051 }
1052 if (sel_min) {
1053 if (delim != sel_delim || delim == SEL_UNREAD) {
1054 append_range(1, sel_delim, sel_min, sel_max);
1055 sel_delim = delim;
1056 if (delim == SEL_UNREAD)
1057 sel_min = 0;
1058 else
1059 sel_min = anum;
1060 } else
1061 sel_max = anum;
1062 } else if (delim != SEL_UNREAD) {
1063 sel_min = sel_max = anum;
1064 sel_delim = delim;
1065 }
1066 }
1067
1068 void
flush_newsrc(void)1069 flush_newsrc(void)
1070 {
1071 newsrc_update_freq = 0;
1072 if (select_update_count)
1073 dump_select();
1074 if (newsrc_update_count)
1075 dump_newsrc();
1076 }
1077
1078 int
restore_bak(void)1079 restore_bak(void)
1080 {
1081 if (no_update)
1082 return 1;
1083
1084 prompt("Are you sure? ");
1085 if (!yes(1))
1086 return 0;
1087
1088 if (dump_file(newsrc_file, DM_ORIG_NEWSRC))
1089 nn_exitmsg(1, "Could not restore %s -- restore %s file manually",
1090 newsrc_file, bak_suffix);
1091
1092 prompt("Restore selections? ");
1093 if (yes(1))
1094 dump_file(sel_path, DM_ORIG_SELECT);
1095
1096 no_update = 1; /* so current group is not updated */
1097 return 1;
1098 }
1099
1100 /*
1101 * Update .newsrc for one group.
1102 * sort_articles(-2) MUST HAVE BEEN CALLED BEFORE USE.
1103 */
1104
1105 int rc_merged_groups_hack = 0;
1106
1107 void
update_rc(register group_header * gh)1108 update_rc(register group_header * gh)
1109 {
1110 register article_header *ah, **ahp;
1111 register article_number art;
1112 static int junk_seen = 0;
1113
1114 if (!rc_merged_groups_hack)
1115 junk_seen = 0;
1116
1117 if (gh->group_flag & (G_FOLDER | G_FAKED))
1118 return;
1119
1120 begin_rc_update(gh);
1121
1122 for (ahp = articles, art = 0; art < n_articles; ahp++, art++) {
1123 ah = *ahp;
1124 if (ah->a_group != NULL && ah->a_group != gh)
1125 continue;
1126
1127 switch (ah->attr) {
1128 case A_READ:
1129 case A_KILL:
1130 continue;
1131
1132 case A_LEAVE:
1133 case A_LEAVE_NEXT:
1134 case A_SELECT:
1135 mark_article(ah, ah->attr);
1136 continue;
1137
1138 case A_SEEN:
1139 if (junk_seen == 0) {
1140 junk_seen = -1;
1141 if (auto_junk_seen) {
1142 if (conf_junk_seen) {
1143 prompt("\1Junk seen articles\1 ");
1144 if (yes(0) > 0)
1145 junk_seen = 1;
1146 } else
1147 junk_seen = 1;
1148 }
1149 }
1150 if (junk_seen > 0)
1151 continue;
1152 mark_article(ah, (attr_type) (retain_seen_status ? A_SEEN : 0));
1153 continue;
1154
1155 case A_AUTO_SELECT:
1156 default:
1157 mark_article(ah, (attr_type) 0);
1158 continue;
1159 }
1160 }
1161
1162 end_rc_update(gh);
1163 }
1164
1165 void
update_rc_all(register group_header * gh,int unsub)1166 update_rc_all(register group_header * gh, int unsub)
1167 {
1168 if (unsub) {
1169 gh->group_flag &= ~G_NEW;
1170 gh->group_flag |= G_UNSUBSCRIBED;
1171
1172 if (!keep_unsubscribed) {
1173 add_unread(gh, -1);
1174 if (gh->newsrc_line != NULL && gh->newsrc_orig != gh->newsrc_line)
1175 freeobj(gh->newsrc_line);
1176 gh->newsrc_line = NULL;
1177 if (gh->newsrc_orig != NULL)
1178 dump_newsrc();
1179 if (gh->select_line != NULL && gh->newsrc_orig != gh->select_line)
1180 freeobj(gh->select_line);
1181 gh->select_line = NULL;
1182 if (gh->select_orig != NULL)
1183 dump_select();
1184 return;
1185 }
1186 if (keep_unsub_long) {
1187 update_rc(gh);
1188 return;
1189 }
1190 }
1191 begin_rc_update(gh);
1192 end_rc_update(gh);
1193 }
1194
1195 void
add_to_newsrc(group_header * gh)1196 add_to_newsrc(group_header * gh)
1197 {
1198 gh->group_flag &= ~G_UNSUBSCRIBED;
1199
1200 if (gh->newsrc_seq != NULL || gh == rc_seq_tail) {
1201 update_rc(gh);
1202 return;
1203 }
1204 rc_seq_tail->newsrc_seq = gh;
1205 rc_seq_tail = gh;
1206 if (gh->last_db_article > 0)
1207 sprintf(rcbuf, "%s: %s%ld\n", gh->group_name,
1208 gh->last_db_article > 1 ? "1-" : "",
1209 (long) gh->last_db_article);
1210 else
1211 sprintf(rcbuf, "%s:\n", gh->group_name);
1212 gh->newsrc_line = copy_str(rcbuf);
1213 dump_newsrc();
1214 }
1215
1216 article_number
restore_rc(register group_header * gh,article_number last)1217 restore_rc(register group_header * gh, article_number last)
1218 {
1219 register article_number *numtab, n;
1220 register attr_type *attrtab, attr;
1221 register int32 at, atmax;
1222 article_header ahdr;
1223 article_number count;
1224
1225 if (last > gh->last_db_article)
1226 return 0;
1227
1228 if (gh->unread_count <= 0) {
1229 /* no unread articles to account for -- quick update */
1230 n = gh->last_db_article;/* fake for end_rc_update */
1231 gh->last_db_article = last;
1232 begin_rc_update(gh);
1233 end_rc_update(gh);
1234 gh->last_db_article = n;
1235 add_unread(gh, 1); /* not done by end_rc_update bec.
1236 * mark_counter==0 */
1237 return gh->unread_count;
1238 }
1239 /* there are unread articles in the group */
1240 /* we must truncate rc&select lines to retain older unread articles */
1241
1242 atmax = at = 0;
1243 numtab = NULL;
1244 attrtab = NULL;
1245
1246 use_newsrc(gh, 0);
1247 ahdr.flag = 0;
1248 count = gh->unread_count;
1249
1250 for (n = gh->last_article + 1; n <= last; n++) {
1251 if (cur.rc_min == END_OF_LIST) {
1252 /* current & rest is unread */
1253 last = n - 1;
1254 break;
1255 }
1256 ahdr.a_number = n;
1257 if ((attr = rctest(&ahdr, &cur)) == A_READ)
1258 continue;
1259 if (at >= atmax) {
1260 atmax += 100;
1261 numtab = resizeobj(numtab, article_number, atmax);
1262 attrtab = resizeobj(attrtab, attr_type, atmax);
1263 }
1264 numtab[at] = n;
1265 attrtab[at] = attr;
1266 at++;
1267 }
1268
1269 begin_rc_update(gh);
1270 while (--at >= 0) {
1271 ahdr.a_number = *numtab++;
1272 mark_article(&ahdr, *attrtab++);
1273 }
1274 for (n = last + 1; n <= gh->last_db_article; n++) {
1275 ahdr.a_number = n;
1276 mark_article(&ahdr, (attr_type) 0);
1277 }
1278 end_rc_update(gh);
1279 return gh->unread_count - count;
1280 }
1281
1282 int
restore_unread(register group_header * gh)1283 restore_unread(register group_header * gh)
1284 {
1285 if (gh->select_line != gh->select_orig) {
1286 if (gh->select_line != NULL)
1287 freeobj(gh->select_line);
1288 gh->select_line = gh->select_orig;
1289 dump_select();
1290 }
1291 if (gh->newsrc_orig == gh->newsrc_line)
1292 return 0;
1293
1294 add_unread(gh, -1);
1295 if (gh->newsrc_line != NULL)
1296 freeobj(gh->newsrc_line);
1297 gh->newsrc_line = gh->newsrc_orig;
1298 gh->last_article = gh->first_article;
1299 dump_newsrc();
1300
1301 add_unread(gh, 1);
1302
1303 return 1;
1304 }
1305
1306 void
count_unread_articles(void)1307 count_unread_articles(void)
1308 {
1309 register group_header *gh;
1310 long n = 0;
1311
1312 unread_articles = 0;
1313 unread_groups = 0;
1314
1315 Loop_Groups_Sequence(gh) {
1316 gh->unread_count = 0;
1317
1318 if (gh->master_flag & M_NO_DIRECTORY)
1319 continue;
1320
1321 if (gh->last_db_article > gh->last_article) {
1322 n = unread_articles;
1323 add_unread(gh, 1);
1324 }
1325 if ((gh->group_flag & G_COUNTED) == 0)
1326 continue;
1327 if (verbose)
1328 tprintf("%6d %s\n", unread_articles - n, gh->group_name);
1329 }
1330 }
1331
1332
1333 void
prt_unread(register char * format)1334 prt_unread(register char *format)
1335 {
1336 if (format == NULL) {
1337 clrdisp();
1338 tprintf("No News (is good news)\n");
1339 return;
1340 }
1341 while (*format) {
1342 if (*format != '%') {
1343 tputc(*format++);
1344 continue;
1345 }
1346 format++;
1347 switch (*format++) {
1348 case 'u':
1349 tprintf("%ld unread article%s", unread_articles, plural((long) unread_articles));
1350 continue;
1351 case 'g':
1352 tprintf("%d group%s", unread_groups, plural((long) unread_groups));
1353 continue;
1354 case 'i':
1355 tprintf(unread_articles == 1 ? "is" : "are");
1356 continue;
1357 case 'U':
1358 tprintf("%ld", unread_articles);
1359 continue;
1360 case 'G':
1361 tprintf("%d", unread_groups);
1362 continue;
1363 }
1364 }
1365 }
1366
1367 article_number
add_unread(group_header * gh,int mode)1368 add_unread(group_header * gh, int mode)
1369 /* mode: +1 => count + add, 0 => gh->unread_count, -1 => subtract */
1370 {
1371 article_number old_count;
1372 article_header ahdr;
1373
1374 old_count = gh->unread_count;
1375
1376 if (mode == 0)
1377 goto add_directly;
1378
1379 if (gh->group_flag & G_COUNTED) {
1380 unread_articles -= gh->unread_count;
1381 unread_groups--;
1382 gh->unread_count = 0;
1383 gh->group_flag &= ~G_COUNTED;
1384 }
1385 if (mode < 0)
1386 goto out;
1387
1388 if (quick_unread_count)
1389 gh->unread_count = gh->last_db_article - gh->last_article;
1390 else {
1391 use_newsrc(gh, 0);
1392 ahdr.flag = 0;
1393 for (ahdr.a_number = gh->last_article + 1;
1394 ahdr.a_number <= gh->last_db_article;
1395 ahdr.a_number++) {
1396 if (cur.rc_min == END_OF_LIST) {
1397 gh->unread_count += gh->last_db_article - ahdr.a_number + 1;
1398 break;
1399 }
1400 if (rctest(&ahdr, &cur) != A_READ)
1401 gh->unread_count++;
1402 }
1403 }
1404
1405 add_directly:
1406 if (gh->unread_count <= 0) {
1407 gh->unread_count = 0;
1408 goto out;
1409 }
1410 if (gh->group_flag & G_UNSUBSCRIBED)
1411 goto out;
1412
1413 unread_articles += gh->unread_count;
1414 unread_groups++;
1415 gh->group_flag |= G_COUNTED;
1416
1417 out:
1418 return old_count != gh->unread_count;
1419 }
1420
1421 /*
1422 * nngrep
1423 */
1424
1425 static int
1426 grep_all = 0, grep_new = 0, grep_not_sequence = 0,
1427 grep_pending = 0, grep_read = 0, grep_sequence = 0,
1428 grep_unsub = 0, grep_long = 0, grep_patterns;
1429
1430 static
Option_Description(grep_options)1431 Option_Description(grep_options)
1432 {
1433 'a', Bool_Option(grep_all),
1434 'i', Bool_Option(grep_not_sequence),
1435 'n', Bool_Option(grep_new),
1436 'p', Bool_Option(grep_pending),
1437 'r', Bool_Option(grep_read),
1438 's', Bool_Option(grep_sequence),
1439 'u', Bool_Option(grep_unsub),
1440 'l', Bool_Option(grep_long),
1441 '\0',
1442 };
1443
1444 void
opt_nngrep(int argc,char * argv[])1445 opt_nngrep(int argc, char *argv[])
1446 {
1447 grep_patterns =
1448 parse_options(argc, argv, (char *) NULL, grep_options, " pattern...");
1449 }
1450
1451 void
do_grep(char ** pat)1452 do_grep(char **pat)
1453 {
1454 register group_header *gh;
1455 register regexp **re = NULL;
1456 register int i;
1457 int header = 1;
1458
1459 if (grep_patterns > 0) {
1460 re = newobj(regexp *, grep_patterns);
1461 for (i = 0; i < grep_patterns; i++)
1462 re[i] = regcomp(pat[i]);
1463 }
1464 Loop_Groups_Sorted(gh) {
1465 if (gh->master_flag & M_IGNORE_GROUP)
1466 continue;
1467
1468 if (grep_pending && gh->unread_count <= 0)
1469 continue;
1470 if (grep_read && gh->unread_count > 0)
1471 continue;
1472 if (grep_sequence && (gh->group_flag & G_SEQUENCE) == 0)
1473 continue;
1474 if (grep_not_sequence && (gh->group_flag & G_SEQUENCE))
1475 continue;
1476 if (grep_new && (gh->group_flag & G_NEW) == 0)
1477 continue;
1478 if (!grep_all) {
1479 if (grep_unsub && (gh->group_flag & G_UNSUBSCRIBED) == 0)
1480 continue;
1481 if (!grep_unsub && (gh->group_flag & G_UNSUBSCRIBED))
1482 continue;
1483 }
1484 if (grep_patterns > 0) {
1485 for (i = 0; i < grep_patterns; i++)
1486 if (regexec(re[i], gh->group_name))
1487 break;
1488 if (i == grep_patterns)
1489 continue;
1490 }
1491 if (grep_long) {
1492 if (header)
1493 tprintf("SUBSCR IN_RC NEW UNREAD SEQUENCE GROUP\n");
1494 header = 0;
1495
1496 tprintf(" %s %s %s ",
1497 (gh->group_flag & G_UNSUBSCRIBED) ? "no " : "yes",
1498 (gh->newsrc_line == NULL) ? "no " : "yes",
1499 (gh->group_flag & G_NEW) ? "yes" : "no ");
1500
1501 if (gh->unread_count > 0)
1502 tprintf("%6d ", gh->unread_count);
1503 else
1504 tprintf(" ");
1505 if (gh->group_flag & G_SEQUENCE)
1506 tprintf(" %4d ", gh->preseq_index);
1507 else
1508 tprintf(" ");
1509 }
1510 tprintf("%s\n", gh->group_name);
1511 }
1512 }
1513
1514
1515 /*
1516 * nntidy
1517 */
1518
1519 static int
1520 tidy_unsubscribed = 0, /* truncate lines for unsub groups */
1521 tidy_remove_unsub = 0, /* remove lines for unsub groups */
1522 tidy_sequence = 0, /* remove groups not in sequence */
1523 tidy_ignored = 0, /* remove G_IGN groups */
1524 tidy_crap = 0, /* remove unrecognized lines */
1525 tidy_all = 0; /* all of the above */
1526
1527 static
Option_Description(tidy_options)1528 Option_Description(tidy_options)
1529 {
1530 'N', Bool_Option(no_update),
1531 'Q', Bool_Option(silent),
1532 'v', Bool_Option(verbose),
1533 'a', Bool_Option(tidy_all),
1534 'c', Bool_Option(tidy_crap),
1535 'i', Bool_Option(tidy_ignored),
1536 'r', Bool_Option(tidy_remove_unsub),
1537 's', Bool_Option(tidy_sequence),
1538 'u', Bool_Option(tidy_unsubscribed),
1539 '\0',
1540 };
1541
1542 int
opt_nntidy(int argc,char * argv[])1543 opt_nntidy(int argc, char *argv[])
1544 {
1545 return parse_options(argc, argv, (char *) NULL,
1546 tidy_options, " [group]...");
1547 }
1548
1549 void
do_tidy_newsrc(void)1550 do_tidy_newsrc(void)
1551 {
1552 register group_header *gh;
1553 int changed;
1554 char *why;
1555
1556 /* visit_rc_file has been called. */
1557
1558 keep_rc_backup = 1;
1559 bak_suffix = ".tidy";
1560
1561 tidy_newsrc = 0;
1562 changed = 0;
1563
1564 if (tidy_all)
1565 tidy_sequence = tidy_ignored = tidy_crap = tidy_unsubscribed = 1;
1566
1567 newsrc_update_freq = 9999;
1568
1569 Loop_Groups_Newsrc(gh) {
1570 if ((gh->master_flag & M_VALID) == 0) {
1571 why = "Unknown group: ";
1572 goto delete;
1573 }
1574 if (tidy_sequence && (gh->group_flag & G_SEQUENCE) == 0) {
1575 why = "Not in sequence: ";
1576 goto delete;
1577 }
1578 if (tidy_ignored && (gh->master_flag & M_IGNORE_GROUP)) {
1579 why = "Ignored group: ";
1580 goto delete;
1581 }
1582 if (tidy_crap && (gh->group_flag & G_FAKED)) {
1583 why = "Crap in .newsrc: ";
1584 goto delete;
1585 }
1586 if (tidy_remove_unsub && (gh->group_flag & G_UNSUBSCRIBED)) {
1587 if (gh->group_flag & G_FAKED)
1588 continue;
1589 why = "Unsubscribed: ";
1590 goto delete;
1591 }
1592 if (tidy_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED)) {
1593 if (gh->group_flag & G_FAKED)
1594 continue;
1595
1596 begin_rc_update(gh);
1597 gh->last_db_article = 0;
1598 end_rc_update(gh);
1599
1600 if (gh->newsrc_line != gh->newsrc_orig) {
1601 why = "Truncated: ";
1602 goto change;
1603 }
1604 }
1605 if (verbose) {
1606 why = "Ok: ";
1607 goto report;
1608 }
1609 continue;
1610
1611 delete:
1612 gh->newsrc_line = NULL;
1613 gh->select_line = NULL;
1614
1615 change:
1616 changed = 1;
1617
1618 report:
1619 if (!silent)
1620 tprintf("%s%s\n", why, gh->group_name);
1621 }
1622
1623 if (no_update) {
1624 if (changed)
1625 tprintf("Files were NOT updated\n");
1626 return;
1627 }
1628 if (changed) {
1629 newsrc_update_freq = 0;
1630 dump_newsrc();
1631 dump_select();
1632 tprintf("NOTICE: Original files are saved with %s extention\n", bak_suffix);
1633 }
1634 }
1635
1636 /*
1637 * nngoback
1638 */
1639
1640 static int
1641 goback_interact = 0, /* interactive nngoback */
1642 goback_days = -1, goback_alsounsub = 0; /* unsubscribed groups
1643 * also */
1644
1645 static
Option_Description(goback_options)1646 Option_Description(goback_options)
1647 {
1648 'N', Bool_Option(no_update),
1649 'Q', Bool_Option(silent),
1650 'd', Int_Option(goback_days),
1651 'i', Bool_Option(goback_interact),
1652 'u', Bool_Option(goback_alsounsub),
1653 'v', Bool_Option(verbose),
1654 '\0',
1655 };
1656
1657 int
opt_nngoback(int argc,char *** argvp)1658 opt_nngoback(int argc, char ***argvp)
1659 {
1660 int n;
1661
1662 n = parse_options(argc, *argvp, (char *) NULL, goback_options,
1663 " days [groups]...");
1664
1665 if (goback_days < 0) {
1666 if (n == 0 || !isdigit((*argvp)[1][0])) {
1667 fprintf(stderr, "usage: %s [-NQvi] days [groups]...\n", pname);
1668 nn_exit(1);
1669 }
1670 goback_days = atoi((*argvp)[1]);
1671 n--;
1672 ++*argvp;
1673 }
1674 return n;
1675 }
1676
1677 void
do_goback(void)1678 do_goback(void)
1679 {
1680 char back_act[FILENAME];
1681 FILE *ba;
1682 register group_header *gh;
1683 article_number count, total;
1684 int groups, y;
1685
1686 sprintf(back_act, "%s/active.%d", db_directory, goback_days);
1687 if ((ba = open_file(back_act, OPEN_READ)) == NULL) {
1688 fprintf(stderr, "Cannot go back %d days\n", goback_days);
1689 nn_exit(1);
1690 }
1691 read_active_file(ba, (FILE *) NULL);
1692
1693 fclose(ba);
1694
1695 /* visit_rc_file has been called. */
1696
1697 keep_rc_backup = 1;
1698 bak_suffix = ".goback";
1699 newsrc_update_freq = 9999;
1700 quick_unread_count = 0;
1701 total = groups = 0;
1702
1703 if (goback_interact) {
1704 if (no_update)
1705 tprintf("Warning: changes will not be saved\n");
1706 init_term(1);
1707 nn_raw();
1708 }
1709 Loop_Groups_Sequence(gh) {
1710 if ((gh->master_flag & M_VALID) == 0)
1711 continue;
1712 if (!goback_alsounsub && (gh->group_flag & G_UNSUBSCRIBED))
1713 continue;
1714
1715 add_unread(gh, 1);
1716
1717 count = restore_rc(gh, gh->last_a_article);
1718 if (count > 0) {
1719 if (goback_interact) {
1720 tprintf("%s + %ld ? (y) ", gh->group_name, (long) count);
1721 fl;
1722 y = yes(0);
1723 tputc(CR);
1724 tputc(NL);
1725 switch (y) {
1726 case 1:
1727 break;
1728 case 0:
1729 gh->newsrc_line = gh->newsrc_orig;
1730 gh->select_line = gh->select_orig;
1731 continue;
1732 case -1:
1733 if (total > 0) {
1734 tprintf("\nSave changes sofar? (n) ");
1735 fl;
1736 if (yes(1) <= 0)
1737 nn_exit(0);
1738 }
1739 goto out;
1740 }
1741 } else if (verbose)
1742 tprintf("%5ld\t%s\n", (long) count, gh->group_name);
1743
1744 total += count;
1745 groups++;
1746 }
1747 }
1748
1749 out:
1750
1751 if (goback_interact)
1752 unset_raw();
1753
1754 if (total == 0) {
1755 tprintf("No articles marked\n");
1756 return;
1757 }
1758 flush_newsrc();
1759
1760 if (verbose)
1761 tputc(NL);
1762 if (!silent)
1763 tprintf("%ld article%s marked unread in %d group%s\n",
1764 (long) total, plural((long) total),
1765 groups, plural((long) groups));
1766
1767 if (no_update)
1768 tprintf("No files were updated\n");
1769 }
1770
1771 /* fake this for read_active_file */
1772
1773 group_header *
add_new_group(char * name)1774 add_new_group(char *name)
1775 {
1776 return NULL;
1777 }
1778