1 /*
2  *  Copyright (C) 1995, 1996  Karl-Johan Johnsson.
3  */
4 
5 #include "global.h"
6 #include <sys/stat.h>
7 #include "ahead.h"
8 #include "bg.h"
9 #include "codes.h"
10 #include "expand.h"
11 #include "file.h"
12 #include "newsrc.h"
13 #include "parse.h"
14 #include "resource.h"
15 #include "server.h"
16 #include "thread.h"
17 #include "util.h"
18 #include "widgets.h"
19 #include "xutil.h"
20 #include "../Widgets/ScrList.h"
21 
22 typedef enum {
23     AheadStateNone,
24     AheadStateSentGroup,
25     AheadStateSentXover,
26     AheadStateDoingXover,
27     AheadStateSentHead,
28     AheadStateDoingHead,
29     AheadStateSentNext
30 } AheadState;
31 
32 enum {
33     AheadFlagNoop,
34     AheadFlagScheduled,
35     AheadFlagThreading,
36     AheadFlagDone
37 };
38 
39 typedef struct GROUP_AHEAD_NODE GROUP_AHEAD_NODE;
40 struct GROUP_AHEAD_NODE {
41     GROUP_AHEAD_NODE	*next;
42     GROUP		*group;
43     char		*file_name;
44     unsigned char	flag;
45     unsigned char	needs_update;
46 };
47 
48 static GROUP_AHEAD_NODE	*schedule = NULL, *current = NULL;
49 
50 static AheadState	ahead_state = AheadStateNone;
51 static THREAD_CONTEXT	*bogus_context = NULL;
52 static long		n_to_put = 0;
53 static long		n_pending = 0;
54 static long		total = 1;
55 static long		n_done = 0;
56 static long		hit_end;
57 static long		curr_art_no = -1;
58 static FILE		*fp = NULL;
59 
close_fp(void)60 static void close_fp(void)
61 {
62     if (fp)
63 	fclose(fp);
64     fp = NULL;
65 }
66 
print_refs(FILE * fp,ARTICLE * art)67 static void print_refs(FILE *fp, ARTICLE *art)
68 {
69     if (!art)
70 	return;
71 
72     print_refs(fp, A_PARENT(art));
73     fprintf(fp, "<%s> ", art->msgid);
74 }
75 
print_xover_line(FILE * fp,ARTICLE * art,char * refs)76 static void print_xover_line(FILE *fp, ARTICLE *art, char *refs)
77 {
78     char	time_buf[32];
79 
80     fprintf(fp, "%ld\t%s\t%s\t%s\t<%s>\t",
81 	    art->no, art->subject->subject, art->from,
82 	    time_t_to_date(art->date, time_buf), art->msgid);
83     if (refs)
84 	fputs(refs, fp);
85     else
86 	print_refs(fp, A_PARENT(art));
87     fprintf(fp, "\t\t%hu", art->lines);
88     if (art->xref)
89 	fprintf(fp, "\tXref: %s\r\n", art->xref);
90     else
91 	fputs("\r\n", fp);
92 
93 }
94 
get_tmp_filename(char * group_name)95 static char *get_tmp_filename(char *group_name)
96 {
97     char	*cache_dir = res_cache_dir();
98     char	*ret;
99 
100     ret = XtMalloc(strlen(cache_dir) + strlen(group_name) + 2);
101     sprintf(ret, "%s/%s", cache_dir, group_name);
102 
103     return ret;
104 }
105 
clear_bogus_context(int create)106 static void clear_bogus_context(int create)
107 {
108     if (bogus_context)
109 	clear_thread_context(bogus_context);
110     else if (create)
111 	bogus_context = create_thread_context();
112 }
113 
create_ahead_node(GROUP * group,int flag)114 static GROUP_AHEAD_NODE *create_ahead_node(GROUP *group, int flag)
115 {
116     GROUP_AHEAD_NODE	*temp, *last = schedule;
117 
118     temp = (GROUP_AHEAD_NODE *)XtMalloc(sizeof *temp);
119     temp->next         = NULL;
120     temp->group        = group;
121     temp->file_name    = get_tmp_filename(group->name);
122     temp->flag         = flag;
123     temp->needs_update = False;
124 
125     group->ahead_flag = True;
126 
127     if (!last)
128 	schedule = temp;
129     else {
130 	while (last->next)
131 	    last = last->next;
132 	last->next = temp;
133     }
134 
135     return temp;
136 }
137 
remove_ahead_node(GROUP_AHEAD_NODE * node,int do_unlink)138 static void remove_ahead_node(GROUP_AHEAD_NODE *node, int do_unlink)
139 {
140     GROUP	*group = node->group;
141 
142     if (schedule)
143 	if (node == schedule)
144 	    schedule = schedule->next;
145 	else {
146 	    GROUP_AHEAD_NODE	*prev = schedule;
147 
148 	    while (prev->next && prev->next != node)
149 		prev = prev->next;
150 
151 	    if (prev->next)
152 		prev->next = prev->next->next;
153 	}
154 
155     if (node->file_name) {
156 	if (do_unlink)
157 	    unlink_expand(node->file_name);
158 	XtFree(node->file_name);
159     }
160     node->next = NULL;
161     if (node->group)
162 	node->group->ahead_flag = False;
163     node->group = NULL;
164     node->file_name = NULL;
165     XtFree((char *)node);
166     update_group_entry(group);
167 }
168 
remove_current_node(void)169 static void remove_current_node(void)
170 {
171     clear_bogus_context(False);
172     if (current)
173 	remove_ahead_node(current, True);
174     current = NULL;
175     close_fp();
176 }
177 
get_scheduled_node(void)178 static GROUP_AHEAD_NODE *get_scheduled_node(void)
179 {
180     GROUP_AHEAD_NODE	*node;
181 
182     for (node = schedule ; node ; node = node->next)
183 	if (node->flag == AheadFlagScheduled)
184 	    return node;
185 
186     return NULL;
187 }
188 
find_ahead_node(GROUP * group)189 static GROUP_AHEAD_NODE *find_ahead_node(GROUP *group)
190 {
191     GROUP_AHEAD_NODE	*node;
192 
193     for (node = schedule ; node ; node = node->next)
194 	if (node->group == group)
195 	    return node;
196 
197     return NULL;
198 }
199 
200 #define HEAD_NEXT	12
201 #define MAX_HEAD_NEXT	16
202 
put_head_next(SERVER * server)203 static int put_head_next(SERVER *server)
204 {
205     char	buffer[MAX_HEAD_NEXT * HEAD_NEXT + 1];
206     char	*c;
207     long	i, n = n_to_put;
208     int		busy;
209 
210     if (hit_end || n_pending >= MAX_HEAD_NEXT)
211 	return True;
212 
213     if (n == 0)
214 	n = 1;
215     else {
216 	if (n > MAX_HEAD_NEXT)
217 	    n = MAX_HEAD_NEXT;
218 	n_to_put -= n;
219     }
220     n_pending += n;
221 
222     buffer[0] = '\0';
223     for (i = 0, c = buffer ; i < n ; i++, c += HEAD_NEXT)
224 	strcpy(c, "HEAD\r\nNEXT\r\n");
225 
226     busy = global.busy;
227     if (!busy)
228 	global.busy = True;
229     i = server_write(server, buffer);
230     if (!busy)
231 	global.busy = False;
232 
233     return i == 0;
234 }
235 
thread_ahead_start_head(SERVER * server)236 static int thread_ahead_start_head(SERVER *server)
237 {
238     clear_bogus_context(True);
239     n_pending = 0;
240     n_to_put = total;
241     hit_end = False;
242 
243     return put_head_next(server);
244 }
245 
start_xover(SERVER * server)246 static int start_xover(SERVER *server)
247 {
248     long		first, last;
249     GROUP		*group;
250     GROUP_AHEAD_NODE	*node;
251 
252     ahead_state = AheadStateNone;
253     group = bg_in_group(&total, &first, &last);
254     if (!group)
255 	return False;
256     node = find_ahead_node(group);
257     if (!node || node->flag != AheadFlagThreading)
258 	return False;
259 
260     if (total <= 0)
261 	total = 1;
262 
263     if (global.xover_supported) {
264 	char	command[512];
265 	int	tmp, busy = global.busy;
266 
267 	sprintf(command, "XOVER %ld-%ld\r\n", first, last);
268 	if (!busy)
269 	    global.busy = True;
270 	tmp = server_write(server, command);
271 	if (!busy)
272 	    global.busy = False;
273 
274 	if (tmp == 0) {
275 	    ahead_state = AheadStateSentXover;
276 	    return True;
277 	}
278 
279 	current->flag = AheadFlagScheduled;
280 	update_group_entry(current->group);
281 	ahead_state = AheadStateNone;
282 	return False;
283     }
284 
285     ahead_state = AheadStateSentHead;
286     thread_ahead_start_head(server); /* check return? */
287     return True;
288 }
289 
start_group(SERVER * server)290 static int start_group(SERVER *server)
291 {
292     char		message[512];
293     GROUP_AHEAD_NODE	*node;
294     GROUP		*group;
295 
296     n_to_put = 0;
297     n_pending = 0;
298     n_done = 0;
299     ahead_state = AheadStateNone;
300 
301     while ((node = get_scheduled_node())) {
302 	fp = fopen_expand(node->file_name, "w", True);
303 	if (fp)
304 	    break;
305     }
306 
307     if (!node)
308 	return False;
309 
310     current = node;
311     node->flag = AheadFlagThreading;
312     update_group_entry(node->group);
313 
314     group = bg_in_group(NULL, NULL, NULL);
315     if (total <= 0)
316 	total = 1;
317 
318     if (group && group == node->group)
319 	return start_xover(server);
320 
321     bg_start_group(node->group);
322     ahead_state = AheadStateSentGroup;
323     sprintf(message, "Threading %s...", node->group->name);
324     set_message(message, False);
325 
326     return True;
327 }
328 
thread_ahead_proc(SERVER * server)329 static int thread_ahead_proc(SERVER *server)
330 {
331     char	*buffer = NULL;
332     long	status;
333     int		tmp;
334     ARTICLE	*art;
335 
336     if (!server) {
337 	remove_current_node();
338 	ahead_state = AheadStateNone;
339 	return False;
340     }
341 
342     do {
343 	switch (ahead_state) {
344 	case AheadStateNone:
345 	    return start_group(server);
346 	case AheadStateSentGroup:
347 	    return start_xover(server);
348 	case AheadStateSentXover:
349 	    buffer = server_get_line(server);
350 	    if (!buffer)
351 		break;
352 
353 	    if (atoi(buffer) == NNTP_OK_XOVER) {
354 		ahead_state = AheadStateDoingXover;
355 		break;
356 	    }
357 
358 	    /* fall back to head */
359 	    thread_ahead_start_head(server); /* check return? */
360 	    return True;
361 	case AheadStateDoingXover:
362 	    buffer = server_get_line(server);
363 	    if (!buffer)
364 		break;
365 
366 	    n_done++;
367 	    if (fp)
368 		fprintf(fp, "%s\r\n", buffer);
369 
370 	    if (IS_DOT(buffer)) {
371 		current->flag = AheadFlagDone;
372 		update_group_entry(current->group);
373 		current = NULL;
374 		close_fp();
375 		ahead_state = AheadStateNone;
376 		return False;
377 	    }
378 	    break;
379 	case AheadStateSentHead:
380 	    buffer = server_get_line(server);
381 	    if (!buffer)
382 		break;
383 
384 	    tmp = sscanf(buffer, "%ld%ld", &status, &curr_art_no);
385 	    if (tmp <= 0 || status != NNTP_OK_HEAD)
386 		ahead_state = AheadStateSentNext;
387 	    else {
388 		ahead_state = AheadStateDoingHead;
389 		if (tmp == 1 || hit_end)
390 		    curr_art_no = -1;
391 	    }
392 
393 	    if (!hit_end) {
394 		n_done++;
395 		if (n_pending < MAX_HEAD_NEXT  && !put_head_next(server)) {
396 		    ahead_state = AheadStateNone;
397 		    return False;
398 		}
399 	    }
400 	    break;
401 	case AheadStateDoingHead:
402 	    buffer = server_get_chunk(server);
403 	    if (!buffer)
404 		break;
405 
406 	    if (curr_art_no > 0) {
407 		art = parse_head(curr_art_no, bogus_context, buffer);
408 		if (art && fp)
409 		    print_xover_line(fp, art, get_refs(bogus_context));
410 	    }
411 	    curr_art_no = -1;
412 	    ahead_state = AheadStateSentNext;
413 	    break;
414 	case AheadStateSentNext:
415 	    buffer = server_get_line(server);
416 	    if (!buffer)
417 		break;
418 
419 	    n_pending--;
420 	    if (!hit_end) {
421 		status = atoi(buffer);
422 		if (status != NNTP_OK_NOTEXT) /* maybe better checking */
423 		    hit_end = True;
424 	    }
425 
426 	    if (hit_end && n_pending <= 0) {
427 		current->flag = AheadFlagDone;
428 		update_group_entry(current->group);
429 		current = NULL;
430 		close_fp();
431 		ahead_state = AheadStateNone;
432 		return False;
433 	    }
434 
435 	    ahead_state = AheadStateSentHead;
436 	    break;
437 	}
438     } while (buffer);
439 
440     return True;
441 }
442 
action_schedule_thread_ahead(Widget w,XEvent * event,String * params,Cardinal * no_params)443 void action_schedule_thread_ahead(Widget w, XEvent *event,
444 				  String *params, Cardinal *no_params)
445 {
446     GROUP		*group = NULL;
447     GROUP_AHEAD_NODE	*node;
448     long		n, i;
449 
450     switch (global.mode) {
451     case NewsModeConnected:
452 	if (w != main_widgets.group_list)
453 	    group = global.curr_group;
454 	else {
455 	    n = ScrListEventToIndex(w, event);
456 	    if (n < 0)
457 		break;
458 	    for (i = 0 ; i < global.no_groups ; i++)
459 		if (global.groups[i]->disp == n) {
460 		    group = global.groups[i];
461 		    break;
462 		}
463 	}
464 	break;
465     case NewsModeAllgroups:
466 	if (w != main_widgets.group_list)
467 	    group = global.curr_group;
468 	else {
469 	    n = ScrListEventToIndex(w, event);
470 	    if (n >= 0 && n < global.no_groups)
471 		group = global.groups[n];
472 	}
473 	break;
474     case NewsModeSomegroups:
475 	n = ScrListEventToIndex(w, event);
476 	if (n < 0)
477 	    break;
478 	for (i = 0 ; i < global.no_groups ; i++)
479 	    if (global.groups[i]->disp == n) {
480 		group = global.groups[i];
481 		break;
482 	    }
483 	break;
484     case NewsModeDisconnected:
485     case NewsModeGroup:
486     case NewsModeThread:
487     case NewsModeNewgroups:
488 	if (global.bell)
489 	    XBell(display, 0);
490 	return;
491     }
492 
493     if (!group) {
494 	if (global.bell)
495 	    XBell(display, 0);
496 	return;
497     }
498 
499     node = find_ahead_node(group);
500     if (node) {
501 	char	message[128];
502 
503 	switch (node->flag) {
504 	case AheadFlagNoop:
505 	    break;
506 	case AheadFlagDone:
507 	    set_message("That group has already been threaded!", True);
508 	    return;
509 	case AheadFlagScheduled:
510 	    set_message("That group is already scheduled for "
511 			"thread ahead.", True);
512 	    return;
513 	case AheadFlagThreading:
514 	    sprintf(message, "That group is beeing threaded, %ld%% done!",
515 		    100 * n_done / total);
516 	    set_message(message, False);
517 	    return;
518 	}
519     }
520 
521     if (!node)
522 	create_ahead_node(group, AheadFlagScheduled);
523     update_group_entry(group);
524 
525     bg_nudge(thread_ahead_proc);
526 }
527 
thread_ahead_char(GROUP * group)528 char thread_ahead_char(GROUP *group)
529 {
530     GROUP_AHEAD_NODE	*node = find_ahead_node(group);
531 
532     if (node)
533 	switch (node->flag) {
534 	case AheadFlagNoop:
535 	    return ' ';
536 	case AheadFlagScheduled:
537 	    return '-';
538 	case AheadFlagThreading:
539 	    return '*';
540 	case AheadFlagDone:
541 	    return '+';
542 	}
543 
544     return ' ';
545 }
546 
thread_ahead_shutdown(void)547 void thread_ahead_shutdown(void)
548 {
549     int	save = res_save_thread_info();
550 
551     n_pending = 0;
552     n_to_put = 0;
553     curr_art_no = -1;
554     clear_bogus_context(False);
555     close_fp();
556 
557     while (schedule)
558 	remove_ahead_node(schedule, !save);
559 }
560 
thread_ahead_check(GROUP * group)561 int thread_ahead_check(GROUP *group)
562 {
563     GROUP_AHEAD_NODE	*node = find_ahead_node(group);
564     char		message[128];
565     int			threaded = False;
566     SERVER		*server;
567     int			fd;
568 
569     if (node)
570 	switch (node->flag) {
571 	case AheadFlagNoop:
572 	    break;
573 	case AheadFlagThreading:
574 	    sprintf(message,
575 		    "Thread ahead in progress %ld%% done, try later.",
576 		    100 * n_done / total);
577 	    set_message(message, False);
578 	    return -1;
579 	case AheadFlagScheduled:
580 	    node->flag = AheadFlagNoop;
581 	    update_group_entry(group);
582 	    break;
583 	case AheadFlagDone:
584 	    fd = open_expand(node->file_name, O_RDONLY, True);
585 	    if (fd < 0)
586 		break;
587 
588 	    set_message("Threading from file...", False);
589 	    set_busy(False);
590 	    server = server_create(fd);
591 	    thread_from_file(server, group->first_art);
592 	    server_free(server);
593 	    unset_busy();
594 	    threaded = True;
595 	    break;
596 	}
597 
598     return threaded;
599 }
600 
thread_ahead_leave_group(GROUP * group)601 void thread_ahead_leave_group(GROUP *group)
602 {
603     GROUP_AHEAD_NODE	*node = find_ahead_node(group);
604     int			keep = res_keep_thread_info(group->subscribed);
605     int			save = group->subscribed && res_save_thread_info();
606 
607     if (!save && !keep) {
608 	if (node)
609 	    remove_ahead_node(node, True);
610     } else {
611 	if (!node)
612 	    node = create_ahead_node(group, AheadFlagNoop);
613 	if (node->flag == AheadFlagNoop || node->flag == AheadFlagScheduled ||
614 	    (node->flag == AheadFlagDone && save && node->needs_update)) {
615 	    FILE	*fp;
616 
617 	    fp = fopen_expand(node->file_name, "w", True);
618 	    if (!fp)
619 		fputs("knews: couldn't create temp file "
620 		      "for thread data.\n", stderr);
621 	    else {
622 		ARTICLE	*art;
623 
624 		for (art = get_articles(main_thr) ; art ; art = art->next)
625 		    if (art->no >= group->first_art)
626 			print_xover_line(fp, art, NULL);
627 		fclose(fp);
628 		node->flag = AheadFlagDone;
629 		node->needs_update = False;
630 	    }
631 	}
632     }
633 
634     clear_thread_context(main_thr);
635     global.curr_group = group;
636 }
637 
scan_group(GROUP * group,int scan)638 static int scan_group(GROUP *group, int scan)
639 {
640     struct stat		stat_buf;
641     GROUP_AHEAD_NODE	*node;
642     char		*path;
643     int			tmp;
644 
645     node = create_ahead_node(group, AheadFlagScheduled);
646 
647     if (!scan || !(path = expand_path(node->file_name)))
648 	return True;
649 
650     tmp = stat(path, &stat_buf);
651     if (tmp < 0 && errno != ENOENT)
652 	perror(path);
653     XtFree(path);
654     if (tmp < 0)
655 	return True;
656     node->flag = AheadFlagDone;
657     if (scan)
658 	node->needs_update = True;
659 
660     return False;
661 }
662 
thread_ahead_init(void)663 void thread_ahead_init(void)
664 {
665     char	**groups = res_thread_ahead_groups();
666     int		scan     = res_save_thread_info();
667     int		doit     = False;
668 
669     if ((!groups || !*groups) && !scan)
670 	return;
671 
672     if (scan || case_lstrcmp(*groups, "all") == 0) {
673 	int	all = scan || **groups == 'A';
674 	int	n;
675 
676 	for (n = 0 ; n < global.no_groups ; n++) {
677 	    if (!global.groups[n]->subscribed)
678 		break;
679 	    if (!all && global.groups[n]->no_unread <= 0)
680 		continue;
681 
682 	    doit |= scan_group(global.groups[n], scan);
683 	    update_group_entry(global.groups[n]);
684 	    if (scan)
685 		XFlush(display);
686 	}
687     } else {
688 	while (*groups) {
689 	    GROUP	*group = find_group(*groups);
690 
691 	    if (!group)
692 		fprintf(stderr, "thread_ahead: couldn't find group %s\n",
693 			*groups);
694 	    else {
695 		doit |= scan_group(group, False);
696 		update_group_entry(group);
697 		if (scan)
698 		    XFlush(display);
699 	    }
700 
701 	    groups++;
702 	}
703     }
704 
705     if (doit)
706 	bg_nudge(thread_ahead_proc);
707 }
708 
thread_ahead_todo(void)709 int thread_ahead_todo(void)
710 {
711     return get_scheduled_node() != NULL;
712 }
713