1/*
2 * rarian-info.c
3 * This file is part of Rarian
4 *
5 * Copyright (C) 2007 - Don Scorgie
6 *
7 * Rarian is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * Rarian is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22#include "config.h"
23#include <stdio.h>
24#include <string.h>
25#ifdef HAVE_MALLOC_H
26#include <malloc.h>
27#endif /*HAVE_MALLOC_H*/
28#include <ctype.h>
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <stdlib.h>
32
33#include "rarian-info.h"
34#include "rarian-utils.h"
35
36typedef struct _InfoLink InfoLink;
37
38
39struct _InfoLink
40{
41  RrnInfoEntry *reg;
42  InfoLink *next;
43  InfoLink *prev;
44};
45
46static InfoLink * info_head = NULL;
47static InfoLink * info_tail = NULL;
48static char **categories = NULL;
49static char *current_category = NULL;
50static char *current_path = NULL;
51RrnInfoEntry *current_entry = NULL;
52
53static void
54set_category (const char *new_cat)
55{
56  char *stripped = strdup (new_cat);
57  char **cats = categories;
58  int ncats = 1;
59  char **tmp_copy = NULL;
60  char **tmp_copy_iter = NULL;
61  if (current_category)
62    free (current_category);
63
64  stripped = rrn_strip (stripped);
65  while (cats && *cats) {
66    if (!strcmp (stripped, *cats)) {
67      current_category = strdup (*cats);
68      free (stripped);
69      return;
70    }
71    ncats++;
72    cats++;
73  }
74  /* If we get here, we got a new category and have to do some work */
75  cats = categories;
76  tmp_copy = malloc (sizeof(char *) * (ncats+1));
77  memset (tmp_copy, 0, sizeof(char *) * (ncats+1));
78  tmp_copy_iter = tmp_copy;
79
80  cats = categories;
81  while (cats && *cats) {
82    char *tmp = *cats;
83    *tmp_copy_iter = strdup (tmp);
84    cats++;
85    tmp_copy_iter++;
86    free (tmp);
87  }
88  *tmp_copy_iter = strdup (stripped);
89  current_category = strdup (stripped);
90  free (categories);
91  categories = tmp_copy;
92  free (stripped);
93  return;
94}
95
96static void
97process_initial_entry (const char *line)
98{
99  char *tmp = (char *) line;
100  char *end_name = NULL;
101  char *begin_fname = NULL;
102  char *end_fname = NULL;
103  char *begin_section = NULL;
104  char *end_section = NULL;
105  char *comment = NULL;
106
107  if (!tmp) {
108    fprintf (stderr, "Error: Malformed line!  Ignoring\n");
109    return;
110  }
111
112  if (!current_category) {
113    /* The docs are appearing before the first category.  Ignore
114       them. */
115    fprintf (stderr, "Error: Documents outwith categories.  Ignoring\n");
116    return;
117  }
118
119  tmp++;
120  end_name = strchr(tmp, ':');
121  if (!end_name) {
122    fprintf (stderr, "Error: Malformed line (no ':').  Ignoring entry\n");
123    return;
124  }
125  begin_fname = strchr(end_name, '(');
126  if (!begin_fname) {
127    fprintf (stderr, "Error: Malformed line (no filename).  Ignoring entry\n");
128    return;
129  }
130  end_fname = strchr(begin_fname, ')');
131  if (!end_fname) {
132    fprintf (stderr, "Error: Malformed line (no filename close).  Ignoring entry\n");
133    return;
134  }
135  end_section = strchr(end_fname, '.');
136
137  if (!end_section) {
138    fprintf (stderr, "Error: Malformed line (no section).  Ignoring entry\n");
139    return;
140  }
141
142  current_entry->category = strdup(current_category);
143  current_entry->base_path = strdup(current_path);
144  current_entry->base_filename = NULL;
145  current_entry->doc_name = rrn_strip(rrn_strndup(tmp, (end_name - tmp)));
146  current_entry->name = rrn_strip(rrn_strndup(begin_fname+1,
147					    (end_fname-begin_fname-1)));
148  if (end_section == end_fname+1)
149    current_entry->section = NULL;
150  else {
151    current_entry->section = rrn_strip(rrn_strndup(end_fname+1,
152					   (end_section - end_fname -1)));
153  }
154  comment = rrn_strip(strdup (end_section+1));
155  if (strlen (comment) > 0) {
156    current_entry->comment = comment;
157  } else {
158    free(comment);
159    current_entry->comment = NULL;
160  }
161}
162
163static void
164process_add_desc (const char *line)
165{
166  char *current;
167  char *cpy = NULL;
168  cpy = rrn_strip(strdup(line));
169  if (!cpy)
170    return;
171
172  if (current_entry->comment) {
173    current = malloc (sizeof(char) * (strlen(current_entry->comment) +
174				      strlen(cpy) + 2));
175    sprintf (current, "%s %s", current_entry->comment, cpy);
176    free (current_entry->comment);
177  } else {
178    current = strdup(cpy);
179  }
180  current_entry->comment = current;
181  free (cpy);
182}
183
184static int
185process_check_file()
186{
187  char *filename = NULL;
188  char *tmp = NULL;
189  InfoLink *iter;
190  struct stat fileinfo;
191
192  if (!current_entry->name) {
193    return FALSE;
194  }
195
196  /* First, look for an additional part on the filename */
197  tmp = strchr(current_entry->name, '/');
198  if (tmp) {
199    char *new_base = NULL;
200    char *addition;
201    char *name;
202    addition = rrn_strndup (current_entry->name, tmp - current_entry->name);
203    name = strdup(tmp+1);
204    new_base = malloc (sizeof(char) * (strlen(current_entry->base_path) +
205				       strlen(addition)+ 2));
206    sprintf (new_base, "%s/%s", current_entry->base_path, addition);
207    free(current_entry->base_path);
208    free(current_entry->name);
209    free(addition);
210    current_entry->base_path = new_base;
211    current_entry->name = name;
212  }
213
214  /* Search for duplicate files.  If we find one, we
215   * use the older one and forget this one
216   */
217  iter = info_head;
218
219  while (iter) {
220    if (!strcmp (iter->reg->doc_name, current_entry->doc_name)) {
221      return FALSE;
222    }
223    iter = iter->next;
224
225  }
226
227
228  /* Search for all the types we know of in all the
229   * locations we know of to find the file, starting with
230   * the most popular and working down.
231   * If and when we find it, we set the encoding
232   * (loose) and return */
233  /* Use the largest possible storage for filename */
234  filename = malloc(sizeof(char) * (strlen(current_entry->base_path) +
235				    (strlen(current_entry->name)*2) + 15));
236
237
238  sprintf (filename, "%s/%s.info.gz", current_entry->base_path,
239	   current_entry->name);
240  if (!stat(filename, &fileinfo)) {
241    current_entry->compression = INFO_ENCODING_GZIP;
242    current_entry->base_filename = filename;
243    return TRUE;
244  }
245  sprintf (filename, "%s/%s.gz", current_entry->base_path,
246	   current_entry->name);
247  if (!stat(filename, &fileinfo)) {
248    current_entry->compression = INFO_ENCODING_GZIP;
249    current_entry->base_filename = filename;
250    return TRUE;
251  }
252  sprintf (filename, "%s/%s.info.bz2", current_entry->base_path,
253	   current_entry->name);
254  if (!stat(filename, &fileinfo)) {
255    current_entry->compression = INFO_ENCODING_BZIP;
256    current_entry->base_filename = filename;
257    return TRUE;
258  }
259  sprintf (filename, "%s/%s.bz2", current_entry->base_path,
260	   current_entry->name);
261  if (!stat(filename, &fileinfo)) {
262    current_entry->compression = INFO_ENCODING_BZIP;
263    current_entry->base_filename = filename;
264    return TRUE;
265  }
266  sprintf (filename, "%s/%s.info.lzma", current_entry->base_path,
267		  current_entry->name);
268  if (!stat(filename, &fileinfo)) {
269    current_entry->compression = INFO_ENCODING_LZMA;
270    current_entry->base_filename = filename;
271    return TRUE;
272  }
273  sprintf (filename, "%s/%s.lzma", current_entry->base_path,
274		  current_entry->name);
275  if (!stat(filename, &fileinfo)) {
276    current_entry->compression = INFO_ENCODING_LZMA;
277    current_entry->base_filename = filename;
278    return TRUE;
279  }
280  sprintf (filename, "%s/%s.info", current_entry->base_path,
281	   current_entry->name);
282  if (!stat(filename, &fileinfo)) {
283    current_entry->compression = INFO_ENCODING_NONE;
284    current_entry->base_filename = filename;
285    return TRUE;
286  }
287  sprintf (filename, "%s/%s/%s.info.gz", current_entry->base_path,
288	   current_entry->name, current_entry->name);
289  if (!stat(filename, &fileinfo)) {
290    /* Add to base path */
291    char *new_base = malloc (sizeof(char) * (strlen(current_entry->base_path) +
292					     (strlen(current_entry->name) *2) +
293					     2));
294    sprintf (new_base, "%s/%s", current_entry->base_path,
295	     current_entry->name);
296    free(current_entry->base_path);
297    current_entry->base_path = new_base;
298
299    current_entry->compression = INFO_ENCODING_GZIP;
300    current_entry->base_filename = filename;
301    return TRUE;
302  }
303  sprintf (filename, "%s/%s/%s.gz", current_entry->base_path,
304	   current_entry->name, current_entry->name);
305  if (!stat(filename, &fileinfo)) {
306    /* Add to base path */
307    char *new_base = malloc (sizeof(char) * (strlen(current_entry->base_path) +
308					     (strlen(current_entry->name) *2) +
309					     2));
310    sprintf (new_base, "%s/%s", current_entry->base_path,
311	     current_entry->name);
312    free(current_entry->base_path);
313    current_entry->base_path = new_base;
314
315    current_entry->compression = INFO_ENCODING_GZIP;
316    current_entry->base_filename = filename;
317    return TRUE;
318  }
319  sprintf (filename, "%s/%s/%s.info.bz2", current_entry->base_path,
320	   current_entry->name, current_entry->name);
321  if (!stat(filename, &fileinfo)) {
322    /* Add to base path */
323    char *new_base = malloc (sizeof(char) * (strlen(current_entry->base_path) +
324					     (strlen(current_entry->name) *2) +
325					     2));
326    sprintf (new_base, "%s/%s", current_entry->base_path,
327	     current_entry->name);
328    free(current_entry->base_path);
329    current_entry->base_path = new_base;
330
331    current_entry->compression = INFO_ENCODING_BZIP;
332    current_entry->base_filename = filename;
333    return TRUE;
334  }
335  sprintf (filename, "%s/%s/%s.bz2", current_entry->base_path,
336	   current_entry->name, current_entry->name);
337  if (!stat(filename, &fileinfo)) {
338    /* Add to base path */
339    char *new_base = malloc (sizeof(char) * (strlen(current_entry->base_path) +
340					     (strlen(current_entry->name) *2) +
341					     2));
342    sprintf (new_base, "%s/%s", current_entry->base_path,
343	     current_entry->name);
344    free(current_entry->base_path);
345    current_entry->base_path = new_base;
346
347    current_entry->compression = INFO_ENCODING_BZIP;
348    current_entry->base_filename = filename;
349    return TRUE;
350  }
351    sprintf (filename, "%s/%s/%s.info.lzma", current_entry->base_path,
352		    current_entry->name, current_entry->name);
353    if (!stat(filename, &fileinfo)) {
354    /* Add to base path */
355    char *new_base = malloc (sizeof(char) * (strlen(current_entry->base_path) +
356					    (strlen(current_entry->name) *2) +
357					    2));
358    sprintf (new_base, "%s/%s", current_entry->base_path,
359		    current_entry->name);
360    free(current_entry->base_path);
361    current_entry->base_path = new_base;
362
363    current_entry->compression = INFO_ENCODING_LZMA;
364    current_entry->base_filename = filename;
365    return TRUE;
366    }
367
368    sprintf (filename, "%s/%s/%s.lzma", current_entry->base_path,
369		    current_entry->name, current_entry->name);
370    if (!stat(filename, &fileinfo)) {
371	    /* Add to base path */
372    char *new_base = malloc (sizeof(char) * (strlen(current_entry->base_path) +
373					    (strlen(current_entry->name) *2) +
374					    2));
375    sprintf (new_base, "%s/%s", current_entry->base_path,
376    current_entry->name);
377    free(current_entry->base_path);
378    current_entry->base_path = new_base;
379
380    current_entry->compression = INFO_ENCODING_LZMA;
381    current_entry->base_filename = filename;
382    return TRUE;
383    }
384
385  sprintf (filename, "%s/%s/%s.info", current_entry->base_path,
386	   current_entry->name, current_entry->name);
387  if (!stat(filename, &fileinfo)) {
388    /* Add to base path */
389    char *new_base = malloc (sizeof(char) * (strlen(current_entry->base_path) +
390					     (strlen(current_entry->name) *2) +
391					     2));
392    sprintf (new_base, "%s/%s", current_entry->base_path,
393	     current_entry->name);
394    free(current_entry->base_path);
395    current_entry->base_path = new_base;
396
397    current_entry->compression = INFO_ENCODING_NONE;
398    current_entry->base_filename = filename;
399    return TRUE;
400  }
401  free(filename);
402  return FALSE;
403
404
405}
406
407static void
408free_entry (RrnInfoEntry *entry)
409{
410  if (entry->name)
411    free(entry->name);
412  if (entry->base_path)
413    free(entry->base_path);
414  if (entry->base_filename)
415    free (entry->base_filename);
416  if (entry->category)
417    free (entry->category);
418  if (entry->section)
419    free(entry->section);
420  if (entry->doc_name)
421    free(entry->doc_name);
422  if (entry->comment)
423    free(entry->comment);
424  free(entry);
425
426}
427
428static void
429process_add_entry (void)
430{
431  InfoLink *link = NULL;
432
433  link = malloc (sizeof(InfoLink));
434  link->reg = current_entry;
435  link->next = NULL;
436  link->prev = NULL;
437  if (info_tail && info_head) {
438    info_tail->next = link;
439    link->prev = info_tail;
440    info_tail = link;
441  } else {
442    /* Initial link */
443    info_head = info_tail = link;
444  }
445}
446
447static void
448process_info_dir (const char *dir)
449{
450
451  char *filename;
452  FILE *fp = NULL;
453  char *line = NULL;
454  int started = FALSE;
455  int ret = FALSE;
456
457  filename = (char *) malloc(sizeof(char) * strlen(dir)+5);
458
459  sprintf(filename, "%s/dir", dir);
460  fp = fopen(filename, "r");
461  if (!fp) {
462    free (filename);
463    return;
464  }
465  if (current_path)
466    free (current_path);
467  current_path = strdup (dir);
468  line = (char *) malloc(sizeof(char) * 1024);
469  while (fgets (line, 1023, fp)) {
470    if (!started) {
471      if (!strncmp (line, "* Menu", 6) ||
472	  !strncmp (line, "* menu", 6)) {
473	started = TRUE;
474      }
475      continue;
476    }
477    if ((*line != '*') && !isspace(*line)) {
478      /* New category */
479      set_category (line);
480    } else if ((*line == '*')) {
481      /* New entry */
482      if (current_entry) {
483	if (process_check_file()) {
484	  process_add_entry ();
485	} else {
486	  free_entry (current_entry);
487	}
488	current_entry = NULL;
489      }
490      current_entry = malloc (sizeof(RrnInfoEntry));
491      current_entry->name = NULL;
492      current_entry->base_path = NULL;
493      current_entry->base_filename = NULL;
494      current_entry->category = NULL;
495      current_entry->section = NULL;
496      current_entry->doc_name = NULL;
497      current_entry->comment = NULL;
498
499
500      process_initial_entry (line);
501
502
503    } else if (strlen(line) > 1) {
504      /* Continuation of description */
505      process_add_desc (line);
506    } else {
507      /* Blank line, ignore */
508    }
509  }
510  if (process_check_file()) {
511    process_add_entry ();
512  } else {
513    free_entry (current_entry);
514  }
515  current_entry = NULL;
516  free (line);
517  fclose(fp);
518  free (filename);
519}
520
521static void
522sanity_check_categories ()
523{
524  char **cats = categories;
525  char **iter = cats;
526  char **new_cats = NULL;
527  InfoLink *l;
528  int ncats = 1;
529
530  while (iter && *iter) {
531    l = info_head;
532    while (l) {
533      if (!strcmp(l->reg->category, *iter)) {
534	char **tmp = NULL;
535	char **cats_iter = new_cats;
536	char **tmp_iter = NULL;
537	ncats++;
538	tmp = (char **) malloc(sizeof(char *) * (ncats+1));
539	memset(tmp, 0, sizeof(char *) * (ncats+1));
540	tmp_iter = tmp;
541	while (cats_iter && *cats_iter) {
542	  *tmp_iter = *cats_iter;
543	  tmp_iter++;
544	  cats_iter++;
545	}
546	*tmp_iter = *iter;
547	tmp_iter = tmp;
548	if (new_cats)
549	  free(new_cats);
550	new_cats = tmp;
551
552	break;
553      }
554      l = l->next;
555    }
556    /* If we got here, we have an empty category and should remove
557     * it
558     */
559    iter++;
560  }
561  free(categories);
562  categories = new_cats;
563}
564
565static void
566rrn_info_init (void)
567{
568  char *default_dirs = "@DEFAULT_INFOPATH@";
569  char *info_dirs = NULL;
570  char *split = NULL;
571  int free_info_dirs = FALSE;
572
573  info_dirs = (char *) getenv ("INFOPATH");
574
575
576  if (!info_dirs || !strcmp (info_dirs, "")) {
577    free_info_dirs = TRUE;
578    info_dirs = strdup (default_dirs);
579  }
580
581  split = info_dirs;
582  do {
583    char *next = strchr(split, ':');
584    char *dirname = NULL;
585
586    if (next)
587      dirname = rrn_strndup(split, (next-split));
588    else
589      dirname = strdup (split);
590    process_info_dir (dirname);
591    free (dirname);
592
593    split = strchr(split, ':');
594    if (split)
595      split++;
596  } while (split);
597
598  if (free_info_dirs)
599    free (info_dirs);
600
601  /* Sanity check our categories.  Yes, I put this
602  * comment in then came up with the function name */
603  sanity_check_categories ();
604
605}
606
607
608char **
609rrn_info_get_categories (void)
610{
611  if (!categories)
612    rrn_info_init();
613  return categories;
614
615}
616
617void rrn_info_for_each (RrnInfoForeachFunc funct, void * user_data)
618{
619  InfoLink *l;
620  if (!categories)
621    rrn_info_init();
622  l = info_head;
623  while (l) {
624    int res;
625    res = funct (l->reg, user_data);
626    if (res == FALSE)
627      break;
628    l = l->next;
629  }
630  return;
631}
632
633
634void
635rrn_info_for_each_in_category (char *category,
636				 RrnInfoForeachFunc funct,
637				 void * user_data)
638{
639  InfoLink *l;
640  if (!categories)
641    rrn_info_init();
642  l = info_head;
643  while (l) {
644    int res;
645    if (!strcmp (l->reg->category, category)) {
646      res = funct (l->reg, user_data);
647      if (res == FALSE)
648	break;
649    }
650    l = l->next;
651  }
652  return;
653
654}
655
656RrnInfoEntry *
657rrn_info_find_from_uri (char *uri, char *section)
658{
659  InfoLink *l;
660  InfoLink *best_result = NULL;
661  if (!categories)
662    rrn_info_init();
663
664  l = info_head;
665
666  while (l) {
667    if ((l->reg->doc_name && !strcmp (uri, l->reg->doc_name)) ||
668	(!strcmp (uri, l->reg->name))) {
669      if (!section || (*section && l->reg->section && !strcmp (l->reg->section, section))) {
670	return l->reg;
671      } else {
672	best_result = l;
673      }
674    }
675    l = l->next;
676  }
677
678  if (best_result)
679     return best_result->reg;
680
681  return NULL;
682}
683
684void
685rrn_info_shutdown ()
686{
687  InfoLink *l = info_head;
688
689  while (l) {
690    InfoLink *next = l->next;
691    free_entry (l->reg);
692    free (l);
693    l = next;
694  }
695  info_head = info_tail = NULL;
696
697  free(categories);
698  categories = NULL;
699}
700