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