1 /* -*- mode: C; mode: fold -*- */
2 /*
3 This file is part of SLRN.
4
5 Copyright (c) 1994, 1999, 2007-2016 John E. Davis <jed@jedsoft.org>
6 Copyright (c) 2001-2006 Thomas Schultz <tststs@gmx.de>
7
8 This program is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2 of the License, or (at your option)
11 any later version.
12
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 more details.
17
18 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 */
22 #include "config.h"
23 #include "slrnfeat.h"
24
25 /*{{{ Include files */
26
27 #include <stdio.h>
28 #include <string.h>
29 #include <time.h>
30 #ifndef VMS
31 # include <sys/types.h>
32 # include <sys/stat.h>
33 #else
34 # include "vms.h"
35 #endif
36
37 #include <signal.h>
38
39 #ifdef HAVE_STDLIB_H
40 # include <stdlib.h>
41 #endif
42
43 #ifdef HAVE_UNISTD_H
44 # include <unistd.h>
45 #endif
46
47 #include <slang.h>
48 #include "jdmacros.h"
49
50 #include "slrn.h"
51 #include "group.h"
52 #include "art.h"
53 #include "misc.h"
54 #include "post.h"
55 #include "util.h"
56 #include "server.h"
57 #include "hash.h"
58 #include "score.h"
59 #include "menu.h"
60 #include "startup.h"
61 #include "slrndir.h"
62 #include "vfile.h"
63 #include "snprintf.h"
64 #include "hooks.h"
65 #include "common.h"
66 #include "strutil.h"
67
68 /*}}}*/
69
70 /*{{{ Global Variables */
71
72 int Slrn_Query_Group_Cutoff = 1000;
73 int Slrn_Groups_Dirty; /* 1 == need to write newsrc */
74 int Slrn_List_Active_File = 0;
75 int Slrn_Write_Newsrc_Flags = 0; /* if 1, do not save unsubscribed
76 * if 2, do not save new unsubscribed.
77 */
78
79 int Slrn_Display_Cursor_Bar;
80 char *Slrn_Group_Help_Line;
81 char *Slrn_Group_Status_Line;
82
83 Slrn_Group_Type *Slrn_Group_Current_Group;
84 static SLscroll_Window_Type Group_Window;
85 static Slrn_Group_Type *Groups;
86
87 int Slrn_No_Backups = 0;
88 int Slrn_No_Autosave = 0;
89
90 int Slrn_Unsubscribe_New_Groups = 0;
91 int Slrn_Check_New_Groups = 1;
92 int Slrn_Drop_Bogus_Groups = 1;
93 int Slrn_Max_Queued_Groups = 20;
94
95 SLKeyMap_List_Type *Slrn_Group_Keymap;
96 int *Slrn_Prefix_Arg_Ptr;
97
98 /*}}}*/
99 /*{{{ Static Variables */
100
101 #define GROUP_HASH_TABLE_SIZE 1250
102 static Slrn_Group_Type *Group_Hash_Table [GROUP_HASH_TABLE_SIZE];
103
104 static unsigned int Last_Cursor_Row;
105 static int Groups_Hidden; /* if 1, hide groups with no articles;
106 * if 2, groups are hidden by user request */
107 static int Kill_After_Max = 1;
108
109 typedef struct Unsubscribed_Slrn_Group_Type
110 {
111 char *group_name;
112 struct Unsubscribed_Slrn_Group_Type *next;
113 }
114 Unsubscribed_Slrn_Group_Type;
115
116 static Unsubscribed_Slrn_Group_Type *Unsubscribed_Groups;
117
118 /*}}}*/
119
120 /*{{{ Forward Function Declarations */
121
122 static void group_update_screen (void);
123 static void group_quick_help (void);
124 static void save_newsrc_cmd (void);
125 static void read_and_parse_active (int);
126 static int parse_active_line (unsigned char *, unsigned int *, int *, int *);
127 static void remove_group_entry (Slrn_Group_Type *);
128
129 /*}}}*/
130
131 /*{{{ Functions that deal with Group Range */
132
count_unread(Slrn_Range_Type * r)133 static NNTP_Artnum_Type count_unread (Slrn_Range_Type *r)
134 {
135 NNTP_Artnum_Type nread = 0;
136 NNTP_Artnum_Type rmax = r->max;
137
138 while (r->next != NULL)
139 {
140 r = r->next;
141 nread += r->max - r->min + 1;
142 }
143 if (nread > rmax)
144 return 0;
145
146 return rmax - nread;
147 }
148
slrn_group_recount_unread(Slrn_Group_Type * g)149 void slrn_group_recount_unread (Slrn_Group_Type *g)
150 {
151 /* Make sure old (unavailable) messages are marked read */
152 if (g->range.min>1)
153 g->range.next = slrn_ranges_add (g->range.next, 1, g->range.min-1);
154 g->unread = count_unread (&g->range);
155 }
156
is_article_requested(Slrn_Group_Type * g,NNTP_Artnum_Type num)157 static int is_article_requested (Slrn_Group_Type *g, NNTP_Artnum_Type num) /*{{{*/
158 {
159 #if SLRN_HAS_SPOOL_SUPPORT
160 if (Slrn_Server_Id != SLRN_SERVER_ID_SPOOL) return 0;
161
162 if (g->requests_loaded == 0)
163 {
164 g->requests = slrn_spool_get_requested_ranges (g->group_name);
165 g->requests_loaded = 1;
166 }
167
168 return slrn_ranges_is_member (g->requests, num);
169 #else
170 (void) g; (void) num;
171 return 0;
172 #endif
173 }
174 /*}}}*/
175
group_mark_article_as_read(Slrn_Group_Type * g,NNTP_Artnum_Type num)176 static void group_mark_article_as_read (Slrn_Group_Type *g, NNTP_Artnum_Type num) /*{{{*/
177 {
178 Slrn_Range_Type *r;
179
180 /* Never mark articles as read if their body has been requested. */
181 if (is_article_requested (g, num)) return;
182
183 r = &g->range;
184 if (r->max < num) /* not at server yet so update our data */
185 {
186 r->max = num;
187 g->unread += 1;
188 }
189
190 r = r->next;
191
192 while (r != NULL)
193 {
194 /* Already read */
195 if ((num <= r->max) && (num >= r->min)) return;
196 if (num < r->min) break;
197 r = r->next;
198 }
199
200 if (g->unread != 0) g->unread -= 1;
201 Slrn_Groups_Dirty = 1;
202 g->range.next = slrn_ranges_add (g->range.next, num, num);
203 }
204
205 /*}}}*/
206
slrn_mark_articles_as_read(char * group,NNTP_Artnum_Type rmin,NNTP_Artnum_Type rmax)207 void slrn_mark_articles_as_read (char *group,
208 NNTP_Artnum_Type rmin, NNTP_Artnum_Type rmax) /*{{{*/
209 {
210 Slrn_Group_Type *g;
211
212 if (group == NULL)
213 g = Slrn_Group_Current_Group;
214 else
215 {
216 unsigned long hash = slrn_compute_hash ((unsigned char *) group,
217 (unsigned char *) group + strlen (group));
218
219 g = Group_Hash_Table[hash % GROUP_HASH_TABLE_SIZE];
220
221 while (g != NULL)
222 {
223 if ((g->hash == hash) && !strcmp (group, g->group_name))
224 {
225 /* If it looks like we have read this group, mark it read. */
226 if ((g->flags & GROUP_UNSUBSCRIBED)
227 && (g->range.next == NULL))
228 return;
229
230 break;
231 }
232 g = g->hash_next;
233 }
234 }
235
236 if (g == NULL)
237 return;
238
239 while (rmin <= rmax)
240 {
241 group_mark_article_as_read (g, rmin);
242 rmin++;
243 }
244 }
245
246 /*}}}*/
247
group_update_range(Slrn_Group_Type * g,NNTP_Artnum_Type min,NNTP_Artnum_Type max)248 static int group_update_range (Slrn_Group_Type *g, NNTP_Artnum_Type min, NNTP_Artnum_Type max) /*{{{*/
249 {
250 NNTP_Artnum_Type n, max_available;
251 Slrn_Range_Type *r;
252
253 if (max == 0)
254 {
255 NNTP_Artnum_Type nmax, nmin;
256
257 nmax = g->range.max;
258 nmin = g->range.min;
259
260 /* Server database inconsistent. */
261 if (nmax > 0)
262 for (n = nmin; n <= nmax; n++)
263 group_mark_article_as_read (g, n);
264
265 /* g->unread = 0; */
266 Slrn_Full_Screen_Update = 1;
267 Slrn_Groups_Dirty = 1;
268 return -1;
269 }
270
271 g->range.min = min;
272
273 /* This might be due to a cancel -- or to a rescan delay in inn after we
274 * read the active file; if Kill_After_Max is 0, play it safe and do not
275 * mark any articles as read */
276 if (Kill_After_Max && (max < g->range.max))
277 {
278 NNTP_Artnum_Type nmax = g->range.max;
279 for (n = max + 1; n <= nmax; n++)
280 group_mark_article_as_read (g, n);
281 }
282 else g->range.max = max;
283
284 /* In case more articles arrived at the server between the time that
285 * slrn was first started and when the server was just queried, update
286 * the ranges of read/unread articles.
287 */
288
289 max_available = g->range.max - g->range.min + 1;
290
291 r = &g->range;
292 if (r->next != NULL)
293 {
294 n = r->max;
295 while (r->next != NULL)
296 {
297 r = r->next;
298 n -= r->max - r->min + 1;
299 }
300 if (n < 0) n = 0;
301 if (n > max_available) n = max_available;
302 g->unread = n;
303
304 r = &g->range;
305 if (r->next->min <= r->min)
306 r->next->min = 1;
307 }
308 else
309 g->unread = max_available;
310
311 if ((g->flags & GROUP_UNSUBSCRIBED) == 0)
312 {
313 if (g->unread > 0)
314 g->flags &= ~GROUP_HIDDEN;
315 else if ((Groups_Hidden & 1) && (g != Slrn_Group_Current_Group))
316 g->flags |= GROUP_HIDDEN;
317 }
318
319 return 0;
320 }
321
322 /*}}}*/
323
group_sync_group_with_server(Slrn_Group_Type * g,NNTP_Artnum_Type * minp,NNTP_Artnum_Type * maxp)324 static int group_sync_group_with_server (Slrn_Group_Type *g, NNTP_Artnum_Type *minp, NNTP_Artnum_Type *maxp) /*{{{*/
325 {
326 char *group;
327 int status, reselect;
328
329 if (g == NULL) return -1;
330
331 group = g->group_name;
332
333 slrn_message_now (_("Selecting %s ..."), group);
334
335 if (((reselect = Kill_After_Max) == 0) &&
336 (strcmp (group, Slrn_Server_Obj->sv_current_group ())))
337 reselect = 1;
338
339 status = Slrn_Server_Obj->sv_select_group (group, minp, maxp);
340 if (status == -1)
341 return -1;
342
343 if (status == ERR_NOGROUP)
344 {
345 slrn_message_now (_("Group %s is bogus%s."), group,
346 Slrn_Drop_Bogus_Groups ? _(" - dropping it") : "");
347 Slrn_Saw_Warning = 1;
348 if (Slrn_Drop_Bogus_Groups)
349 remove_group_entry (g);
350 return -1;
351 }
352 else if (status != OK_GROUP)
353 {
354 slrn_error (_("Could not enter group %s."), group);
355 return -1;
356 }
357
358 Kill_After_Max = reselect;
359
360 return group_update_range (g, *minp, *maxp);
361 }
362
363 /*}}}*/
364
free_newsgroup_type(Slrn_Group_Type * g)365 static void free_newsgroup_type (Slrn_Group_Type *g)
366 {
367 if (g == NULL)
368 return;
369 slrn_free (g->descript);
370 slrn_ranges_free (g->range.next);
371 slrn_ranges_free (g->requests);
372 slrn_free (g->group_name);
373 slrn_free ((char *) g);
374 }
375
free_unsubscribed_group_type(Unsubscribed_Slrn_Group_Type * ug)376 static void free_unsubscribed_group_type (Unsubscribed_Slrn_Group_Type *ug)
377 {
378 if (ug == NULL)
379 return;
380 slrn_free (ug->group_name);
381 slrn_free ((char *) ug);
382 }
383
slrn_catchup_group(void)384 void slrn_catchup_group (void) /*{{{*/
385 {
386 if ((Slrn_Group_Current_Group == NULL)||
387 (Slrn_Group_Current_Group->unread==0))
388 return;
389
390 Slrn_Group_Current_Group->range.next =
391 slrn_ranges_add(Slrn_Group_Current_Group->range.next, 1,
392 Slrn_Group_Current_Group->range.max);
393 Slrn_Group_Current_Group->unread = 0;
394 Slrn_Groups_Dirty = 1;
395 Slrn_Group_Current_Group->flags |= GROUP_TOUCHED;
396 }
397
398 /*}}}*/
399
slrn_uncatchup_group(void)400 void slrn_uncatchup_group (void) /*{{{*/
401 {
402 if (Slrn_Group_Current_Group == NULL)
403 return;
404 slrn_ranges_free (Slrn_Group_Current_Group->range.next);
405 Slrn_Group_Current_Group->range.next = NULL;
406 slrn_group_recount_unread (Slrn_Group_Current_Group);
407 Slrn_Groups_Dirty = 1;
408 Slrn_Group_Current_Group->flags |= GROUP_TOUCHED;
409 }
410
411 /*}}}*/
412
413 /*}}}*/
414
415 /*{{{ Misc Utility Functions */
find_line_num(void)416 static void find_line_num (void) /*{{{*/
417 {
418 Group_Window.lines = (SLscroll_Type *) Groups;
419 Group_Window.current_line = (SLscroll_Type *) Slrn_Group_Current_Group;
420 (void) SLscroll_find_line_num (&Group_Window);
421 }
422
423 /*}}}*/
424
find_group_entry(char * name,unsigned int len)425 static Slrn_Group_Type *find_group_entry (char *name, unsigned int len) /*{{{*/
426 {
427 int hash_index;
428 unsigned long hash;
429 Slrn_Group_Type *g;
430
431 hash = slrn_compute_hash ((unsigned char *) name,
432 (unsigned char *) name + len);
433
434 hash_index = hash % GROUP_HASH_TABLE_SIZE;
435 g = Group_Hash_Table[hash_index];
436
437 while (g != NULL)
438 {
439 if ((g->hash == hash) && !strncmp (name, g->group_name, len))
440 {
441 if (len == strlen (g->group_name)) break;
442 }
443 g = g->hash_next;
444 }
445 return g;
446 }
447
448 /*}}}*/
create_group_entry(char * name,unsigned int len,NNTP_Artnum_Type min,NNTP_Artnum_Type max,int query_server,int skip_find)449 static Slrn_Group_Type *create_group_entry (char *name, unsigned int len, /*{{{*/
450 NNTP_Artnum_Type min, NNTP_Artnum_Type max, int query_server,
451 int skip_find)
452 {
453 int hash_index;
454 unsigned long hash;
455 Slrn_Group_Type *g;
456
457 if (skip_find == 0)
458 {
459 g = find_group_entry (name, len);
460 if (g != NULL) return g;
461 }
462
463 g = (Slrn_Group_Type *) slrn_safe_malloc (sizeof (Slrn_Group_Type));
464 g->requests = NULL;
465 g->requests_loaded = 0;
466
467 g->group_name = slrn_safe_malloc (len + 1);
468 strncpy (g->group_name, name, len);
469 g->group_name [len] = 0;
470
471 if (query_server)
472 {
473 int status;
474
475 status = Slrn_Server_Obj->sv_select_group (g->group_name, &min, &max);
476 if (status == ERR_NOGROUP)
477 {
478 slrn_message_now (_("Group %s is bogus%s."), g->group_name,
479 Slrn_Drop_Bogus_Groups ? _(" - ignoring it") : "");
480 if (Slrn_Drop_Bogus_Groups)
481 {
482 Slrn_Groups_Dirty = 1;
483 free_newsgroup_type (g);
484 return NULL;
485 }
486 }
487 }
488 g->range.min = min;
489 g->range.max = max;
490 if (max > 0)
491 g->unread = max - min + 1;
492 else g->unread = 0;
493
494 g->flags = (GROUP_UNSUBSCRIBED | GROUP_HIDDEN);
495
496 hash = slrn_compute_hash ((unsigned char *) name,
497 (unsigned char *) name + len);
498 hash_index = hash % GROUP_HASH_TABLE_SIZE;
499
500 g->hash = hash;
501 g->hash_next = Group_Hash_Table[hash_index];
502 Group_Hash_Table[hash_index] = g;
503
504 if (Groups == NULL)
505 {
506 Slrn_Group_Current_Group = Groups = g;
507 }
508 else
509 {
510 if (Slrn_Group_Current_Group == NULL) /* insert on top */
511 {
512 g->prev = NULL;
513 g->next = Groups;
514 Groups->prev = g;
515 Groups = g;
516 }
517 else /* insert after current group */
518 {
519 g->next = Slrn_Group_Current_Group->next;
520 if (g->next != NULL) g->next->prev = g;
521 Slrn_Group_Current_Group->next = g;
522 g->prev = Slrn_Group_Current_Group;
523 }
524 }
525 Slrn_Group_Current_Group = g;
526 return g;
527 }
528
529 /*}}}*/
remove_group_entry(Slrn_Group_Type * g)530 static void remove_group_entry (Slrn_Group_Type *g) /*{{{*/
531 {
532 Slrn_Group_Type *tmp;
533 if (g == Groups)
534 Groups = g->next;
535 if (g == Slrn_Group_Current_Group)
536 Slrn_Group_Current_Group = g->next != NULL ? g->next : g->prev;
537 if (g->prev != NULL)
538 g->prev->next = g->next;
539 if (g->next != NULL)
540 g->next->prev = g->prev;
541 if (g == (tmp = Group_Hash_Table[g->hash % GROUP_HASH_TABLE_SIZE]))
542 Group_Hash_Table[g->hash % GROUP_HASH_TABLE_SIZE] = g->hash_next;
543 else while (tmp != NULL)
544 {
545 if (g == tmp->hash_next)
546 {
547 tmp->hash_next = g->hash_next;
548 break;
549 }
550 tmp = tmp->hash_next;
551 }
552 free_newsgroup_type (g);
553 find_line_num ();
554 Slrn_Groups_Dirty = 1;
555 }
556 /*}}}*/
add_group(char * name,unsigned int len,unsigned int subscribe_flag,int query_server,int create_flag)557 static int add_group (char *name, unsigned int len, /*{{{*/
558 unsigned int subscribe_flag, int query_server,
559 int create_flag)
560 {
561 Slrn_Group_Type *g;
562
563 g = find_group_entry (name, len);
564 if (g == NULL)
565 {
566 if (Slrn_List_Active_File && Slrn_Drop_Bogus_Groups)
567 {
568 char *tmp_name;
569
570 if (NULL != (tmp_name = slrn_strnmalloc (name, len, 1)))
571 {
572 slrn_message_now (_("Group %s is bogus - ignoring it."), tmp_name);
573 Slrn_Saw_Warning = 1;
574 slrn_free (tmp_name);
575 }
576 return -1;
577 }
578 else g = create_group_entry (name, len, -1, -1, query_server &&
579 !(subscribe_flag & GROUP_UNSUBSCRIBED),
580 0);
581 if (g == NULL) return -1;
582 }
583 Slrn_Groups_Dirty = 1;
584
585 /* If we have already processed this, then the group is duplicated in
586 * the newsrc file. Throw it out now.
587 */
588 if (g->flags & GROUP_PROCESSED) return -1;
589
590 Slrn_Group_Current_Group = g;
591 g->flags = subscribe_flag;
592 g->flags |= GROUP_PROCESSED;
593
594 if (subscribe_flag & GROUP_UNSUBSCRIBED)
595 {
596 g->flags |= GROUP_HIDDEN;
597
598 if (create_flag) return 0;
599 g->unread = 0;
600 /* if (Slrn_List_Active_File == 0) return 0; */
601 }
602
603 if (create_flag) return 0;
604
605 /* find ranges for this */
606 name += len; /* skip past name */
607 if (*name) name++; /* skip colon */
608 g->range.next = slrn_ranges_from_newsrc_line (name);
609 slrn_group_recount_unread (g);
610
611 if ((g->range.next != NULL)
612 && (g->range.next->min < g->unread)
613 && (g->range.next->min > 1))
614 g->unread -= g->range.next->min - 1;
615
616 return 0;
617 }
618
619 /*}}}*/
620
621 /* Rearrange Slrn_Group_Current_Group such that it follows last_group and
622 * return Slrn_Group_Current_Group. If last_group is NULL, Slrn_Group_Current_Group
623 * should be put at top of list.
624 */
place_group_in_newsrc_order(Slrn_Group_Type * last_group)625 static Slrn_Group_Type *place_group_in_newsrc_order (Slrn_Group_Type *last_group) /*{{{*/
626 {
627 Slrn_Group_Type *next_group, *prev_group;
628
629 next_group = Slrn_Group_Current_Group->next;
630 prev_group = Slrn_Group_Current_Group->prev;
631 if (next_group != NULL) next_group->prev = prev_group;
632 if (prev_group != NULL) prev_group->next = next_group;
633
634 Slrn_Group_Current_Group->prev = last_group;
635 if (last_group != NULL)
636 {
637 Slrn_Group_Current_Group->next = last_group->next;
638
639 if (Slrn_Group_Current_Group->next != NULL)
640 Slrn_Group_Current_Group->next->prev = Slrn_Group_Current_Group;
641
642 last_group->next = Slrn_Group_Current_Group;
643 }
644 else if (Slrn_Group_Current_Group != Groups)
645 {
646 Slrn_Group_Current_Group->next = Groups;
647 if (Groups != NULL) Groups->prev = Slrn_Group_Current_Group;
648 Groups = Slrn_Group_Current_Group;
649 }
650 else
651 {
652 /* correct next_group->prev since it was not set correctly above */
653 if (next_group != NULL)
654 next_group->prev = Slrn_Group_Current_Group;
655 }
656
657 return Slrn_Group_Current_Group;
658 }
659
660 /*}}}*/
insert_new_groups(void)661 static void insert_new_groups (void) /*{{{*/
662 {
663 Slrn_Group_Type *last_group = Groups;
664
665 while (last_group != NULL)
666 {
667 /* unmark new groups from previous run */
668 last_group->flags &= ~GROUP_NEW_GROUP_FLAG;
669 last_group = last_group->next;
670 }
671
672 if (Unsubscribed_Groups != NULL)
673 {
674 unsigned int subscribe_flag;
675 Unsubscribed_Slrn_Group_Type *ug = Unsubscribed_Groups, *ugnext;
676
677 if (Slrn_Unsubscribe_New_Groups)
678 subscribe_flag = GROUP_UNSUBSCRIBED | GROUP_NEW_GROUP_FLAG;
679 else subscribe_flag = GROUP_NEW_GROUP_FLAG;
680
681 while (ug != NULL)
682 {
683 ugnext = ug->next;
684
685 if (-1 != add_group (ug->group_name, strlen (ug->group_name), subscribe_flag, 0, 0))
686 last_group = place_group_in_newsrc_order (last_group);
687
688 free_unsubscribed_group_type (ug);
689 ug = ugnext;
690 }
691 Unsubscribed_Groups = NULL;
692 }
693 }
694
695 /*}}}*/
696
init_group_win_struct(void)697 static void init_group_win_struct (void) /*{{{*/
698 {
699 Group_Window.nrows = SLtt_Screen_Rows - 3;
700 Group_Window.hidden_mask = GROUP_HIDDEN;
701 Group_Window.current_line = (SLscroll_Type *) Slrn_Group_Current_Group;
702 Group_Window.cannot_scroll = SLtt_Term_Cannot_Scroll;
703 Group_Window.lines = (SLscroll_Type *) Groups;
704 Group_Window.border = 1;
705 if (Slrn_Scroll_By_Page)
706 {
707 /* Slrn_Group_Window.border = 0; */
708 Group_Window.cannot_scroll = 2;
709 }
710 find_line_num ();
711 }
712
713 /*}}}*/
714
find_group(char * name)715 static int find_group (char *name) /*{{{*/
716 {
717 Slrn_Group_Type *g = find_group_entry (name, strlen (name));
718 if (g == NULL) return 0;
719
720 g->flags &= ~GROUP_HIDDEN;
721 Slrn_Group_Current_Group = g;
722 find_line_num ();
723 return 1;
724 }
725
726 /*}}}*/
727
728 /* origpat needs enough space for SLRL_DISPLAY_BUFFER_SIZE chars */
read_group_regexp(char * prompt,char * origpat)729 static SLRegexp_Type *read_group_regexp (char *prompt, char *origpat) /*{{{*/
730 {
731 static char pattern[SLRL_DISPLAY_BUFFER_SIZE];
732
733 if (slrn_read_input (prompt, NULL, pattern, 1, 0) <= 0) return NULL;
734
735 if (origpat != NULL)
736 strcpy (origpat, pattern); /* safe */
737
738 return slrn_compile_regexp_pattern (slrn_fix_regexp (pattern));
739 }
740
741 /*}}}*/
742
add_unsubscribed_group(unsigned char * name)743 static void add_unsubscribed_group (unsigned char *name) /*{{{*/
744 {
745 Unsubscribed_Slrn_Group_Type *g;
746 unsigned char *p;
747
748 g = (Unsubscribed_Slrn_Group_Type *) slrn_safe_malloc (sizeof (Unsubscribed_Slrn_Group_Type));
749
750 g->next = Unsubscribed_Groups;
751 Unsubscribed_Groups = g;
752
753 p = name;
754 while (*p > ' ') p++;
755 *p = 0;
756
757 g->group_name = slrn_safe_strmalloc ((char*)name);
758 }
759
760 /*}}}*/
761
762 /*}}}*/
763
764 static char *Group_Display_Formats [SLRN_MAX_DISPLAY_FORMATS];
765 static unsigned int Group_Format_Number;
766
slrn_set_group_format(unsigned int num,char * fmt)767 int slrn_set_group_format (unsigned int num, char *fmt)
768 {
769 return slrn_set_display_format (Group_Display_Formats, num, fmt);
770 }
771
toggle_group_formats(void)772 static void toggle_group_formats (void)
773 {
774 Group_Format_Number = slrn_toggle_format (Group_Display_Formats,
775 Group_Format_Number);
776 }
777
slrn_group_search(char * str,int dir)778 int slrn_group_search (char *str, int dir) /*{{{*/
779 {
780 #if SLANG_VERSION < 20000
781 SLsearch_Type st;
782 #else
783 SLsearch_Type *st = NULL;
784 unsigned int flags;
785 #endif
786 Slrn_Group_Type *g;
787 int found = 0;
788
789 g = Slrn_Group_Current_Group;
790 if (g == NULL) return 0;
791
792 #if SLANG_VERSION < 20000
793 SLsearch_init (str, 1, 0, &st);
794 #else
795 flags = SLSEARCH_CASELESS;
796 if (Slrn_UTF8_Mode)
797 flags |= SLSEARCH_UTF8;
798
799 st = SLsearch_new ((SLuchar_Type *) str, flags);
800 if (st == NULL)
801 return 0;
802 #endif
803
804 do
805 {
806 if (dir > 0)
807 g = g->next;
808 else
809 g = g->prev;
810 if (g == NULL)
811 {
812 g = Groups;
813 if (dir < 0)
814 while (g->next != NULL)
815 g = g->next;
816 }
817
818 if ((g->flags & GROUP_HIDDEN) == 0)
819 {
820 #if SLANG_VERSION < 20000
821 if ((NULL != SLsearch ((unsigned char *) g->group_name,
822 (unsigned char *) g->group_name + strlen (g->group_name),
823 &st))
824 || ((NULL != g->descript)
825 && (NULL != SLsearch ((unsigned char *) g->descript,
826 (unsigned char *) g->descript + strlen (g->descript),
827 &st))))
828 {
829 found = 1;
830 break;
831 }
832 #else
833 if ((NULL != SLsearch_forward (st, (unsigned char *) g->group_name,
834 (unsigned char *) g->group_name + strlen (g->group_name)))
835 || ((NULL != g->descript)
836 && (NULL != SLsearch_forward (st, (unsigned char *) g->descript,
837 (unsigned char *) g->descript + strlen (g->descript)))))
838 {
839 found = 1;
840 break;
841 }
842 #endif
843 }
844 }
845 while (g != Slrn_Group_Current_Group);
846
847 #if SLANG_VERSION >= 20000
848 SLsearch_delete (st);
849 #endif
850
851 Slrn_Group_Current_Group = g;
852 find_line_num ();
853 return found;
854 }
855
856 /*}}}*/
slrn_group_up_n(unsigned int n)857 unsigned int slrn_group_up_n (unsigned int n) /*{{{*/
858 {
859 n = SLscroll_prev_n (&Group_Window, n);
860 Slrn_Group_Current_Group = (Slrn_Group_Type *) Group_Window.current_line;
861 return n;
862 }
863
864 /*}}}*/
slrn_group_down_n(unsigned int n)865 unsigned int slrn_group_down_n (unsigned int n) /*{{{*/
866 {
867 n = SLscroll_next_n (&Group_Window, n);
868 Slrn_Group_Current_Group = (Slrn_Group_Type *) Group_Window.current_line;
869 return n;
870 }
871
872 /*}}}*/
873
slrn_group_select_group(void)874 int slrn_group_select_group (void) /*{{{*/
875 {
876 NNTP_Artnum_Type min, max, n, max_available, last_n;
877 int ret;
878 Slrn_Range_Type *r;
879 int prefix;
880
881 if (Slrn_Prefix_Arg_Ptr != NULL)
882 {
883 prefix = *Slrn_Prefix_Arg_Ptr;
884 Slrn_Prefix_Arg_Ptr = NULL;
885 }
886 else prefix = 0;
887
888 if (Slrn_Group_Current_Group == NULL) return -1;
889
890 last_n = Slrn_Group_Current_Group->unread;
891
892 if (-1 == group_sync_group_with_server (Slrn_Group_Current_Group, &min, &max))
893 {
894 slrn_message (_("No articles to read."));
895 return -1;
896 }
897
898 n = Slrn_Group_Current_Group->unread;
899
900 #if 1
901 if ((prefix == 0) && (n == 0) && (n != last_n))
902 return -1;
903 #endif
904
905 max_available = Slrn_Group_Current_Group->range.max - Slrn_Group_Current_Group->range.min + 1;
906
907 if ((prefix != 0) || (n == 0))
908 n = max_available;
909
910 if ((prefix & 1)
911 || ((Slrn_Query_Group_Cutoff > 0)
912 && (n > (NNTP_Artnum_Type)Slrn_Query_Group_Cutoff))
913 || ((Slrn_Query_Group_Cutoff < 0)
914 && (n > (NNTP_Artnum_Type)(-Slrn_Query_Group_Cutoff))))
915 {
916 char int_prompt_buf[512];
917 if ((prefix & 1) || (Slrn_Query_Group_Cutoff > 0))
918 {
919 slrn_snprintf (int_prompt_buf, sizeof (int_prompt_buf),
920 _("%s: Read how many? "),
921 Slrn_Group_Current_Group->group_name);
922 if ((-1 == slrn_read_artnum_int (int_prompt_buf, &n, &n))
923 || (n <= 0))
924 {
925 slrn_clear_message ();
926 Slrn_Full_Screen_Update = 1;
927 return 0;
928 }
929 }
930 else
931 {
932 slrn_message_now (_("Only downloading %d of " NNTP_FMT_ARTNUM " articles."),
933 -Slrn_Query_Group_Cutoff, n);
934 n = -Slrn_Query_Group_Cutoff;
935 }
936
937 if ((0 == prefix)
938 && (Slrn_Group_Current_Group->unread != 0))
939 {
940 r = Slrn_Group_Current_Group->range.next;
941 if (r != NULL)
942 {
943 while (r->next != NULL) r = r->next;
944 if (r->max + n > max)
945 n = -n; /* special treatment in article mode
946 * because we will need to query the
947 * server about articles in a group
948 * that we have already read.
949 */
950 }
951 }
952 }
953 else if ((0 == prefix) && (Slrn_Group_Current_Group->unread != 0))
954 n = 0;
955
956 ret = slrn_select_article_mode (Slrn_Group_Current_Group, n,
957 ((prefix & 2) == 0));
958
959 if (ret == -2)
960 slrn_catchup_group ();
961
962 return ret;
963 }
964
965 /*}}}*/
slrn_select_next_group(void)966 void slrn_select_next_group (void) /*{{{*/
967 {
968 if (Slrn_Group_Current_Group == NULL)
969 return;
970
971 while ((SLang_get_error () == 0) && (1 == slrn_group_down_n (1)))
972 {
973 if (Slrn_Group_Current_Group->unread == 0)
974 continue;
975
976 if (0 == slrn_group_select_group ())
977 break;
978 else if (SLang_get_error () == INTRINSIC_ERROR)
979 /* all articles killed by scorefile, so proceed */
980 {
981 SLang_set_error (0);
982 slrn_clear_message ();
983 }
984 }
985 }
986
987 /*}}}*/
slrn_select_prev_group(void)988 void slrn_select_prev_group (void) /*{{{*/
989 {
990 if (Slrn_Group_Current_Group == NULL)
991 return;
992
993 while ((SLang_get_error () == 0) && (1 == slrn_group_up_n (1)))
994 {
995 if (Slrn_Group_Current_Group->unread == 0)
996 continue;
997
998 if (0 == slrn_group_select_group ())
999 break;
1000 }
1001 }
1002
1003 /*}}}*/
1004
1005 /*{{{ Interactive commands */
1006
slrn_group_quit(void)1007 void slrn_group_quit (void) /*{{{*/
1008 {
1009 if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_QUIT)
1010 && (Slrn_Batch == 0)
1011 && (slrn_get_yesno (1, _("Do you really want to quit")) <= 0)) return;
1012
1013 if ((Slrn_Groups_Dirty) && (-1 == slrn_write_newsrc (0)))
1014 {
1015 if (Slrn_Batch)
1016 slrn_quit (1);
1017
1018 slrn_smg_refresh ();
1019 if (Slrn_Batch == 0) slrn_sleep (2);
1020 if (slrn_get_yesno (0, _("Write to newsrc file failed. Quit anyway")) <= 0)
1021 return;
1022 }
1023 slrn_quit (0);
1024 }
1025
1026 /*}}}*/
1027
group_pagedown(void)1028 static void group_pagedown (void) /*{{{*/
1029 {
1030 Slrn_Full_Screen_Update = 1;
1031
1032 if (-1 == SLscroll_pagedown (&Group_Window))
1033 slrn_error (_("End of Buffer."));
1034 Slrn_Group_Current_Group = (Slrn_Group_Type *) Group_Window.current_line;
1035 }
1036
1037 /*}}}*/
1038
group_pageup(void)1039 static void group_pageup (void) /*{{{*/
1040 {
1041 Slrn_Full_Screen_Update = 1;
1042
1043 if (-1 == SLscroll_pageup (&Group_Window))
1044 slrn_error (_("Top of Buffer."));
1045
1046 Slrn_Group_Current_Group = (Slrn_Group_Type *) Group_Window.current_line;
1047 }
1048
1049 /*}}}*/
1050
group_up(void)1051 static void group_up (void) /*{{{*/
1052 {
1053 if (0 == slrn_group_up_n (1))
1054 {
1055 slrn_error (_("Top of buffer."));
1056 }
1057 }
1058
1059 /*}}}*/
1060
set_current_group(void)1061 static void set_current_group (void) /*{{{*/
1062 {
1063 Slrn_Group_Type *g;
1064
1065 g = Slrn_Group_Current_Group;
1066 if (g == NULL) g = Groups;
1067
1068 while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->next;
1069 if ((g == NULL) && (Slrn_Group_Current_Group != NULL))
1070 {
1071 g = Slrn_Group_Current_Group -> prev;
1072 while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->prev;
1073 }
1074 Slrn_Group_Current_Group = g;
1075
1076 /* When there are less than SCREEN_HEIGHT-2 groups, they should all get
1077 * displayed; scroll to the top of the buffer to ensure this. */
1078 while (SLscroll_prev_n (&Group_Window, 1000));
1079 (void) SLscroll_find_top (&Group_Window);
1080 find_line_num ();
1081
1082 Slrn_Full_Screen_Update = 1;
1083 }
1084
1085 /*}}}*/
1086
refresh_groups(Slrn_Group_Type ** c)1087 static void refresh_groups (Slrn_Group_Type **c) /*{{{*/
1088 {
1089 Slrn_Group_Type *g = Groups;
1090 Slrn_Group_Range_Type *ranges;
1091
1092 if (Slrn_Max_Queued_Groups <= 0)
1093 Slrn_Max_Queued_Groups = 1;
1094 ranges = (Slrn_Group_Range_Type *) slrn_safe_malloc
1095 (Slrn_Max_Queued_Groups * sizeof(Slrn_Group_Range_Type));
1096
1097 while (g != NULL)
1098 {
1099 Slrn_Group_Type *start = g;
1100 int i = 0, j = 0;
1101
1102 while ((g != NULL) && (i < Slrn_Max_Queued_Groups))
1103 {
1104 if (!(g->flags & GROUP_UNSUBSCRIBED))
1105 {
1106 ranges[i].name = g->group_name;
1107 i++;
1108 }
1109 g = g->next;
1110 }
1111 if (Slrn_Server_Obj->sv_refresh_groups (ranges, i))
1112 {
1113 slrn_error (_("Server connection dropped."));
1114 goto free_and_return;
1115 }
1116 g = start;
1117 while (j < i)
1118 {
1119 if (g->flags & GROUP_UNSUBSCRIBED)
1120 {
1121 g = g->next;
1122 continue;
1123 }
1124 if (ranges[j].min == -1)
1125 {
1126 slrn_message_now (_("Group %s is bogus%s."), g->group_name,
1127 Slrn_Drop_Bogus_Groups ? _(" - dropping it") : "");
1128 Slrn_Saw_Warning = 1;
1129 if (Slrn_Drop_Bogus_Groups)
1130 {
1131 Slrn_Group_Type *tmp = g->next;
1132 remove_group_entry (g);
1133 if (g == *c) *c = Groups;
1134 g = tmp;
1135 j++;
1136 continue;
1137 }
1138 }
1139 else
1140 group_update_range (g, ranges[j].min, ranges[j].max);
1141 g = g->next;
1142 j++;
1143 }
1144 while ((g != NULL) && (g->flags & GROUP_UNSUBSCRIBED))
1145 g = g->next;
1146 }
1147 free_and_return:
1148 SLfree ((char*) ranges);
1149 }
1150 /*}}}*/
1151
refresh_groups_cmd(void)1152 static void refresh_groups_cmd (void) /*{{{*/
1153 {
1154 Slrn_Group_Type *c = Slrn_Group_Current_Group;
1155 Slrn_Group_Current_Group = NULL;
1156
1157 slrn_message_now (_("Checking news%s ..."),
1158 Slrn_List_Active_File ? _(" via active file") : "");
1159
1160 if (Slrn_List_Active_File)
1161 {
1162 read_and_parse_active (0);
1163 if ((Slrn_Server_Obj->sv_id == SERVER_ID_INN) && (c != NULL))
1164 /* hack: avoid a problem with inn not updating the high water mark */
1165 {
1166 Slrn_Group_Type *a = c, *b;
1167 NNTP_Artnum_Type min, max;
1168
1169 if (((NULL != (b = c->next)) || (NULL != (b = c->prev))) &&
1170 (0 == strcmp (c->group_name,
1171 Slrn_Server_Obj->sv_current_group ())))
1172 {
1173 a = b;
1174 }
1175
1176 (void) group_sync_group_with_server (a, &min, &max);
1177 }
1178 }
1179
1180 if (Slrn_Check_New_Groups)
1181 {
1182 slrn_get_new_groups (0);
1183 insert_new_groups ();
1184 }
1185
1186 if (!Slrn_List_Active_File)
1187 {
1188 refresh_groups (&c);
1189 }
1190
1191 slrn_read_group_descriptions ();
1192
1193 Slrn_Group_Current_Group = c;
1194 set_current_group ();
1195 group_quick_help ();
1196 }
1197
1198 /*}}}*/
1199
generic_group_search(int dir)1200 static void generic_group_search (int dir) /*{{{*/
1201 {
1202 static char search_str[SLRL_DISPLAY_BUFFER_SIZE];
1203 char* prompt;
1204 Slrn_Group_Type *g;
1205 unsigned int n;
1206 int ret;
1207
1208 g = Slrn_Group_Current_Group;
1209 if (g == NULL) return;
1210
1211 prompt = slrn_strdup_strcat ((dir > 0 ? _("Forward") : _("Backward")),
1212 _(" Search: "), NULL);
1213 ret = slrn_read_input (prompt, search_str, NULL, 1, 0);
1214 slrn_free (prompt);
1215 if (ret <= 0) return;
1216
1217 n = Group_Window.line_num;
1218 if (0 == slrn_group_search (search_str, dir))
1219 {
1220 slrn_error (_("Not found."));
1221 return;
1222 }
1223
1224 if (((dir > 0) && (n > Group_Window.line_num)) ||
1225 ((dir < 0) && (n < Group_Window.line_num)))
1226 slrn_message (_("Search wrapped."));
1227 }
1228
1229 /*}}}*/
1230
group_search_forward(void)1231 static void group_search_forward (void) /*{{{*/
1232 {
1233 generic_group_search (1);
1234 }
1235 /*}}}*/
1236
group_search_backward(void)1237 static void group_search_backward (void) /*{{{*/
1238 {
1239 generic_group_search (-1);
1240 }
1241 /*}}}*/
1242
slrn_add_group(char * group)1243 int slrn_add_group (char *group) /*{{{*/
1244 {
1245 int retval = 0;
1246 if (!find_group (group))
1247 {
1248 if (Slrn_List_Active_File == 0)
1249 {
1250 retval = add_group (group, strlen (group), 0, 1, 0);
1251 slrn_read_group_descriptions ();
1252 }
1253 else
1254 {
1255 slrn_error (_("Group %s does not exist."), group);
1256 retval = -1;
1257 }
1258 }
1259 Slrn_Groups_Dirty = 1;
1260 Slrn_Full_Screen_Update = 1;
1261 find_line_num ();
1262 return retval;
1263 }
1264 /*}}}*/
1265
add_group_cmd(void)1266 static void add_group_cmd (void) /*{{{*/
1267 {
1268 char group[SLRL_DISPLAY_BUFFER_SIZE];
1269
1270 *group = 0;
1271 if (slrn_read_input (_("Add group: "), NULL, group, 1, 0) > 0)
1272 (void) slrn_add_group (group);
1273 }
1274
1275 /*}}}*/
1276
group_down(void)1277 static void group_down (void) /*{{{*/
1278 {
1279 if (1 != slrn_group_down_n (1))
1280 {
1281 slrn_error (_("End of Buffer."));
1282 }
1283 }
1284
1285 /*}}}*/
1286
transpose_groups(void)1287 static void transpose_groups (void) /*{{{*/
1288 {
1289 Slrn_Group_Type *g, *g1, *tmp;
1290
1291 if (NULL == (g = Slrn_Group_Current_Group))
1292 return;
1293
1294 if (1 != slrn_group_up_n (1))
1295 return;
1296
1297 g1 = Slrn_Group_Current_Group;
1298 tmp = g1->next;
1299
1300 g1->next = g->next;
1301 if (g1->next != NULL) g1->next->prev = g1;
1302 g->next = tmp;
1303 tmp->prev = g; /* tmp cannot be NULL but it can be
1304 * equal to g. This link is corrected
1305 * below
1306 */
1307
1308 tmp = g1->prev;
1309 g1->prev = g->prev;
1310 g1->prev->next = g1; /* g1->prev cannot be NULL */
1311 g->prev = tmp;
1312 if (tmp != NULL) tmp->next = g;
1313
1314 if (g1 == Groups) Groups = g;
1315
1316 find_line_num ();
1317
1318 (void) slrn_group_down_n (1);
1319
1320 Slrn_Full_Screen_Update = 1;
1321 Slrn_Groups_Dirty = 1;
1322 }
1323
1324 /*}}}*/
1325
move_group_cmd(void)1326 static void move_group_cmd (void) /*{{{*/
1327 {
1328 SLang_Key_Type *key;
1329 void (*f)(void);
1330 Slrn_Group_Type *from, *to;
1331
1332 if (Slrn_Batch) return;
1333 if (Slrn_Group_Current_Group == NULL) return;
1334 from = Slrn_Group_Current_Group;
1335
1336 /* Already centering the window here should avoid confusing the user */
1337 Group_Window.top_window_line = NULL;
1338 slrn_update_screen ();
1339
1340 while (1)
1341 {
1342 slrn_message_now (_("Moving %s. Press RETURN when finished."), Slrn_Group_Current_Group->group_name);
1343
1344 /* key = SLang_do_key (Slrn_Group_Keymap, (int (*)(void)) SLang_getkey); */
1345 key = SLang_do_key (Slrn_Group_Keymap, slrn_getkey);
1346
1347 if ((key == NULL)
1348 || (key->type == SLKEY_F_INTERPRET))
1349 f = NULL;
1350 else f = (void (*)(void)) key->f.f;
1351
1352 if ((f == group_up) || (f == group_down))
1353 {
1354 if (f == group_down)
1355 (void) slrn_group_down_n (1);
1356 else
1357 (void) slrn_group_up_n (1);
1358
1359 to = Slrn_Group_Current_Group;
1360 if (from == to) break;
1361
1362 Slrn_Full_Screen_Update = 1;
1363 Slrn_Groups_Dirty = 1;
1364
1365 if (NULL != from->next)
1366 from->next->prev = from->prev;
1367 if (NULL != from->prev)
1368 from->prev->next = from->next;
1369
1370 if (f == group_down)
1371 {
1372 if (NULL != to->next)
1373 to->next->prev = from;
1374 from->next = to->next;
1375 from->prev = to;
1376 to->next = from;
1377 if (from == Groups) Groups = to;
1378 }
1379 else
1380 {
1381 if (NULL != to->prev)
1382 to->prev->next = from;
1383 from->prev = to->prev;
1384 from->next = to;
1385 to->prev = from;
1386 if (to == Groups) Groups = from;
1387 }
1388 }
1389 else break;
1390
1391 if (from != Slrn_Group_Current_Group)
1392 {
1393 Slrn_Group_Current_Group = from;
1394 find_line_num ();
1395 }
1396
1397 /* For a recenter if possible. */
1398 /* if (Group_Window.top_window_line == Group_Window.current_line) */
1399 Group_Window.top_window_line = NULL;
1400
1401 slrn_update_screen ();
1402 }
1403 }
1404
1405 /*}}}*/
1406
subscribe(void)1407 static void subscribe (void) /*{{{*/
1408 {
1409 SLRegexp_Type *re;
1410 Slrn_Group_Type *g;
1411
1412 if (Slrn_Prefix_Arg_Ptr == NULL)
1413 {
1414 if (Slrn_Group_Current_Group != NULL)
1415 {
1416 Slrn_Group_Current_Group->flags &= ~GROUP_UNSUBSCRIBED;
1417 Slrn_Group_Current_Group->flags |= GROUP_TOUCHED;
1418 slrn_group_down_n (1);
1419 Slrn_Groups_Dirty = 1;
1420 }
1421 return;
1422 }
1423
1424 Slrn_Prefix_Arg_Ptr = NULL;
1425
1426 if (NULL == (re = read_group_regexp (_("Subscribe pattern: "), NULL)))
1427 return;
1428
1429 g = Groups;
1430 while (g != NULL)
1431 {
1432 if (g->flags & GROUP_UNSUBSCRIBED)
1433 {
1434 if (NULL != slrn_regexp_match (re, g->group_name))
1435 {
1436 g->flags &= ~GROUP_HIDDEN;
1437 g->flags &= ~GROUP_UNSUBSCRIBED;
1438 g->flags |= GROUP_TOUCHED;
1439 Slrn_Groups_Dirty = 1;
1440 }
1441 }
1442 g = g->next;
1443 }
1444 find_line_num ();
1445 Slrn_Full_Screen_Update = 1;
1446 #if SLANG_VERSION >= 20000
1447 SLregexp_free (re);
1448 #endif
1449 }
1450
1451 /*}}}*/
1452
catch_up(void)1453 static void catch_up (void) /*{{{*/
1454 {
1455 if ((Slrn_Group_Current_Group == NULL)
1456 || ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_CATCHUP)
1457 && (Slrn_Batch == 0)
1458 && slrn_get_yesno(1, _("Mark %s as read"), Slrn_Group_Current_Group->group_name) <= 0))
1459 return;
1460
1461 slrn_catchup_group ();
1462 slrn_message (_("Group marked as read."));
1463 (void) slrn_group_down_n (1);
1464 }
1465
1466 /*}}}*/
1467
uncatch_up(void)1468 static void uncatch_up (void) /*{{{*/
1469 {
1470 if ((Slrn_Group_Current_Group == NULL)
1471 || ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_CATCHUP)
1472 && (Slrn_Batch == 0)
1473 && slrn_get_yesno(1, _("Mark %s as un-read"), Slrn_Group_Current_Group->group_name) <= 0))
1474 return;
1475
1476 slrn_uncatchup_group ();
1477 slrn_message (_("Group marked as un-read."));
1478 (void) slrn_group_down_n (1);
1479 }
1480
1481 /*}}}*/
1482
unsubscribe(void)1483 static void unsubscribe (void) /*{{{*/
1484 {
1485 SLRegexp_Type *re;
1486 Slrn_Group_Type *g;
1487
1488 if (Slrn_Group_Current_Group == NULL) return;
1489
1490 if (Slrn_Prefix_Arg_Ptr == NULL)
1491 {
1492 Slrn_Group_Current_Group->flags |= GROUP_UNSUBSCRIBED | GROUP_TOUCHED;
1493 slrn_group_down_n (1);
1494 Slrn_Groups_Dirty = 1;
1495 return;
1496 }
1497
1498 Slrn_Prefix_Arg_Ptr = NULL;
1499
1500 if (NULL == (re = read_group_regexp (_("Un-Subscribe pattern: "), NULL)))
1501 return;
1502
1503 g = Groups;
1504 while (g != NULL)
1505 {
1506 if ((g->flags & GROUP_UNSUBSCRIBED) == 0)
1507 {
1508 if (NULL != (slrn_regexp_match (re, g->group_name)))
1509 {
1510 g->flags &= ~GROUP_HIDDEN;
1511 g->flags |= (GROUP_TOUCHED | GROUP_UNSUBSCRIBED);
1512 }
1513 }
1514 g = g->next;
1515 }
1516 find_line_num ();
1517 Slrn_Full_Screen_Update = 1;
1518 #if SLANG_VERSION >= 20000
1519 SLregexp_free (re);
1520 #endif
1521 }
1522
1523 /*}}}*/
1524
group_bob(void)1525 static void group_bob (void)
1526 {
1527 while (slrn_group_up_n (1000));
1528 }
1529
group_eob(void)1530 static void group_eob (void)
1531 {
1532 while (slrn_group_down_n (1000));
1533 }
1534
toggle_list_all_groups1(int hide_flag)1535 static void toggle_list_all_groups1 (int hide_flag) /*{{{*/
1536 {
1537 Slrn_Group_Type *g, *first_found = NULL;
1538 static int all_hidden = 1;
1539
1540 g = Groups;
1541
1542 if (hide_flag != -1)
1543 {
1544 all_hidden = hide_flag;
1545 }
1546 else all_hidden = !all_hidden;
1547
1548 if (all_hidden)
1549 {
1550 while (g != NULL)
1551 {
1552 if (g->flags & GROUP_UNSUBSCRIBED) g->flags |= GROUP_HIDDEN;
1553 g = g->next;
1554 }
1555 }
1556 else if (hide_flag != -1)
1557 {
1558 while (g != NULL)
1559 {
1560 if (g->flags & GROUP_UNSUBSCRIBED) g->flags &= ~GROUP_HIDDEN;
1561 g = g->next;
1562 }
1563 }
1564 else
1565 {
1566 SLRegexp_Type *re;
1567 char origpat[SLRL_DISPLAY_BUFFER_SIZE];
1568
1569 if (NULL == (re = read_group_regexp (_("List Groups (e.g., comp*unix*): "),
1570 origpat)))
1571 {
1572 all_hidden = 1;
1573 return;
1574 }
1575
1576 if ((Slrn_List_Active_File == 0)
1577 && (OK_GROUPS == Slrn_Server_Obj->sv_list_active (origpat)))
1578 {
1579 char buf [NNTP_BUFFER_SIZE];
1580 Slrn_Group_Type *save = Slrn_Group_Current_Group;
1581 Slrn_Group_Current_Group = NULL;
1582
1583 while (Slrn_Server_Obj->sv_read_line (buf, sizeof (buf)) > 0)
1584 {
1585 unsigned int len;
1586 int min, max;
1587
1588 parse_active_line ((unsigned char *)buf, &len, &min, &max);
1589 g = create_group_entry (buf, len, min, max, 0, 0);
1590
1591 if (g != NULL)
1592 {
1593 g->flags &= ~GROUP_HIDDEN;
1594 if ((first_found == NULL) && (g->flags & GROUP_UNSUBSCRIBED))
1595 first_found = g;
1596 }
1597 }
1598
1599 Slrn_Group_Current_Group = save;
1600 }
1601 else while (g != NULL)
1602 {
1603 if (NULL != slrn_regexp_match (re, g->group_name))
1604 {
1605 if ((first_found == NULL) && (g->flags & GROUP_UNSUBSCRIBED))
1606 first_found = g;
1607 g->flags &= ~GROUP_HIDDEN;
1608 }
1609 g = g->next;
1610 }
1611 #if SLANG_VERSION >= 20000
1612 SLregexp_free (re);
1613 #endif
1614 }
1615
1616 g = Slrn_Group_Current_Group;
1617 if (first_found != NULL)
1618 g = first_found;
1619 else
1620 {
1621 while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->next;
1622 if ((g == NULL) && (Slrn_Group_Current_Group != NULL))
1623 {
1624 g = Slrn_Group_Current_Group -> prev;
1625 while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->prev;
1626 }
1627 }
1628 Slrn_Group_Current_Group = g;
1629
1630 Slrn_Full_Screen_Update = 1;
1631
1632 if ((all_hidden == 0) && (Slrn_Group_Current_Group == NULL))
1633 {
1634 Slrn_Group_Current_Group = Groups;
1635 if ((Slrn_Group_Current_Group != NULL)
1636 && (Slrn_Group_Current_Group->flags & GROUP_HIDDEN))
1637 {
1638 Slrn_Group_Current_Group = NULL;
1639 }
1640 }
1641
1642 find_line_num ();
1643 }
1644
1645 /*}}}*/
1646
toggle_list_all_groups(void)1647 static void toggle_list_all_groups (void) /*{{{*/
1648 {
1649 int mode = -1;
1650
1651 if (Slrn_Prefix_Arg_Ptr != NULL)
1652 {
1653 mode = *Slrn_Prefix_Arg_Ptr;
1654 Slrn_Prefix_Arg_Ptr = NULL;
1655 if (mode == 2) mode = 0;
1656 }
1657
1658 toggle_list_all_groups1 (mode);
1659 }
1660
1661 /*}}}*/
1662
slrn_list_all_groups(int mode)1663 void slrn_list_all_groups (int mode)
1664 {
1665 toggle_list_all_groups1 (!mode);
1666 }
1667
slrn_hide_current_group(void)1668 void slrn_hide_current_group (void) /*{{{*/
1669 {
1670 if (Slrn_Group_Current_Group == NULL) return;
1671 Groups_Hidden |= 2;
1672 Slrn_Group_Current_Group->flags |= GROUP_HIDDEN;
1673 set_current_group ();
1674 }
1675 /*}}}*/
1676
toggle_hide_groups(void)1677 static void toggle_hide_groups (void) /*{{{*/
1678 {
1679 Slrn_Group_Type *g;
1680
1681 Groups_Hidden = !Groups_Hidden;
1682
1683 g = Groups;
1684
1685 if (Groups_Hidden)
1686 {
1687 while (g != NULL)
1688 {
1689 if ((g->unread == 0)
1690 && ((g->flags & GROUP_UNSUBSCRIBED) == 0))
1691 g->flags |= GROUP_HIDDEN;
1692
1693 g = g->next;
1694 }
1695 }
1696 else
1697 {
1698 while (g != NULL)
1699 {
1700 if ((g->flags & GROUP_UNSUBSCRIBED) == 0)
1701 g->flags &= ~GROUP_HIDDEN;
1702
1703 g = g->next;
1704 }
1705 }
1706
1707 set_current_group ();
1708 }
1709
1710 /*}}}*/
1711
slrn_hide_groups(int mode)1712 void slrn_hide_groups (int mode)
1713 {
1714 Groups_Hidden = !mode;
1715 toggle_hide_groups ();
1716 }
1717
select_group_cmd(void)1718 static void select_group_cmd (void)
1719 {
1720 if (-1 == slrn_group_select_group ())
1721 slrn_error (_("No unread articles."));
1722 }
1723
slrn_post_cmd(void)1724 void slrn_post_cmd (void) /*{{{*/
1725 {
1726 char *name;
1727 char group[SLRL_DISPLAY_BUFFER_SIZE];
1728 char followupto[SLRL_DISPLAY_BUFFER_SIZE];
1729 char subj[SLRL_DISPLAY_BUFFER_SIZE];
1730
1731 if (Slrn_Post_Obj->po_can_post == 0)
1732 {
1733 slrn_error (_("Posting not allowed."));
1734 return;
1735 }
1736
1737 if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_POST) && (Slrn_Batch == 0) &&
1738 (slrn_get_yesno (1, _("Are you sure that you want to post")) <= 0))
1739 return;
1740
1741 slrn_run_hooks (HOOK_POST, 0);
1742 if (SLang_get_error ())
1743 return;
1744
1745 if (Slrn_Group_Current_Group == NULL)
1746 name = "";
1747 else name = Slrn_Group_Current_Group->group_name;
1748
1749 if (strlen (name) >= sizeof (group))
1750 name = "";
1751 slrn_strncpy (group, name, sizeof (group));
1752 if (slrn_read_input (_("Newsgroup: "), NULL, group, 1, -1) <= 0) return;
1753 if (slrn_strbyte (group, ',') != NULL)
1754 {
1755 slrn_strncpy (followupto, name, sizeof (followupto));
1756 (void) slrn_read_input (_("Followup-To: "), NULL, followupto, 1, -1);
1757 }
1758 else
1759 *followupto = '\0';
1760
1761 *subj = 0; if (slrn_read_input (_("Subject: "), NULL, subj, 1, 0) <= 0) return;
1762
1763 (void) slrn_post (group, followupto, subj);
1764 }
1765
1766 /*}}}*/
1767
toggle_scoring(void)1768 static void toggle_scoring (void) /*{{{*/
1769 {
1770 /* Note to translators: Here, "fF" means "full", "sS" is "simple",
1771 * "nN" means "none" and "cC" is "cancel".
1772 * As always, don't change the length of the string; you cannot use
1773 * the default characters for different fields.
1774 */
1775 char rsp, *responses = _("fFsSnNcC");
1776
1777 if (-1 == slrn_check_batch ())
1778 return;
1779 if (strlen (responses) != 8)
1780 responses = "";
1781 rsp = slrn_get_response ("fFsSnNcC\007", responses,
1782 _("Select scoring mode: \001Full, \001Simple, \001None, \001Cancel"));
1783
1784 if (rsp != 7)
1785 rsp = slrn_map_translated_char ("fFsSnNcC", responses, rsp) | 0x20;
1786 switch (rsp)
1787 {
1788 case 'f':
1789 Slrn_Perform_Scoring = SLRN_XOVER_SCORING | SLRN_EXPENSIVE_SCORING;
1790 slrn_message (_("Full Header Scoring enabled."));
1791 break;
1792
1793 case 's':
1794 Slrn_Perform_Scoring = SLRN_XOVER_SCORING;
1795 slrn_message (_("Expensive Scoring disabled."));
1796 break;
1797
1798 case 'n':
1799 Slrn_Perform_Scoring = 0;
1800 slrn_message (_("Scoring disabled."));
1801 break;
1802
1803 default:
1804 slrn_clear_message ();
1805 break;
1806 }
1807 }
1808
1809 /*}}}*/
save_newsrc_cmd(void)1810 static void save_newsrc_cmd (void) /*{{{*/
1811 {
1812 if (Slrn_Groups_Dirty)
1813 {
1814 slrn_write_newsrc (0);
1815 }
1816 else
1817 {
1818 slrn_message (_("No changes need to be saved."));
1819 }
1820 slrn_smg_refresh ();
1821 }
1822
1823 /*}}}*/
1824
1825 /*}}}*/
1826
1827 /*{{{ Group Mode Initialization/Keybindings */
1828
slrn_group_hup(int sig)1829 static void slrn_group_hup (int sig)
1830 {
1831 slrn_write_newsrc (0);
1832 slrn_quit (sig);
1833 }
1834
enter_group_mode_hook(void)1835 static void enter_group_mode_hook (void)
1836 {
1837 if (Slrn_Scroll_By_Page)
1838 Group_Window.cannot_scroll = 2;
1839 else
1840 Group_Window.cannot_scroll = SLtt_Term_Cannot_Scroll;
1841 slrn_run_hooks (HOOK_GROUP_MODE, 0);
1842 }
1843
group_winch_sig(int old_r,int old_c)1844 static void group_winch_sig (int old_r, int old_c)
1845 {
1846 (void) old_r; (void) old_c;
1847
1848 if (SLtt_Screen_Rows > 3)
1849 Group_Window.nrows = SLtt_Screen_Rows - 3;
1850 else
1851 Group_Window.nrows = 1;
1852 }
1853
1854 static Slrn_Mode_Type Group_Mode_Cap =
1855 {
1856 NULL,
1857 group_update_screen, /* redraw */
1858 group_winch_sig, /* sig winch hook */
1859 slrn_group_hup, /* hangup hook */
1860 enter_group_mode_hook, /* enter_mode_hook */
1861 SLRN_GROUP_MODE
1862 };
1863
1864 /*{{{ Group Mode Keybindings */
1865
1866 #define A_KEY(s, f) {s, (int (*)(void)) f}
1867 static SLKeymap_Function_Type Group_Functions [] = /*{{{*/
1868 {
1869 A_KEY("add_group", add_group_cmd),
1870 A_KEY("bob", group_bob),
1871 A_KEY("catchup", catch_up),
1872 A_KEY("digit_arg", slrn_digit_arg),
1873 A_KEY("eob", group_eob),
1874 A_KEY("evaluate_cmd", slrn_evaluate_cmd),
1875 A_KEY("group_search", group_search_forward),
1876 A_KEY("group_search_backward", group_search_backward),
1877 A_KEY("group_search_forward", group_search_forward),
1878 A_KEY("help", slrn_group_help),
1879 A_KEY("line_down", group_down),
1880 A_KEY("line_up", group_up),
1881 A_KEY("move_group", move_group_cmd),
1882 A_KEY("page_down", group_pagedown),
1883 A_KEY("page_up", group_pageup),
1884 A_KEY("post", slrn_post_cmd),
1885 A_KEY("post_postponed", slrn_post_postponed),
1886 A_KEY("quit", slrn_group_quit),
1887 A_KEY("redraw", slrn_redraw),
1888 A_KEY("refresh_groups", refresh_groups_cmd),
1889 A_KEY("repeat_last_key", slrn_repeat_last_key),
1890 A_KEY("save_newsrc", save_newsrc_cmd),
1891 A_KEY("select_group", select_group_cmd),
1892 A_KEY("subscribe", subscribe),
1893 A_KEY("suspend", slrn_suspend_cmd),
1894 A_KEY("toggle_group_formats", toggle_group_formats),
1895 A_KEY("toggle_hidden", toggle_hide_groups),
1896 A_KEY("toggle_list_all", toggle_list_all_groups),
1897 A_KEY("toggle_scoring", toggle_scoring),
1898 A_KEY("transpose_groups", transpose_groups),
1899 A_KEY("uncatchup", uncatch_up),
1900 A_KEY("unsubscribe", unsubscribe),
1901 #if 1 /* FIXME: These ones are going to be deleted before 1.0 */
1902 A_KEY("down", group_down),
1903 A_KEY("group_bob", group_bob),
1904 A_KEY("group_eob", group_eob),
1905 A_KEY("pagedown", group_pagedown),
1906 A_KEY("pageup", group_pageup),
1907 A_KEY("toggle_group_display", toggle_group_formats),
1908 A_KEY("uncatch_up", uncatch_up),
1909 A_KEY("up", group_up),
1910 #endif
1911 A_KEY(NULL, NULL)
1912 };
1913
1914 /*}}}*/
1915
1916 /*{{{ Mouse Functions*/
1917
1918 /* actions for different regions:
1919 * - top status line (help)
1920 * - normal region
1921 * - bottom status line
1922 */
group_mouse(void (* top_status)(void),void (* bot_status)(void),void (* normal_region)(void))1923 static void group_mouse (void (*top_status)(void),
1924 void (*bot_status)(void),
1925 void (*normal_region)(void)
1926 )
1927 {
1928 int r,c;
1929
1930 slrn_get_mouse_rc (&r, &c);
1931
1932 /* take top status line into account */
1933 if (r == 1)
1934 {
1935 if (Slrn_Use_Mouse)
1936 slrn_execute_menu (c);
1937 else
1938 if (NULL != top_status) (*top_status) ();
1939 return;
1940 }
1941
1942 if (r >= SLtt_Screen_Rows)
1943 return;
1944
1945 /* bottom status line */
1946 if (r == SLtt_Screen_Rows - 1)
1947 {
1948 if (NULL != bot_status) (*bot_status) ();
1949 return;
1950 }
1951
1952 r -= (1 + Last_Cursor_Row);
1953 if (r < 0)
1954 {
1955 r = -r;
1956 if (r != (int) slrn_group_up_n (r)) return;
1957 }
1958 else if (r != (int) slrn_group_down_n (r)) return;
1959
1960 if (NULL != normal_region) (*normal_region) ();
1961 }
1962
group_mouse_left(void)1963 static void group_mouse_left (void)
1964 {
1965 group_mouse (slrn_group_help, group_pagedown, select_group_cmd);
1966 }
1967
group_mouse_middle(void)1968 static void group_mouse_middle (void)
1969 {
1970 group_mouse (toggle_group_formats, toggle_hide_groups, select_group_cmd);
1971 #if 1
1972 /* Make up for buggy rxvt which have problems with the middle key. */
1973 if (NULL != getenv ("COLORTERM"))
1974 {
1975 if (SLang_input_pending (7))
1976 {
1977 while (SLang_input_pending (0))
1978 (void) SLang_getkey ();
1979 }
1980 }
1981 #endif
1982 }
1983
group_mouse_right(void)1984 static void group_mouse_right (void)
1985 {
1986 group_mouse (slrn_group_help, group_pageup, select_group_cmd);
1987 }
1988
1989 /*}}}*/
1990
1991 /*}}}*/
1992
1993 #define USE_TEST_FUNCTION 0
1994 #if USE_TEST_FUNCTION
test_function(void)1995 static void test_function (void)
1996 {
1997 char *file;
1998 file = slrn_browse_dir (".");
1999 if (file != NULL)
2000 {
2001 slrn_message (file);
2002 slrn_free (file);
2003 }
2004 }
2005 #endif
2006
slrn_init_group_mode(void)2007 void slrn_init_group_mode (void) /*{{{*/
2008 {
2009 char *err = _("Unable to create group keymap!");
2010
2011 if (NULL == (Slrn_Group_Keymap = SLang_create_keymap ("group", NULL)))
2012 slrn_exit_error ("%s", err);
2013
2014 Group_Mode_Cap.keymap = Slrn_Group_Keymap;
2015
2016 Slrn_Group_Keymap->functions = Group_Functions;
2017
2018 SLkm_define_key ("\0331", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2019 SLkm_define_key ("\0332", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2020 SLkm_define_key ("\0333", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2021 SLkm_define_key ("\0334", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2022 SLkm_define_key ("\0335", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2023 SLkm_define_key ("\0336", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2024 SLkm_define_key ("\0337", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2025 SLkm_define_key ("\0338", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2026 SLkm_define_key ("\0339", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2027 SLkm_define_key ("\0330", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2028 SLkm_define_key ("^K\033[A", (FVOID_STAR) group_bob, Slrn_Group_Keymap);
2029 SLkm_define_key ("^K\033OA", (FVOID_STAR) group_bob, Slrn_Group_Keymap);
2030 SLkm_define_key ("^K\033[B", (FVOID_STAR) group_eob, Slrn_Group_Keymap);
2031 SLkm_define_key ("^K\033OB", (FVOID_STAR) group_eob, Slrn_Group_Keymap);
2032 SLkm_define_key ("\033a", (FVOID_STAR) toggle_group_formats, Slrn_Group_Keymap);
2033 SLkm_define_key ("\033>", (FVOID_STAR) group_eob, Slrn_Group_Keymap);
2034 SLkm_define_key ("\033<", (FVOID_STAR) group_bob, Slrn_Group_Keymap);
2035 SLkm_define_key ("^D", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2036 SLkm_define_key ("^V", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2037 #if defined(IBMPC_SYSTEM)
2038 SLkm_define_key ("^@Q", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2039 SLkm_define_key ("\xE0Q", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2040 SLkm_define_key ("^@I", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2041 SLkm_define_key ("\xE0I", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2042 #else
2043 SLkm_define_key ("\033[6~", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2044 SLkm_define_key ("\033[G", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2045 SLkm_define_key ("\033[5~", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2046 SLkm_define_key ("\033[I", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2047 #endif
2048 SLkm_define_key ("m", (FVOID_STAR) move_group_cmd, Slrn_Group_Keymap);
2049 SLkm_define_key ("^U", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2050 SLkm_define_key ("\033V", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2051 SLkm_define_key ("a", (FVOID_STAR) add_group_cmd, Slrn_Group_Keymap);
2052 SLkm_define_key ("u", (FVOID_STAR) unsubscribe, Slrn_Group_Keymap);
2053 SLkm_define_key ("s", (FVOID_STAR) subscribe, Slrn_Group_Keymap);
2054 SLkm_define_key ("\033u", (FVOID_STAR) uncatch_up, Slrn_Group_Keymap);
2055 SLkm_define_key ("c", (FVOID_STAR) catch_up, Slrn_Group_Keymap);
2056 SLkm_define_key ("K", (FVOID_STAR) toggle_scoring, Slrn_Group_Keymap);
2057 SLkm_define_key ("L", (FVOID_STAR) toggle_list_all_groups, Slrn_Group_Keymap);
2058 SLkm_define_key ("l", (FVOID_STAR) toggle_hide_groups, Slrn_Group_Keymap);
2059 SLkm_define_key ("^Z", (FVOID_STAR) slrn_suspend_cmd, Slrn_Group_Keymap);
2060 SLkm_define_key (" ", (FVOID_STAR) select_group_cmd, Slrn_Group_Keymap);
2061 SLkm_define_key (".", (FVOID_STAR) slrn_repeat_last_key, Slrn_Group_Keymap);
2062 SLkm_define_key ("P", (FVOID_STAR) slrn_post_cmd, Slrn_Group_Keymap);
2063 SLkm_define_key ("\033P", (FVOID_STAR) slrn_post_postponed, Slrn_Group_Keymap);
2064 SLkm_define_key ("?", (FVOID_STAR) slrn_group_help, Slrn_Group_Keymap);
2065 SLkm_define_key ("\r", (FVOID_STAR) select_group_cmd, Slrn_Group_Keymap);
2066 SLkm_define_key ("q", (FVOID_STAR) slrn_group_quit, Slrn_Group_Keymap);
2067 SLkm_define_key ("^X^C", (FVOID_STAR) slrn_group_quit, Slrn_Group_Keymap);
2068 SLkm_define_key ("^X^T", (FVOID_STAR) transpose_groups, Slrn_Group_Keymap);
2069 SLkm_define_key ("^X^[", (FVOID_STAR) slrn_evaluate_cmd, Slrn_Group_Keymap);
2070 SLkm_define_key ("^R", (FVOID_STAR) slrn_redraw, Slrn_Group_Keymap);
2071 SLkm_define_key ("^L", (FVOID_STAR) slrn_redraw, Slrn_Group_Keymap);
2072 SLkm_define_key ("^P", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2073 #if defined(IBMPC_SYSTEM)
2074 SLkm_define_key ("^@H", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2075 SLkm_define_key ("\xE0H", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2076 SLkm_define_key ("^@P", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2077 SLkm_define_key ("\xE0P", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2078 #else
2079 SLkm_define_key ("\033[A", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2080 SLkm_define_key ("\033OA", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2081 SLkm_define_key ("\033[B", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2082 SLkm_define_key ("\033OB", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2083 #endif
2084 SLkm_define_key ("N", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2085 SLkm_define_key ("^N", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2086 SLkm_define_key ("/", (FVOID_STAR) group_search_forward, Slrn_Group_Keymap);
2087 SLkm_define_key ("\\", (FVOID_STAR) group_search_backward, Slrn_Group_Keymap);
2088 SLkm_define_key ("G", (FVOID_STAR) refresh_groups_cmd, Slrn_Group_Keymap);
2089 SLkm_define_key ("X", (FVOID_STAR) save_newsrc_cmd, Slrn_Group_Keymap);
2090
2091 /* mouse (left/right/middle) */
2092 SLkm_define_key ("\033[M\040", (FVOID_STAR) group_mouse_left, Slrn_Group_Keymap);
2093 SLkm_define_key ("\033[M\041", (FVOID_STAR) group_mouse_middle, Slrn_Group_Keymap);
2094 SLkm_define_key ("\033[M\042", (FVOID_STAR) group_mouse_right, Slrn_Group_Keymap);
2095 SLkm_define_key ("\033[M\043", (FVOID_STAR) group_mouse_left, Slrn_Group_Keymap);
2096 SLkm_define_key ("\033[M\044", (FVOID_STAR) group_mouse_left, Slrn_Group_Keymap);
2097 #if USE_TEST_FUNCTION
2098 SLkm_define_key ("y", (FVOID_STAR) test_function, Slrn_Group_Keymap);
2099 #endif
2100 if (SLang_get_error ()) slrn_exit_error ("%s", err);
2101 }
2102
2103 /*}}}*/
2104
2105 /*}}}*/
2106
slrn_select_group_mode(void)2107 int slrn_select_group_mode (void) /*{{{*/
2108 {
2109 init_group_win_struct ();
2110
2111 Last_Cursor_Row = 0;
2112 group_quick_help ();
2113 Slrn_Full_Screen_Update = 1;
2114
2115 slrn_push_mode (&Group_Mode_Cap);
2116 return 0;
2117 }
2118
2119 /*}}}*/
2120
2121 /*{{{ Read/Write Newsrc, Group Descriptions */
2122
add_group_description(unsigned char * s,unsigned char * smax,unsigned char * dsc)2123 static void add_group_description (unsigned char *s, unsigned char *smax, /*{{{*/
2124 unsigned char *dsc)
2125 {
2126 Slrn_Group_Type *g;
2127 unsigned long hash;
2128
2129 hash = slrn_compute_hash (s, smax);
2130 g = Group_Hash_Table[hash % GROUP_HASH_TABLE_SIZE];
2131 while (g != NULL)
2132 {
2133 if ((g->hash == hash) && (!strncmp (g->group_name, (char *) s, (unsigned int) (smax - s))))
2134 {
2135 /* Sometimes these get repeated --- not by slrn but on the server! */
2136 slrn_free (g->descript);
2137
2138 g->descript = slrn_strmalloc ((char *) dsc, 0);
2139 /* Ok to fail. */
2140 return;
2141 }
2142 g = g->hash_next;
2143 }
2144 }
2145
2146 /*}}}*/
slrn_get_group_descriptions(void)2147 void slrn_get_group_descriptions (void) /*{{{*/
2148 {
2149 FILE *fp;
2150 char line[2 * SLRN_MAX_PATH_LEN];
2151 char file[SLRN_MAX_PATH_LEN];
2152 int num;
2153
2154 #ifdef VMS
2155 slrn_snprintf (file, sizeof (file), "%s-dsc", Slrn_Newsrc_File);
2156 #else
2157 # ifdef SLRN_USE_OS2_FAT
2158 slrn_os2_make_fat (file, sizeof (file), Slrn_Newsrc_File, ".dsc");
2159 # else
2160 slrn_snprintf (file, sizeof (file), "%s.dsc", Slrn_Newsrc_File);
2161 # endif
2162 #endif
2163
2164 if (NULL == (fp = fopen (file, "w")))
2165 {
2166 slrn_exit_error (_("\
2167 Unable to create newsgroup description file:\n%s\n"), file);
2168 }
2169
2170 fprintf (stdout, _("\nCreating description file %s.\n"), file);
2171 fprintf (stdout, _("Getting newsgroup descriptions from server.\n\
2172 Note: This step may take some time if you have a slow connection!!!\n"));
2173
2174 fflush (stdout);
2175
2176 if (OK_GROUPS != Slrn_Server_Obj->sv_list_newsgroups ())
2177 {
2178 slrn_error (_("Server failed on list newsgroups command."));
2179 slrn_fclose (fp);
2180 return;
2181 }
2182
2183 num = 0;
2184
2185 while (1)
2186 {
2187 unsigned char *b, *bmax, *dsc, ch;
2188 int status;
2189
2190 status = Slrn_Server_Obj->sv_read_line (line, sizeof (line));
2191 if (status == -1)
2192 {
2193 slrn_error (_("Error reading line from server"));
2194 slrn_fclose (fp);
2195 return;
2196 }
2197 if (status == 0)
2198 break;
2199
2200 num = num % 25;
2201 if (num == 0)
2202 {
2203 putc ('.', stdout);
2204 fflush (stdout);
2205 }
2206
2207 /* Check the syntax on this line. They are often corrupt */
2208 b = (unsigned char *) slrn_skip_whitespace (line);
2209
2210 bmax = b;
2211 while ((ch = *bmax) > ' ') bmax++;
2212 if ((ch == 0) || (ch == '\n')) continue;
2213 *bmax = 0;
2214
2215 /* News group marked off, now get the description. */
2216 dsc = bmax + 1;
2217 while (((ch = *dsc) <= ' ') && (ch != 0)) dsc++;
2218 if ((ch == 0) || (ch == '?') || (ch == '\n')) continue;
2219
2220 /* add_group_description (b, bmax, dsc); */
2221
2222 fputs ((char *) b, fp);
2223 putc(':', fp);
2224 fputs ((char *) dsc, fp);
2225 putc('\n', fp);
2226
2227 num++;
2228 }
2229 slrn_fclose (fp);
2230 putc ('\n', stdout);
2231 }
2232
2233 /*}}}*/
slrn_read_group_descriptions(void)2234 int slrn_read_group_descriptions (void) /*{{{*/
2235 {
2236 FILE *fp;
2237 char line[2 * SLRN_MAX_PATH_LEN];
2238 char file[SLRN_MAX_PATH_LEN];
2239
2240 #ifdef VMS
2241 slrn_snprintf (file, sizeof (file), "%s-dsc", Slrn_Newsrc_File);
2242 #else
2243 # ifdef SLRN_USE_OS2_FAT
2244 slrn_os2_make_fat (file, sizeof (file), Slrn_Newsrc_File, ".dsc");
2245 # else
2246 slrn_snprintf (file, sizeof (file), "%s.dsc", Slrn_Newsrc_File);
2247 # endif
2248 #endif
2249
2250 if (NULL == (fp = fopen (file, "r")))
2251 {
2252 #ifdef VMS
2253 slrn_snprintf (file, sizeof (file), "%snewsgroups.dsc",
2254 SLRN_LIB_DIR);
2255 if (NULL == (fp = fopen (file, "r")))
2256 {
2257 slrn_snprintf (file, sizeof (file), "%snewsgroups-dsc",
2258 SLRN_LIB_DIR);
2259 fp = fopen (file, "r");
2260 }
2261 #else
2262 slrn_snprintf (file, sizeof (file), "%s/newsgroups.dsc",
2263 SLRN_LIB_DIR);
2264 fp = fopen (file, "r");
2265 #endif
2266 if (fp == NULL) return -1;
2267 }
2268
2269 while (NULL != fgets (line, sizeof (line) - 1, fp))
2270 {
2271 unsigned char *bmax, *dsc, ch;
2272
2273 bmax = (unsigned char *) line;
2274 while (((ch = *bmax) != ':') && ch) bmax++;
2275 if (ch <= ' ') continue;
2276 *bmax = 0;
2277
2278 dsc = bmax + 1;
2279 add_group_description ((unsigned char *) line, bmax, dsc);
2280 }
2281 slrn_fclose (fp);
2282 return 0;
2283 }
2284
2285 /*}}}*/
2286
2287 /* Map 1998 --> 98, 2003 --> 3, etc.... */
rfc977_patchup_year(int year)2288 static int rfc977_patchup_year (int year)
2289 {
2290 return year - 100 * (year / 100);
2291 }
2292
slrn_get_new_groups(int create_flag)2293 int slrn_get_new_groups (int create_flag) /*{{{*/
2294 {
2295 FILE *fp;
2296 time_t tloc;
2297 struct tm *tm_struct;
2298 char line[NNTP_BUFFER_SIZE];
2299 char file[SLRN_MAX_PATH_LEN];
2300 int num;
2301 char *p;
2302 int parse_error = 0;
2303
2304 #ifdef VMS
2305 slrn_snprintf (file, sizeof (file), "%s-time", Slrn_Newsrc_File);
2306 #else
2307 # ifdef SLRN_USE_OS2_FAT
2308 slrn_os2_make_fat (file, sizeof (file), Slrn_Newsrc_File, ".tim");
2309 # else
2310 slrn_snprintf (file, sizeof (file), "%s.time", Slrn_Newsrc_File);
2311 # endif
2312 #endif
2313
2314 if ((create_flag == 0)
2315 && (NULL != (fp = fopen (file, "r"))))
2316 {
2317 char ch;
2318 int i;
2319 *line = 0;
2320 fgets (line, sizeof (line), fp);
2321 slrn_fclose (fp);
2322
2323 slrn_message_now (_("Checking for new groups ..."));
2324
2325 time (&tloc);
2326 parse_error = 1;
2327
2328 /* parse this line to make sure it is ok. If it is bad, issue a warning
2329 * and go on.
2330 */
2331 if (strncmp ("NEWGROUPS ", line, 10)) goto parse_error_label;
2332 p = line + 10;
2333
2334 p = slrn_skip_whitespace (p);
2335
2336 /* parse yymmdd */
2337 for (i = 0; i < 6; i++)
2338 {
2339 ch = p[i];
2340 if ((ch < '0') || (ch > '9')) goto parse_error_label;
2341 }
2342 if (p[6] != ' ') goto parse_error_label;
2343
2344 ch = p[2];
2345 if (ch > '1') goto parse_error_label;
2346 if ((ch == '1') && (p[3] > '2')) goto parse_error_label;
2347 ch = p[4];
2348 if (ch > '3') goto parse_error_label;
2349 if ((ch == '3') && (p[5] > '1')) goto parse_error_label;
2350
2351 /* Now the hour: hhmmss */
2352 p = slrn_skip_whitespace (p + 6);
2353
2354 for (i = 0; i < 6; i++)
2355 {
2356 ch = p[i];
2357 if ((ch < '0') || (ch > '9')) goto parse_error_label;
2358 }
2359 ch = p[0];
2360 if (ch > '2') goto parse_error_label;
2361 if ((ch == '2') && (p[1] > '3')) goto parse_error_label;
2362 if ((p[2] > '5') || (p[4] > '5')) goto parse_error_label;
2363
2364 p = slrn_skip_whitespace (p + 6);
2365
2366 if ((p[0] == 'G') && (p[1] == 'M') && (p[2] == 'T'))
2367 p += 3;
2368 *p = 0;
2369
2370 parse_error = 0;
2371
2372 switch (Slrn_Server_Obj->sv_put_server_cmd (line, line, sizeof (line)))
2373 {
2374 case OK_NEWGROUPS:
2375 break;
2376
2377 case ERR_FAULT:
2378 return -1;
2379
2380 case ERR_COMMAND:
2381 slrn_message (_("Server does not implement NEWGROUPS command."));
2382 return 0;
2383
2384 default:
2385 slrn_message (_("Server failed to return proper response to NEWGROUPS:\n%s\n"),
2386 line);
2387 goto parse_error_label;
2388 }
2389
2390 num = 0;
2391
2392 while (1)
2393 {
2394 int status = Slrn_Server_Obj->sv_read_line (line, sizeof (line));
2395 if (status == 0)
2396 break;
2397 if (status == -1)
2398 {
2399 slrn_error (_("Read from server failed"));
2400 return -1;
2401 }
2402
2403 /* line contains new newsgroup name */
2404 add_unsubscribed_group ((unsigned char*)line);
2405 num++;
2406 }
2407
2408 if (num)
2409 {
2410 slrn_message (_("%d new newsgroup(s) found."), num);
2411 }
2412 }
2413 else time (&tloc);
2414
2415 parse_error_label:
2416 if (parse_error)
2417 {
2418 slrn_message (_("\
2419 %s appears corrupt.\n\
2420 I expected to see: NEWGROUPS yymmdd hhmmss GMT\n\
2421 I will patch the file up for you.\n"), file);
2422 }
2423
2424 #ifdef VMS
2425 if (NULL == (fp = fopen (file, "w", "fop=cif")))
2426 #else
2427 if (NULL == (fp = fopen (file, "w")))
2428 #endif
2429 {
2430 slrn_exit_error (_("Unable to open %s to record date."), file);
2431 }
2432
2433 /* According to rfc977, the year must be in the form YY with the closest
2434 * century specifying the rest of the year, i.e., 99 is 1999, 30 is 2030,
2435 * etc.
2436 */
2437 #if defined(VMS) || defined(__BEOS__)
2438 /* gmtime is broken on BEOS */
2439 tm_struct = localtime (&tloc);
2440 tm_struct->tm_year = rfc977_patchup_year (tm_struct->tm_year + 1900);
2441 fprintf (fp, "NEWGROUPS %02d%02d%02d %02d%02d%02d",
2442 tm_struct->tm_year, 1 + tm_struct->tm_mon,
2443 tm_struct->tm_mday, tm_struct->tm_hour,
2444 tm_struct->tm_min, tm_struct->tm_sec);
2445 #else
2446 tm_struct = gmtime (&tloc);
2447 tm_struct->tm_year = rfc977_patchup_year (tm_struct->tm_year + 1900);
2448 fprintf (fp, "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT",
2449 tm_struct->tm_year, 1 + tm_struct->tm_mon,
2450 tm_struct->tm_mday, tm_struct->tm_hour,
2451 tm_struct->tm_min, tm_struct->tm_sec);
2452 #endif
2453 slrn_fclose (fp);
2454 return 0;
2455 }
2456
2457 /*}}}*/
2458
parse_active_line(unsigned char * name,unsigned int * lenp,int * minp,int * maxp)2459 static int parse_active_line (unsigned char *name, unsigned int *lenp, /*{{{*/
2460 int *minp, int *maxp)
2461 {
2462 unsigned char *p;
2463
2464 p = name;
2465 while (*p > ' ') p++;
2466 *lenp = (unsigned int) (p - name);
2467
2468 while (*p == ' ') p++;
2469 *maxp = atoi ((char*)p);
2470 while (*p > ' ') p++;
2471 while (*p == ' ') p++;
2472 *minp = atoi((char*)p);
2473 if (*maxp < *minp) *minp = *maxp + 1;
2474 return 0;
2475 }
2476
2477 /*}}}*/
2478
2479 static char *Subscriptions [] =
2480 {
2481 "news.answers",
2482 "news.announce.newusers",
2483 "news.newusers.questions",
2484 "news.groups.questions",
2485 "news.software.readers",
2486 "alt.test",
2487 NULL
2488 };
2489
read_and_parse_active(int create_flag)2490 static void read_and_parse_active (int create_flag) /*{{{*/
2491 {
2492 char line[NNTP_BUFFER_SIZE];
2493 int count = 0, ret;
2494 int initial_run = (Groups == NULL);
2495
2496 if (OK_GROUPS != (ret = Slrn_Server_Obj->sv_list_active (NULL)))
2497 {
2498 if (ret == ERR_NOAUTH)
2499 slrn_exit_error (_("Server failed LIST ACTIVE - authorization missing."));
2500 slrn_exit_error (_("Server failed LIST ACTIVE."));
2501 }
2502
2503 while (1)
2504 {
2505 unsigned int len;
2506 int min, max;
2507 int status;
2508
2509 status = Slrn_Server_Obj->sv_read_line (line, sizeof(line));
2510 if (status == -1)
2511 {
2512 /* Slrn_Groups_Dirty = 0;*/ /* Huh? */
2513 slrn_exit_error (_("Read from server failed"));
2514 }
2515 if (status == 0)
2516 break;
2517
2518 parse_active_line ((unsigned char *)line, &len, &min, &max);
2519
2520 if (!initial_run)
2521 {
2522 Slrn_Group_Type *g = find_group_entry (line, len);
2523 if (g != NULL)
2524 {
2525 group_update_range (g, min, max);
2526 continue;
2527 }
2528 }
2529
2530 if (NULL == create_group_entry (line, len, min, max, 0, 0))
2531 continue;
2532
2533 if (create_flag)
2534 {
2535 count++;
2536 count = count % 50;
2537 if (count == 0)
2538 {
2539 putc ('.', stdout);
2540 fflush (stdout);
2541 }
2542 add_group (line, len, GROUP_UNSUBSCRIBED, 0, 1);
2543 }
2544 }
2545
2546 if (create_flag)
2547 {
2548 char *name;
2549 unsigned int i = 0;
2550 Slrn_Group_Type *save = Slrn_Group_Current_Group, *last = NULL;
2551
2552 if (OK_GROUPS == Slrn_Server_Obj->sv_list ("SUBSCRIPTIONS"))
2553 {
2554 name = line;
2555 if (Slrn_Server_Obj->sv_read_line (line, sizeof (line)) <= 0)
2556 name = NULL;
2557 }
2558 else
2559 name = *Subscriptions;
2560
2561 while (name != NULL)
2562 {
2563 if (NULL != (Slrn_Group_Current_Group = find_group_entry (name, strlen (name))))
2564 {
2565 Slrn_Group_Current_Group->flags &= ~GROUP_HIDDEN;
2566 Slrn_Group_Current_Group->flags &= ~GROUP_UNSUBSCRIBED;
2567 last = place_group_in_newsrc_order (last);
2568 }
2569
2570 if (name != line)
2571 name = Subscriptions[++i];
2572 else if (Slrn_Server_Obj->sv_read_line (line, sizeof (line)) <= 0)
2573 name = NULL;
2574 }
2575
2576 Slrn_Group_Current_Group = save;
2577 Slrn_Groups_Dirty = 1;
2578 Slrn_Write_Newsrc_Flags = 0;
2579 }
2580
2581 Kill_After_Max = 0;
2582 }
2583
2584 /*}}}*/
2585
read_and_parse_newsrc_file(void)2586 static int read_and_parse_newsrc_file (void)
2587 {
2588 VFILE *vp;
2589 char *vline;
2590 unsigned int vlen;
2591 char file[SLRN_MAX_PATH_LEN];
2592 char *newsrc_filename = Slrn_Newsrc_File;
2593 Slrn_Group_Type *last_group = NULL;
2594 int ret_stat_o, ret_stat_as;
2595 struct stat st_o, st_as;
2596
2597 #ifdef VMS
2598 slrn_snprintf (file, sizeof (file), "%s-as", Slrn_Newsrc_File);
2599 #else
2600 # ifdef SLRN_USE_OS2_FAT
2601 slrn_os2_make_fat (file, sizeof (file), Slrn_Newsrc_File, ".as");
2602 # else
2603 slrn_snprintf (file, sizeof (file), "%s.as", Slrn_Newsrc_File);
2604 # endif
2605 #endif
2606
2607 ret_stat_o = stat (newsrc_filename, &st_o);
2608 ret_stat_as = stat (file, &st_as);
2609
2610 if ((ret_stat_as != -1) &&
2611 (((ret_stat_o == -1) || (st_as.st_mtime > st_o.st_mtime))))
2612 {
2613 slrn_message (_("\n* The autosave file of %s is newer than the file "
2614 "itself.\n"), newsrc_filename);
2615 if (slrn_get_yesno (1, _("Do you want to restore your newsrc from "
2616 "the autosave version")))
2617 {
2618 newsrc_filename = file;
2619 }
2620 }
2621
2622 if (NULL == (vp = vopen (newsrc_filename, 4096, 0)))
2623 #if 0
2624 && (NULL == (vp = slrn_open_home_vfile (".newsrc", file,
2625 sizeof (file))))
2626 #endif
2627 slrn_exit_error (_("Unable to open %s."), newsrc_filename);
2628
2629 while (NULL != (vline = vgets (vp, &vlen)))
2630 {
2631 char *p = vline;
2632 char *pmax = p + vlen;
2633 char ch = 0;
2634
2635 while ((p < pmax)
2636 && ((ch = *p) != '!') && (ch != ':'))
2637 p++;
2638
2639 if ((p == pmax) || (p == vline))
2640 continue;
2641
2642 if (vline[vlen-1] == '\n')
2643 vline[vlen-1] = 0;
2644 else
2645 vline[vlen] = 0;
2646
2647 if (-1 == add_group (vline, (unsigned int) (p - vline),
2648 ((ch == '!') ? GROUP_UNSUBSCRIBED : 0), 0, 0))
2649 continue;
2650
2651 /* perform a re-arrangement to match arrangement in the
2652 * newsrc file
2653 */
2654 /*if (Slrn_List_Active_File && (ch != '!'))*/
2655 last_group = place_group_in_newsrc_order (last_group);
2656 }
2657 vclose (vp);
2658 if (!Slrn_List_Active_File)
2659 refresh_groups (&Slrn_Group_Current_Group);
2660 return 0;
2661 }
2662
slrn_read_newsrc(int create_flag)2663 int slrn_read_newsrc (int create_flag) /*{{{*/
2664 {
2665 slrn_message_now (_("Checking news%s ..."),
2666 Slrn_List_Active_File ? _(" via active file") : "");
2667
2668 if (create_flag)
2669 {
2670 FILE *fp;
2671
2672 /* See if we can open the file. */
2673 #ifdef VMS
2674 if (NULL == (fp = fopen (Slrn_Newsrc_File, "w", "fop=cif")))
2675 #else
2676 if (NULL == (fp = fopen (Slrn_Newsrc_File, "w")))
2677 #endif
2678 {
2679 slrn_exit_error (_("Unable to create %s."), Slrn_Newsrc_File);
2680 }
2681 fclose (fp);
2682 fputs (_("\n--The next step may take a while if the NNTP connection is slow.--\n\n"), stdout);
2683 fprintf (stdout, _("Creating %s."), Slrn_Newsrc_File);
2684 fflush (stdout);
2685 }
2686
2687 if (create_flag || Slrn_List_Active_File)
2688 {
2689 read_and_parse_active (create_flag);
2690 }
2691
2692 if ((create_flag == 0)
2693 && (-1 == read_and_parse_newsrc_file ()))
2694 slrn_exit_error (_("Unable to read newsrc file"));
2695
2696 insert_new_groups ();
2697
2698 Slrn_Group_Current_Group = Groups;
2699
2700 init_group_win_struct ();
2701
2702 toggle_hide_groups ();
2703
2704 /* Unhide the new groups. Do it here so that if there are no unread
2705 * articles, it will be visible but also enables user to toggle them
2706 * so that they will become invisble again.
2707 */
2708 Slrn_Group_Current_Group = Groups;
2709 while ((Slrn_Group_Current_Group != NULL) &&
2710 (create_flag || (Slrn_Group_Current_Group->flags & GROUP_NEW_GROUP_FLAG)))
2711 {
2712 Slrn_Group_Current_Group->flags &= ~GROUP_HIDDEN;
2713 Slrn_Group_Current_Group = Slrn_Group_Current_Group->next;
2714 }
2715
2716 Slrn_Group_Current_Group = Groups;
2717 while ((Slrn_Group_Current_Group != NULL)
2718 && (Slrn_Group_Current_Group->flags & GROUP_HIDDEN))
2719 Slrn_Group_Current_Group = Slrn_Group_Current_Group->next;
2720
2721 find_line_num ();
2722
2723 group_bob ();
2724 return 0;
2725 }
2726
2727 /*}}}*/
2728
slrn_write_newsrc(int auto_save)2729 int slrn_write_newsrc (int auto_save) /*{{{*/
2730 /* auto_save == 0: save to newsrc, == 1: save to autosave file. */
2731 {
2732 Slrn_Group_Type *g;
2733 Slrn_Range_Type *r;
2734 char autosave_file[SLRN_MAX_PATH_LEN];
2735 char *newsrc_filename;
2736 static FILE *fp;
2737 int max;
2738 struct stat filestat;
2739 int stat_worked = 0;
2740 int have_backup = 0;
2741
2742 slrn_init_hangup_signals (0);
2743
2744 if (Slrn_Groups_Dirty == 0)
2745 {
2746 slrn_init_hangup_signals (1);
2747 return 0;
2748 }
2749
2750 /* In case of hangup and we were writing the file, make sure it is closed.
2751 * This will not hurt since we are going to do it again anyway.
2752 */
2753 if (fp != NULL)
2754 {
2755 slrn_fclose (fp);
2756 fp = NULL;
2757 }
2758
2759 #ifdef VMS
2760 slrn_snprintf (autosave_file, sizeof (autosave_file), "%s-as",
2761 Slrn_Newsrc_File);
2762 #else
2763 # ifdef SLRN_USE_OS2_FAT
2764 slrn_os2_make_fat (autosave_file, sizeof (autosave_file), Slrn_Newsrc_File,
2765 ".as");
2766 # else
2767 slrn_snprintf (autosave_file, sizeof (autosave_file), "%s.as",
2768 Slrn_Newsrc_File);
2769 # endif
2770 #endif
2771
2772 if ((auto_save == 1) && (Slrn_No_Autosave != 2))
2773 {
2774 if (Slrn_No_Autosave)
2775 {
2776 slrn_init_hangup_signals (1);
2777 return 0;
2778 }
2779
2780 newsrc_filename = autosave_file;
2781 stat_worked = (-1 != stat (newsrc_filename, &filestat));
2782 }
2783 else
2784 newsrc_filename = Slrn_Newsrc_File;
2785
2786 slrn_message_now (_("Writing %s ..."), newsrc_filename);
2787 /* Try to preserve .newsrc permissions and owner/group. This also
2788 * confirms existence of file. If an autosave file does not (yet)
2789 * exist, use permissions of the .newsrc.
2790 */
2791 if (stat_worked == 0)
2792 stat_worked = (-1 != stat (Slrn_Newsrc_File, &filestat));
2793
2794 if (newsrc_filename == Slrn_Newsrc_File)
2795 {
2796 /* Create a temp backup file. Delete it later if user
2797 * does not want backups.
2798 */
2799 have_backup = (0 == slrn_create_backup (newsrc_filename));
2800 }
2801
2802 if (NULL == (fp = fopen (newsrc_filename, "w")))
2803 {
2804 slrn_error (_("Unable to open file %s for writing."), newsrc_filename);
2805 if (have_backup) slrn_restore_backup (newsrc_filename);
2806 slrn_init_hangup_signals (1);
2807 return -1;
2808 }
2809
2810 #ifdef __unix__
2811 # if !defined(IBMPC_SYSTEM)
2812 /* Try to preserve .newsrc permissions and owner/group */
2813 # ifndef S_IRUSR
2814 # define S_IRUSR 0400
2815 # define S_IWUSR 0200
2816 # define S_IXUSR 0100
2817 # endif
2818 if (stat_worked)
2819 {
2820 if (-1 == chmod (newsrc_filename, filestat.st_mode & (S_IRUSR | S_IWUSR | S_IXUSR)))
2821 (void) chmod (newsrc_filename, S_IWUSR | S_IRUSR);
2822
2823 (void) chown (newsrc_filename, filestat.st_uid, filestat.st_gid);
2824 }
2825 # endif
2826 #endif
2827
2828 g = Groups;
2829 while (g != NULL)
2830 {
2831 if ((g->flags & GROUP_UNSUBSCRIBED) &&
2832 ((Slrn_Write_Newsrc_Flags == 1) ||
2833 ((Slrn_Write_Newsrc_Flags == 2) && (g->range.next == NULL))))
2834 {
2835 g = g->next;
2836 continue;
2837 }
2838 if ((EOF == fputs (g->group_name, fp)) ||
2839 (EOF == putc (((g->flags & GROUP_UNSUBSCRIBED) ? '!' : ':'), fp)))
2840 goto write_error;
2841
2842 r = g->range.next;
2843 max = g->range.max;
2844 if (r != NULL)
2845 {
2846 NNTP_Artnum_Type max_newsrc_number=0;
2847 /* Make this check because the unsubscribed group
2848 * range may not have been initialized from the server.
2849 */
2850 if ((max != -1) && (g->range.min != -1)
2851 && ((g->flags & GROUP_UNSUBSCRIBED) == 0))
2852 max_newsrc_number = max;
2853
2854 if (EOF == putc (' ', fp))
2855 goto write_error;
2856
2857 if (-1 == slrn_ranges_to_newsrc_file (r, max_newsrc_number, fp))
2858 goto write_error;
2859 }
2860 else if (g->range.min == 2)
2861 {
2862 if (EOF == fputs (" 1", fp))
2863 goto write_error;
2864 }
2865 else if (g->range.min > 2)
2866 {
2867 if (fprintf (fp, (" 1-" NNTP_FMT_ARTNUM), g->range.min - 1) < 0)
2868 goto write_error;
2869 }
2870
2871 if (EOF == putc ('\n', fp))
2872 goto write_error;
2873 g = g->next;
2874 }
2875
2876 if (-1 == slrn_fclose (fp))
2877 goto write_error;
2878 fp = NULL;
2879
2880 if (Slrn_No_Backups)
2881 {
2882 if (have_backup) slrn_delete_backup (newsrc_filename);
2883 }
2884
2885 if (newsrc_filename == Slrn_Newsrc_File)
2886 {
2887 Slrn_Groups_Dirty = 0;
2888 if (Slrn_No_Autosave == 0)
2889 slrn_delete_file (autosave_file);
2890 }
2891 if (Slrn_TT_Initialized & SLRN_TTY_INIT)
2892 slrn_message (_("Writing %s ... done."), newsrc_filename);
2893
2894 slrn_init_hangup_signals (1);
2895 return 0;
2896
2897 write_error:
2898
2899 slrn_fclose (fp); fp = NULL;
2900 slrn_error (_("Write to %s failed! Disk Full?"), newsrc_filename);
2901
2902 /* Put back orginal file */
2903 if (have_backup) slrn_restore_backup (newsrc_filename);
2904
2905 slrn_init_hangup_signals (1);
2906 return -1;
2907 }
2908
2909 /*}}}*/
2910
2911 /*}}}*/
2912
group_quick_help(void)2913 static void group_quick_help (void) /*{{{*/
2914 {
2915 char *hlp = _("SPC:Select p:Post c:CatchUp l:List q:Quit ^R:Redraw (u)s:(Un)Subscribe");
2916
2917 if (Slrn_Batch)
2918 return;
2919
2920 if (Slrn_Group_Help_Line != NULL)
2921 hlp = Slrn_Group_Help_Line;
2922
2923 if (0 == slrn_message ("%s", hlp))
2924 Slrn_Message_Present = 0;
2925 }
2926
2927 /*}}}*/
2928
group_display_format_cb(char ch,void * data,int * len,int * color)2929 static char *group_display_format_cb (char ch, void *data, int *len, int *color) /*{{{*/
2930 {
2931 Slrn_Group_Type *g = (Slrn_Group_Type*) data;
2932 static char buf[512];
2933 char *retval = buf;
2934
2935 *retval = 0;
2936 if (g == NULL) return retval;
2937
2938 switch (ch)
2939 {
2940 case 'F':
2941 if (g->flags & GROUP_UNSUBSCRIBED) retval = "U";
2942 else if (g->flags & GROUP_NEW_GROUP_FLAG) retval = "N";
2943 else retval = " ";
2944 *len = 1;
2945 break;
2946 case 'd':
2947 if (NULL == (retval = g->descript))
2948 retval = " ";
2949 if (color != NULL) *color = GROUP_DESCR_COLOR;
2950 break;
2951 case 'h':
2952 if (g->range.max == -1)
2953 {
2954 retval = "?";
2955 *len = 1;
2956 }
2957 else
2958 #ifdef HAVE_ANSI_SPRINTF
2959 *len =
2960 #endif
2961 sprintf (buf, NNTP_FMT_ARTNUM, g->range.max); /* safe */
2962 break;
2963 case 'l':
2964 if (g->range.min == -1)
2965 {
2966 retval = "?";
2967 *len = 1;
2968 }
2969 else
2970 #ifdef HAVE_ANSI_SPRINTF
2971 *len =
2972 #endif
2973 sprintf (buf, NNTP_FMT_ARTNUM, g->range.min); /* safe */
2974 break;
2975 case 'n':
2976 retval = g->group_name;
2977 if (color != NULL) *color = GROUP_COLOR;
2978 break;
2979 case 't':
2980 if (g->range.max == -1)
2981 {
2982 retval = "?";
2983 *len = 1;
2984 }
2985 else
2986 #ifdef HAVE_ANSI_SPRINTF
2987 *len =
2988 #endif
2989 sprintf (buf, NNTP_FMT_ARTNUM, g->range.max - g->range.min + 1); /* safe */
2990 break;
2991 case 'u':
2992 #ifdef HAVE_ANSI_SPRINTF
2993 *len =
2994 #endif
2995 sprintf (buf, NNTP_FMT_ARTNUM, g->unread); /* safe */
2996 break;
2997 }
2998
2999 return retval;
3000 }
3001 /*}}}*/
3002
group_status_line_cb(char ch,void * data,int * len,int * color)3003 static char *group_status_line_cb (char ch, void *data, int *len, int *color) /*{{{*/
3004 {
3005 static char buf[66];
3006 char *retval = NULL;
3007
3008 (void) data; (void) color; /* we currently don't use these */
3009
3010 switch (ch)
3011 {
3012 case 'L':
3013 retval = slrn_print_percent (buf, &Group_Window, 1);
3014 break;
3015 case 'P':
3016 retval = slrn_print_percent (buf, &Group_Window, 0);
3017 break;
3018 case 'D':
3019 retval = Slrn_Groups_Dirty ? "*" : "-";
3020 *len = 1;
3021 break;
3022 case 's':
3023 retval = Slrn_Server_Obj->sv_name;
3024 break;
3025 default:
3026 if (Slrn_Group_Current_Group != NULL)
3027 retval = group_display_format_cb (ch,
3028 (void *) Slrn_Group_Current_Group,
3029 len, NULL);
3030 break;
3031 }
3032
3033 return retval;
3034 }
3035 /*}}}*/
3036
group_update_screen(void)3037 static void group_update_screen (void) /*{{{*/
3038 {
3039 Slrn_Group_Type *g;
3040 char *fmt = Group_Display_Formats[Group_Format_Number];
3041 int height = (int) Group_Window.nrows;
3042 int row;
3043
3044 /* erase last cursor */
3045 if (Last_Cursor_Row && !Slrn_Full_Screen_Update)
3046 {
3047 SLsmg_gotorc (Last_Cursor_Row, 0);
3048 SLsmg_write_string (" ");
3049 }
3050
3051 g = (Slrn_Group_Type *) Group_Window.top_window_line;
3052 (void) SLscroll_find_top (&Group_Window);
3053
3054 if (g != (Slrn_Group_Type *) Group_Window.top_window_line)
3055 {
3056 Slrn_Full_Screen_Update = 1;
3057 g = (Slrn_Group_Type *) Group_Window.top_window_line;
3058 }
3059
3060 if ((fmt == NULL) || (*fmt == 0))
3061 fmt = " %F%-5u %n%45g%d";
3062
3063 for (row = 0; row < height; row++)
3064 {
3065 while ((g != NULL) && (g->flags & GROUP_HIDDEN))
3066 g = g->next;
3067
3068 if (g != NULL)
3069 {
3070 if (Slrn_Full_Screen_Update || (g->flags & GROUP_TOUCHED))
3071 {
3072 slrn_custom_printf (fmt, group_display_format_cb,
3073 (void *) g, row + 1, 0);
3074 g->flags &= ~GROUP_TOUCHED;
3075 }
3076 g = g->next;
3077 }
3078 else if (Slrn_Full_Screen_Update)
3079 {
3080 SLsmg_gotorc (row + 1, 0);
3081 SLsmg_erase_eol ();
3082 }
3083 }
3084
3085 fmt = Slrn_Group_Status_Line;
3086 if ((fmt == NULL) || (*fmt == 0))
3087 fmt = _("-%D-News Groups: %s %-20g -- %L (%P)");
3088 slrn_custom_printf (fmt, group_status_line_cb, NULL, SLtt_Screen_Rows - 2,
3089 STATUS_COLOR);
3090
3091 if (Slrn_Use_Mouse) slrn_update_group_menu ();
3092 else slrn_update_top_status_line ();
3093
3094 if (Slrn_Message_Present == 0) group_quick_help ();
3095
3096 Last_Cursor_Row = 1 + Group_Window.window_row;
3097 SLsmg_gotorc (Last_Cursor_Row, 0);
3098
3099 slrn_set_color (CURSOR_COLOR);
3100
3101 #if SLANG_VERSION > 10003
3102 if (Slrn_Display_Cursor_Bar)
3103 SLsmg_set_color_in_region (CURSOR_COLOR, Last_Cursor_Row, 0, 1, SLtt_Screen_Cols);
3104 else
3105 #endif
3106 SLsmg_write_string ("->");
3107
3108 slrn_set_color (0);
3109 Slrn_Full_Screen_Update = 0;
3110 }
3111
3112 /*}}}*/
3113
3114 /* intrinsic functions */
slrn_intr_get_group_order(void)3115 void slrn_intr_get_group_order (void) /*{{{*/
3116 {
3117 Slrn_Group_Type *g;
3118 SLang_Array_Type *retval;
3119 int n = 0;
3120
3121 g = Groups;
3122 while (g != NULL)
3123 {
3124 n++;
3125 g = g->next;
3126 }
3127
3128 retval = SLang_create_array (SLANG_STRING_TYPE, 0, NULL, &n, 1);
3129 if (retval == NULL)
3130 return;
3131
3132 n = 0;
3133 g = Groups;
3134 while (g != NULL)
3135 {
3136 if (-1 == SLang_set_array_element (retval, &n, &g->group_name))
3137 {
3138 SLang_free_array (retval);
3139 return;
3140 }
3141 n++;
3142 g = g->next;
3143 }
3144
3145 (void) SLang_push_array (retval, 1);
3146 }
3147 /*}}}*/
3148
slrn_intr_set_group_order(void)3149 void slrn_intr_set_group_order (void) /*{{{*/
3150 {
3151 SLang_Array_Type *at;
3152 Slrn_Group_Type *last, *rest, *g;
3153 int i, rows;
3154
3155 if (-1 == SLang_pop_array_of_type (&at, SLANG_STRING_TYPE))
3156 {
3157 slrn_error (_("Array of string expected."));
3158 return;
3159 }
3160
3161 if (at->num_dims != 1)
3162 {
3163 slrn_error (_("One-dimensional array expected."));
3164 SLang_free_array (at);
3165 return;
3166 }
3167
3168 if ((Groups == NULL) || (Groups->next == NULL))
3169 {
3170 SLang_free_array (at);
3171 return;
3172 }
3173
3174 rows = at->dims[0];
3175 last = NULL;
3176 rest = Groups;
3177
3178 for (i = 0; i < rows; i++)
3179 {
3180 char *name;
3181
3182 if ((-1 == SLang_get_array_element (at, &i, &name))
3183 || (name == NULL))
3184 continue;
3185
3186 if (NULL == (g = find_group_entry (name, strlen (name))))
3187 continue;
3188
3189 /* It is possible for name to occur multiple times in the array.
3190 * So we have to be careful. There are two lists here: the part
3191 * that runs from Groups to last, and the rest. The first group
3192 * is the already sorted part, and the second is unsorted.
3193 * Ordinarily g will come from the second (rest), but if name appears
3194 * more than once in the array, it will come from the first part when
3195 * seen the second time.
3196 */
3197 if (g == last) /* at tail of first part */
3198 continue;
3199
3200 if (g == Groups) /* at head of first part */
3201 Groups = g->next;
3202
3203 if (g == rest) /* at head of second part */
3204 rest = rest->next;
3205
3206 /* Remove it from its current location */
3207 if (g->next != NULL)
3208 g->next->prev = g->prev;
3209 if (g->prev != NULL)
3210 g->prev->next = g->next;
3211
3212 g->prev = last; /* append it to first part */
3213
3214 if (last == NULL) /* the head of the first part */
3215 Groups = g;
3216 else
3217 last->next = g;
3218
3219 g->next = rest; /* connect up the rest */
3220 if (rest != NULL)
3221 rest->prev = g;
3222
3223 last = g;
3224 }
3225
3226 SLang_free_array (at);
3227
3228 find_line_num ();
3229 Slrn_Full_Screen_Update = 1;
3230 Slrn_Groups_Dirty = 1;
3231 }
3232 /*}}}*/
3233