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