tin  2.4.4
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.4.4.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

tags.c
Go to the documentation of this file.
1 /*
2  * Project : tin - a Usenet reader
3  * Module : tags.c
4  * Author : Jason Faultless <jason@altarstone.com>
5  * Created : 1999-12-06
6  * Updated : 2010-04-02
7  * Notes : Split out from other modules
8  *
9  * Copyright (c) 1999-2020 Jason Faultless <jason@altarstone.com>
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  *
16  * 1. Redistributions of source code must retain the above copyright notice,
17  * this list of conditions and the following disclaimer.
18  *
19  * 2. Redistributions in binary form must reproduce the above copyright
20  * notice, this list of conditions and the following disclaimer in the
21  * documentation and/or other materials provided with the distribution.
22  *
23  * 3. Neither the name of the copyright holder nor the names of its
24  * contributors may be used to endorse or promote products derived from
25  * this software without specific prior written permission.
26  *
27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 #ifndef TIN_H
41 # include "tin.h"
42 #endif /* !TIN_H */
43 
44 /* Local prototypes */
45 static int get_multipart_info(int base_index, MultiPartInfo *setme);
46 static int get_multiparts(int base_index, MultiPartInfo **malloc_and_setme_info);
47 static int look_for_multipart_info(int base_index, MultiPartInfo *setme, char start, char stop, int *offset);
48 static t_bool parse_range(char *range, int min, int max, int curr, int *range_start, int *range_end);
49 
51 
52 /*
53  * Parses a subject header of the type "multipart message subject (01/42)"
54  * into a MultiPartInfo struct, or fails if the message subject isn't in the
55  * right form.
56  *
57  * @return nonzero on success
58  */
59 static int
61  int base_index,
62  MultiPartInfo *setme)
63 {
64  int i, j, offi, offj;
65  MultiPartInfo setmei, setmej;
66 
67  i = look_for_multipart_info(base_index, &setmei, '[', ']', &offi);
68  j = look_for_multipart_info(base_index, &setmej, '(', ')', &offj);
69 
70  /* Ok i hits first */
71  if (offi > offj) {
72  *setme = setmei;
73  return i;
74  }
75 
76  /* Its j or they are both the same (which must be zero!) so we don't care */
77  *setme = setmej;
78  return j;
79 }
80 
81 
82 static int
84  int base_index,
85  MultiPartInfo* setme,
86  char start,
87  char stop,
88  int *offset)
89 {
90  MultiPartInfo tmp;
91  char *subj;
92  char *pch;
93 
94  *offset = 0;
95 
96  /* entry assertions */
97  assert(0 <= base_index && base_index < grpmenu.max && "invalid base_index");
98  assert(setme != NULL && "setme must not be NULL");
99 
100  /* parse the message */
101  subj = arts[base[base_index]].subject;
102  if (!(pch = strrchr(subj, start)))
103  return 0;
104  if (!isdigit((int) pch[1]))
105  return 0;
106  tmp.base_index = base_index;
107  tmp.subject_compare_len = pch - subj;
108  tmp.part_number = (int) strtol(pch + 1, &pch, 10);
109  if (*pch != '/' && *pch != '|')
110  return 0;
111  if (!isdigit((int) pch[1]))
112  return 0;
113  tmp.total = (int) strtol(pch + 1, &pch, 10);
114  if (*pch != stop)
115  return 0;
116  tmp.subject = subj;
117  *setme = tmp;
118  *offset = pch - subj;
119  return 1;
120 }
121 
122 
123 /*
124  * Tries to find all the parts to the multipart message pointed to by
125  * base_index.
126  *
127  * Weakness(?): only walks through the base messages.
128  *
129  * @return on success, the number of parts found. On failure, zero if not a
130  * multipart or the negative value of the first missing part.
131  * @param base_index index pointing to one of the messages in a multipart
132  * message.
133  * @param malloc_and_setme_info on success, set to a malloced array the
134  * parts found. Untouched on failure.
135  */
136 static int
138  int base_index,
139  MultiPartInfo **malloc_and_setme_info)
140 {
141  MultiPartInfo tmp, tmp2;
142  MultiPartInfo *info = NULL;
143  int i;
144  int part_index;
145 
146  /* entry assertions */
147  assert(0 <= base_index && base_index < grpmenu.max && "Invalid base index");
148  assert(malloc_and_setme_info != NULL && "malloc_and_setme_info must not be NULL");
149 
150  /* make sure this is a multipart message... */
151  if (!get_multipart_info(base_index, &tmp) || tmp.total < 1)
152  return 0;
153 
154  /* make a temporary buffer to hold the multipart info... */
155  info = my_malloc(sizeof(MultiPartInfo) * tmp.total);
156 
157  /* zero out part-number for the repost check below */
158  for (i = 0; i < tmp.total; ++i)
159  info[i].part_number = -1;
160 
161  /* try to find all the multiparts... */
162  for (i = 0; i < grpmenu.max; ++i) {
163  if (strncmp(arts[base[i]].subject, tmp.subject, tmp.subject_compare_len))
164  continue;
165  if (!get_multipart_info(i, &tmp2))
166  continue;
167 
168  part_index = tmp2.part_number - 1;
169 
170  /* skip the "blah (00/102)" info messages... */
171  if (part_index < 0)
172  continue;
173 
174  /* skip insane "blah (103/102) subjects... */
175  if (part_index >= tmp.total)
176  continue;
177 
178  /* repost check: do we already have this part? */
179  if (info[part_index].part_number != -1) {
180  assert(info[part_index].part_number == tmp2.part_number && "bookkeeping error");
181  continue;
182  }
183 
184  /* we have a match, hooray! */
185  info[part_index] = tmp2;
186  }
187 
188  /* see if we got them all. */
189  for (i = 0; i < tmp.total; ++i) {
190  if (info[i].part_number != i + 1) {
191  free(info);
192  return -(i + 1); /* missing part #(i+1) */
193  }
194  }
195 
196  /* looks like a success .. */
197  *malloc_and_setme_info = info;
198  return tmp.total;
199 }
200 
201 
202 /*
203  * Tags all parts of a multipart index if base_index points
204  * to a multipart message and all its parts can be found.
205  *
206  * @param base_index points to one message in a multipart message.
207  * @return number of messages tagged, or zero on failure
208  */
209 int
211  int base_index)
212 {
213  MultiPartInfo *info = NULL;
214  int i;
215  const int qty = get_multiparts(base_index, &info);
216 
217  /* check for failure... */
218  if (qty == 0) {
220  return 0;
221  }
222  if (qty < 0) {
224  return 0;
225  }
226 
227  /*
228  * if any are already tagged, untag 'em first
229  * so num_of_tagged_arts doesn't get corrupted
230  */
231  for (i = 0; i < qty; ++i) {
232  if (arts[base[info[i].base_index]].tagged != 0)
233  untag_article(base[info[i].base_index]);
234  }
235 
236  /*
237  * get_multiparts() sorts info by part number,
238  * so a simple for loop tags in the right order
239  */
240  for (i = 0; i < qty; ++i)
241  arts[base[info[i].base_index]].tagged = ++num_of_tagged_arts;
242 
243  free(info);
244 
245  return qty;
246 }
247 
248 
249 /*
250  * Return the highest tag number of any article in thread
251  * rooted at base[n]
252  */
253 int
255  int n)
256 {
257  int code = 0;
258 
260  int i;
261  for (i = n; i >= 0; i = arts[i].thread) {
262  if (arts[i].tagged > code)
263  code = arts[i].tagged;
264  }
265  } else
266  code = arts[n].tagged;
267 
268  return code;
269 }
270 
271 
272 /*
273  * Toggle tag status of an article. Returns TRUE if we tagged the article
274  * FALSE if we untagged it.
275  */
276 t_bool
278  int art)
279 {
280  if (arts[art].tagged != 0) {
283  return FALSE;
284  } else {
287  return TRUE;
288  }
289 }
290 
291 
292 /*
293  * Remove the tag from an article
294  * Work through all the threads and decrement the tag counter on all arts
295  * greater than 'tag', fixup counters
296  */
297 void
299  long art)
300 {
301  int i, j;
302 
303  for (i = 0; i < grpmenu.max; ++i) {
304  for_each_art_in_thread(j, i) {
305  if (arts[j].tagged > arts[art].tagged)
306  --arts[j].tagged;
307  }
308  }
309  arts[art].tagged = 0;
311 }
312 
313 
314 /*
315  * Clear tag status of all articles. If articles were untagged, return TRUE
316  */
317 t_bool
319  void)
320 {
321  int i;
322  t_bool untagged = FALSE;
323 
324  for_each_art(i) {
325  if (arts[i].tagged != 0) {
326  arts[i].tagged = 0;
327  untagged = TRUE;
328  }
329  }
330  num_of_tagged_arts = 0;
331 
332  return untagged;
333 }
334 
335 
336 /*
337  * RANGE CODE
338  */
339 /*
340  * Allows user to specify an group/article range that a followup
341  * command will operate on (eg. catchup articles 1-56) # 1-56 K
342  * min/max/curr are the lowest/highest and current positions on the
343  * menu from which this was called; used as defaults if needed
344  * Return TRUE if a range was successfully read, parsed and set
345  *
346  * Allowed syntax is 0123456789-.$ (blanks are ignored):
347  * 1-23 mark grp/art 1 through 23
348  * 1-. mark grp/art 1 through current
349  * 1-$ mark grp/art 1 through last
350  * .-$ mark grp/art current through last
351  */
352 t_bool
354  int level,
355  int min,
356  int max,
357  int curr)
358 {
359  char *range;
360  char *prompt;
361  int artnum;
362  int i;
363  int depth;
364  int range_min;
365  int range_max;
366 
367  switch (level) {
368  case SELECT_LEVEL:
369  range = tinrc.default_range_select;
370  break;
371 
372  case GROUP_LEVEL:
373  range = tinrc.default_range_group;
374  break;
375 
376  case THREAD_LEVEL:
377  range = tinrc.default_range_thread;
378  break;
379 
380  default: /* should no happen */
381  return FALSE;
382  }
383 
384 #if 0
385  error_message(2, "Min=[%d] Max=[%d] Cur=[%d] DefRng=[%s]", min, max, curr, range);
386 #endif /* 0 */
387  prompt = fmt_string(_(txt_enter_range), range);
388 
389  if (!(prompt_string_default(prompt, range, _(txt_range_invalid), HIST_OTHER))) {
390  free(prompt);
391  return FALSE;
392  }
393  free(prompt);
394 
395  /*
396  * Parse range string
397  */
398  if (!parse_range(range, min, max, curr, &range_min, &range_max)) {
400  return FALSE;
401  }
402 
403  switch (level) {
404  case SELECT_LEVEL:
405  for (i = 0; i < max; i++) /* Clear existing range */
406  active[my_group[i]].inrange = FALSE;
407 
408  for (i = range_min - 1; i < range_max; i++)
409  active[my_group[i]].inrange = TRUE;
410  break;
411 
412  case GROUP_LEVEL:
413  for (i = 0; i < max; i++) { /* Clear existing range */
414  for_each_art_in_thread(artnum, i)
415  arts[artnum].inrange = FALSE;
416  }
417 
418  for (i = range_min - 1; i < range_max; i++) {
419  for_each_art_in_thread(artnum, i)
420  arts[artnum].inrange = TRUE;
421  }
422  break;
423 
424  case THREAD_LEVEL:
425  /*
426  * Debatably should clear all of arts[] depending on how you
427  * interpret the (non)spec
428  */
429  for (i = 0; i < grpmenu.max; i++) { /* Clear existing range */
430  for_each_art_in_thread(artnum, i)
431  arts[artnum].inrange = FALSE;
432  }
433 
434  depth = 1;
436  if (depth > range_max)
437  break;
438  if (depth >= range_min)
439  arts[artnum].inrange = TRUE;
440  depth++;
441  }
442  break;
443 
444  default:
445  return FALSE;
446  /* NOTREACHED */
447  break;
448  }
449  return TRUE;
450 }
451 
452 
453 /*
454  * Parse 'range', return the range start and end values in range_start and range_end
455  * min/max/curr are used to select defaults when n explicit start/end are given
456  */
457 static t_bool
459  char *range,
460  int min,
461  int max,
462  int curr,
463  int *range_start,
464  int *range_end)
465 {
466  char *ptr = range;
467  enum states { FINDMIN, FINDMAX, DONE };
468  int state = FINDMIN;
469  t_bool ret = FALSE;
470 
471  *range_start = -1;
472  *range_end = -1;
473 
474  while (*ptr && state != DONE) {
475  if (isdigit((int) *ptr)) {
476  if (state == FINDMAX) {
477  *range_end = atoi(ptr);
478  state = DONE;
479  } else
480  *range_start = atoi(ptr);
481  while (isdigit((int) *ptr))
482  ptr++;
483  } else {
484  switch (*ptr) {
485  case '-':
486  state = FINDMAX;
487  break;
488 
489  case '.':
490  if (state == FINDMAX) {
491  *range_end = curr;
492  state = DONE;
493  } else
494  *range_start = curr;
495  break;
496 
497  case '$':
498  if (state == FINDMAX) {
499  *range_end = max;
500  state = DONE;
501  }
502  break;
503 
504  default:
505  break;
506  }
507  ptr++;
508  }
509  }
510 
511  if (*range_start >= min && *range_end >= *range_start && *range_end <= max)
512  ret = TRUE;
513 
514  return ret;
515 }
516 
517 
518 /*
519  * SELECTED CODE
520  */
521 void
523  void)
524 {
525  int i;
526 
527  for_each_art(i) {
528  if (arts[i].status == ART_UNREAD && !arts[i].selected) {
529 # ifdef DEBUG
530  if (debug & DEBUG_NEWSRC)
531  debug_print_comment("group.c: X command");
532 # endif /* DEBUG */
534  arts[i].zombie = TRUE;
535  }
537  arts[i].keep_in_base = FALSE;
538  }
541 
542  grpmenu.curr = 0;
543  show_group_page();
544 }
545 
546 
547 /* selection already happened in filter_articles() */
548 void
550  void)
551 {
552  int i;
553 
554  for_each_art(i) {
555  if (arts[i].status == ART_READ && arts[i].zombie) {
556 # ifdef DEBUG
557  if (debug & DEBUG_NEWSRC)
558  debug_print_comment("group.c: + command");
559 # endif /* DEBUG */
561  arts[i].zombie = FALSE;
562  }
563  }
566 
567  grpmenu.curr = 0; /* do we want this? */
568  show_group_page();
569 }
570 
571 
572 void
574  void)
575 {
576  int i;
577 
578  for_each_art(i) {
579  arts[i].selected = FALSE;
580  arts[i].zombie = FALSE;
581  }
582 }
583 
584 
585 /*
586  * Return TRUE if there are any selected arts
587  */
588 t_bool
590  void)
591 {
592  int i;
593 
594  for_each_art(i) {
595  if (arts[i].selected)
596  return TRUE;
597  }
598 
599  return FALSE;
600 }
look_for_multipart_info
static int look_for_multipart_info(int base_index, MultiPartInfo *setme, char start, char stop, int *offset)
Definition: tags.c:83
GROUP_LEVEL
#define GROUP_LEVEL
Definition: tin.h:1102
_
#define _(Text)
Definition: tin.h:94
txt_prefix_tagged
constext txt_prefix_tagged[]
Definition: lang.c:732
THREAD_LEVEL
#define THREAD_LEVEL
Definition: tin.h:1103
txt_info_not_multipart_message
constext txt_info_not_multipart_message[]
Definition: lang.c:546
do_auto_select_arts
void do_auto_select_arts(void)
Definition: tags.c:522
MultiPartInfo::subject_compare_len
int subject_compare_len
Definition: tin.h:1995
get_multipart_info
static int get_multipart_info(int base_index, MultiPartInfo *setme)
Definition: tags.c:60
art_mark
void art_mark(struct t_group *group, struct t_article *art, int flag)
Definition: newsrc.c:1571
t_article::selected
t_bool selected
Definition: tin.h:1533
base
t_artnum * base
Definition: memory.c:65
undo_selections
void undo_selections(void)
Definition: tags.c:573
num_of_tagged_arts
int num_of_tagged_arts
Definition: tags.c:50
grpmenu
t_menu grpmenu
Definition: group.c:83
for_each_art_in_thread
#define for_each_art_in_thread(x, y)
Definition: tin.h:2212
art
static t_openartinfo * art
Definition: cook.c:78
tinrc
struct t_config tinrc
Definition: init.c:191
HIST_OTHER
@ HIST_OTHER
Definition: extern.h:1538
curr_group
struct t_group * curr_group
Definition: group.c:55
t_config::default_range_thread
char default_range_thread[LEN]
Definition: tinrc.h:82
code
int code
Definition: signal.c:116
MultiPartInfo::base_index
int base_index
Definition: tin.h:1998
thread_basenote
int thread_basenote
Definition: thread.c:51
fmt_string
char * fmt_string(const char *fmt,...)
Definition: string.c:1379
txt_info_missing_part
constext txt_info_missing_part[]
Definition: lang.c:544
state
state
Definition: save.c:56
t_article::zombie
t_bool zombie
Definition: tin.h:1531
t_menu::max
int max
Definition: tin.h:2007
info_message
void info_message(const char *fmt,...)
Definition: screen.c:102
tin.h
DEBUG_NEWSRC
#define DEBUG_NEWSRC
Definition: debug.h:50
MultiPartInfo::part_number
int part_number
Definition: tin.h:1996
txt_article_singular
constext txt_article_singular[]
Definition: lang.c:75
untag_all_articles
t_bool untag_all_articles(void)
Definition: tags.c:318
get_multiparts
static int get_multiparts(int base_index, MultiPartInfo **malloc_and_setme_info)
Definition: tags.c:137
prompt_string_default
char * prompt_string_default(const char *prompt, char *def, const char *failtext, int history)
Definition: prompt.c:558
active
struct t_group * active
Definition: memory.c:66
arts_selected
t_bool arts_selected(void)
Definition: tags.c:589
find_base
void find_base(struct t_group *group)
Definition: art.c:124
ART_READ
#define ART_READ
Definition: tin.h:1320
MultiPartInfo::total
int total
Definition: tin.h:1997
parse_range
static t_bool parse_range(char *range, int min, int max, int curr, int *range_start, int *range_end)
Definition: tags.c:458
untag_article
void untag_article(long art)
Definition: tags.c:298
t_attribute::show_only_unread_arts
unsigned show_only_unread_arts
Definition: tin.h:1627
assert
#define assert(p)
Definition: tin.h:1295
t_article::keep_in_base
t_bool keep_in_base
Definition: tin.h:1536
offset
static int offset
Definition: read.c:62
tag_article
t_bool tag_article(int art)
Definition: tags.c:277
MultiPartInfo
Definition: tin.h:1993
t_config::default_range_group
char default_range_group[LEN]
Definition: tinrc.h:80
t_article::inrange
t_bool inrange
Definition: tin.h:1534
t_article::tagged
int tagged
Definition: tin.h:1525
t_article::thread
int thread
Definition: tin.h:1526
t_article::subject
char * subject
Definition: tin.h:1512
MultiPartInfo::subject
char * subject
Definition: tin.h:1994
undo_auto_select_arts
void undo_auto_select_arts(void)
Definition: tags.c:549
t_config::default_range_select
char default_range_select[LEN]
Definition: tinrc.h:81
t_menu::curr
int curr
Definition: tin.h:2006
atoi
int atoi(const char *s)
txt_range_invalid
constext txt_range_invalid[]
Definition: lang.c:754
FALSE
#define FALSE
Definition: bool.h:70
debug
unsigned short debug
Definition: debug.c:51
show_group_page
void show_group_page(void)
Definition: group.c:915
tag_multipart
int tag_multipart(int base_index)
Definition: tags.c:210
ART_UNREAD
#define ART_UNREAD
Definition: tin.h:1321
my_group
int * my_group
Definition: memory.c:64
error_message
void error_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:184
strtol
long strtol(const char *str, char **ptr, int use_base)
Definition: string.c:410
t_group::attribute
struct t_attribute * attribute
Definition: tin.h:1790
t_attribute::thread_articles
unsigned thread_articles
Definition: tin.h:1631
txt_enter_range
constext txt_enter_range[]
Definition: lang.c:179
t_bool
unsigned t_bool
Definition: bool.h:77
TRUE
#define TRUE
Definition: bool.h:74
set_range
t_bool set_range(int level, int min, int max, int curr)
Definition: tags.c:353
for_each_art
#define for_each_art(x)
Definition: tin.h:2211
arts
struct t_article * arts
Definition: memory.c:69
SELECT_LEVEL
#define SELECT_LEVEL
Definition: tin.h:1101
line_is_tagged
int line_is_tagged(int n)
Definition: tags.c:254
my_malloc
#define my_malloc(size)
Definition: tin.h:2196
txt_prefix_untagged
constext txt_prefix_untagged[]
Definition: lang.c:733