/*
sane-desc.c -- generate list of supported SANE devices
Copyright (C) 2002, 2003 Henning Meier-Geinitz
This file is part of the SANE package.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.
*/
#define SANE_DESC_VERSION "2.0"
#define MAN_PAGE_LINK "http://www.mostang.com/sane/man/%s.5.html"
#define COLOR_MINIMAL "\"#B00000\""
#define COLOR_BASIC "\"#FF9000\""
#define COLOR_GOOD "\"#90B000\""
#define COLOR_COMPLETE "\"#007000\""
#define COLOR_UNTESTED "\"#0000B0\""
#define COLOR_UNSUPPORTED "\"#F00000\""
#define COLOR_NEW "\"#F00000\""
#include <../include/sane/config.h>
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/sanei_config.h"
#ifndef PATH_MAX
# define PATH_MAX 1024
#endif
#define DBG_ERR current_debug_level = 0; debug_call
#define DBG_WARN current_debug_level = 1; debug_call
#define DBG_INFO current_debug_level = 2; debug_call
#define DBG_DBG current_debug_level = 3; debug_call
typedef enum output_mode
{
output_mode_ascii = 0,
output_mode_html_backends,
output_mode_html_backends_split,
output_mode_html_mfgs
}
output_mode;
typedef enum parameter_type
{
param_none = 0,
param_string
}
parameter_type;
typedef enum status_entry
{
status_unknown,
status_untested,
status_unsupported,
status_minimal,
status_basic,
status_good,
status_complete
}
status_entry;
typedef enum device_type
{
type_unknown,
type_scanner,
type_stillcam,
type_vidcam,
type_meta,
type_api
}
device_type;
typedef enum level
{
level_backend,
level_mfg,
level_model,
level_desc
}
level;
typedef struct url_entry
{
struct url_entry *next;
char *name;
}
url_entry;
typedef struct model_entry
{
struct model_entry *next;
char *name;
char *interface;
struct url_entry *url;
char *comment;
enum status_entry status;
}
model_entry;
typedef struct desc_entry
{
struct desc_entry *next;
char *desc;
struct url_entry *url;
char *comment;
}
desc_entry;
typedef struct mfg_entry
{
struct mfg_entry *next;
char *name;
struct url_entry *url;
char *comment;
struct model_entry *model;
}
mfg_entry;
typedef struct type_entry
{
struct type_entry *next;
enum device_type type;
struct desc_entry *desc;
struct mfg_entry *mfg;
}
type_entry;
typedef struct backend_entry
{
struct backend_entry *next;
char *name;
char *version;
enum status_entry status; /* deprecated */
char *manpage;
struct url_entry *url;
char *comment;
struct type_entry *type;
SANE_Bool new;
}
backend_entry;
typedef struct model_record_entry
{
struct model_record_entry *next;
char *name;
char *interface;
struct url_entry *url;
char *comment;
enum status_entry status;
struct backend_entry *be;
}
model_record_entry;
typedef struct mfg_record_entry
{
struct mfg_record_entry *next;
char *name;
char *comment;
struct url_entry *url;
struct model_record_entry *model_record;
}
mfg_record_entry;
static char *program_name;
static int debug = 0;
static int current_debug_level = 0;
static char *search_dir = 0;
static backend_entry *first_backend = 0;
static enum output_mode mode = output_mode_ascii;
static char *title = 0;
static char *intro = 0;
static void
debug_call (const char *fmt, ...)
{
va_list ap;
char *level_txt;
va_start (ap, fmt);
if (debug >= current_debug_level)
{
/* print to stderr */
switch (current_debug_level)
{
case 0:
level_txt = "ERROR:";
break;
case 1:
level_txt = "Warning:";
break;
case 2:
level_txt = "Info:";
break;
default:
level_txt = "";
break;
}
fprintf (stderr, "[%s] %8s ", program_name, level_txt);
vfprintf (stderr, fmt, ap);
}
va_end (ap);
}
static void
print_usage (char *program_name)
{
printf ("Usage: %s [-s dir] [-m mode] [-d level] [-h] [-V]\n",
program_name);
printf (" -s|--search-dir dir Specify the directory that contains "
".desc files\n");
printf
(" -m|--mode mode Output mode (ascii, html-backends-split,\n"
" html-mfgs)\n");
printf (" -t|--title \"title\" The title used for HTML pages\n");
printf (" -i|--intro \"intro\" A short description of the "
"contents of the page\n");
printf (" -d|--debug-level level Specify debug level (0-3)\n");
printf (" -h|--help Print help message\n");
printf (" -V|--version Print version information\n");
printf ("Report bugs to \n");
}
static void
print_version (void)
{
printf ("sane-desc %s (%s)\n", SANE_DESC_VERSION, PACKAGE_STRING);
printf ("Copyright (C) 2002 Henning Meier-Geinitz "
"\n"
"sane-desc comes with NO WARRANTY, to the extent permitted by "
"law.\n"
"You may redistribute copies of sane-desc under the terms of the "
"GNU General\n"
"Public License.\n"
"For more information about these matters, see the files named "
"COPYING.\n");
}
static SANE_Bool
get_options (int argc, char **argv)
{
int longindex;
int opt;
static struct option desc_options[] = {
{"search-dir", required_argument, NULL, 's'},
{"mode", required_argument, NULL, 'm'},
{"title", required_argument, NULL, 't'},
{"intro", required_argument, NULL, 'i'},
{"debug-level", required_argument, NULL, 'd'},
{"help", 0, NULL, 'h'},
{"version", 0, NULL, 'V'},
{0, 0, 0, 0}
};
while ((opt = getopt_long (argc, argv, "s:m:t:i:d:hV", desc_options,
&longindex)) != -1)
{
switch (opt)
{
case 'h':
print_usage (argv[0]);
exit (0);
case 'V':
print_version ();
exit (0);
case 's':
search_dir = strdup (optarg);
DBG_INFO ("setting search directory to `%s'\n", search_dir);
break;
case 'm':
if (strcmp (optarg, "ascii") == 0)
{
DBG_INFO ("Output mode: ascii\n");
mode = output_mode_ascii;
}
else if (strcmp (optarg, "html-backends-split") == 0)
{
DBG_INFO ("Output mode: html-backends-split\n");
mode = output_mode_html_backends_split;
}
else if (strcmp (optarg, "html-mfgs") == 0)
{
DBG_INFO ("Output mode: html-mfgs\n");
mode = output_mode_html_mfgs;
}
else
{
DBG_ERR ("Unknown output mode: %s\n", optarg);
exit (1);
}
break;
case 't':
title = optarg;
DBG_INFO ("setting title to `%s'\n", optarg);
break;
case 'i':
intro = optarg;
DBG_INFO ("setting intro to `%s'\n", optarg);
break;
case 'd':
debug = atoi (optarg);
DBG_INFO ("setting debug level to %d\n", debug);
break;
case '?':
DBG_ERR ("unknown option (use -h for help)\n");
return SANE_FALSE;
case ':':
DBG_ERR ("missing parameter (use -h for help)\n");
return SANE_FALSE;
default:
DBG_ERR ("missing option (use -h for help)\n");
return SANE_FALSE;
}
}
if (!search_dir)
search_dir = ".";
return SANE_TRUE;
}
static int
char_compare (char char1, char char2)
{
char1 = toupper (char1);
char2 = toupper (char2);
if (char1 < char2)
return -1;
else if (char1 > char2)
return 1;
else
return 0;
}
static int
num_compare (char *num_string1, char *num_string2)
{
int num1 = atoi (num_string1);
int num2 = atoi (num_string2);
if (num1 < num2)
return -1;
else if (num1 > num2)
return 1;
else
return 0;
}
/* Compare two strings, try to sort numbers correctly (600 < 1200) */
static int
string_compare (char *string1, char *string2)
{
int count = 0;
int compare = 0;
while (string1[count] && string2[count])
{
if (isdigit (string1[count]) && isdigit (string2[count]))
compare = num_compare (&string1[count], &string2[count]);
else
compare = char_compare (string1[count], string2[count]);
if (compare != 0)
return compare;
count++;
}
return char_compare (string1[count], string2[count]);
}
/* Add URLs to the end of the list if they are unique */
static url_entry *
update_url_list (url_entry * first_url, char *new_url)
{
url_entry *url = first_url;
SANE_Bool found = SANE_FALSE;
while (url && url->name)
{
if (string_compare (url->name, new_url) == 0)
found = SANE_TRUE;
url = url->next;
}
if (found)
return first_url;
url = first_url;
if (url)
{
while (url->next)
url = url->next;
url->next = calloc (1, sizeof (url_entry));
url = url->next;
}
else
{
first_url = calloc (1, sizeof (url_entry));
url = first_url;
}
if (!url)
{
DBG_ERR ("update_url_list: couldn't calloc url_entry\n");
exit (1);
}
url->name = new_url;
return first_url;
}
/* Get the next token, ignoring escaped quotation marks */
static const char *
get_token (const char *str, char **string_const)
{
const char *start;
size_t len;
str = sanei_config_skip_whitespace (str);
if (*str == '"')
{
start = ++str;
while (*str && (*str != '"' || *(str - 1) == '\\'))
++str;
len = str - start;
if (*str == '"')
++str;
else
start = 0; /* final double quote is missing */
}
else
{
start = str;
while (*str && !isspace (*str))
++str;
len = str - start;
}
if (start)
*string_const = strndup (start, len);
else
string_const = 0;
return str;
}
/* Checks a line for a keyword token and determines keyword/string argument */
static SANE_Status
read_keyword (SANE_String line, SANE_String keyword_token,
parameter_type p_type, void *argument)
{
SANE_String_Const cp;
SANE_Char *word;
word = 0;
cp = get_token (line, &word);
if (strcmp (word, keyword_token) != 0)
return SANE_STATUS_INVAL;
free (word);
word = 0;
switch (p_type)
{
case param_none:
return SANE_STATUS_GOOD;
case param_string:
{
char *pos;
cp = get_token (cp, &word);
/* remove escaped quotations */
while ((pos = strstr (word, "\\\"")) != 0)
*pos = ' ';
DBG_DBG ("read_keyword: set entry `%s' to `%s'\n", keyword_token,
word);
*(SANE_String *) argument = strdup (word);
break;
}
default:
DBG_ERR ("read_keyword: unknown param_type %d\n", p_type);
return SANE_STATUS_INVAL;
}
if (word)
free (word);
word = 0;
return SANE_STATUS_GOOD;
}
/* Read and interprete the .desc files */
static SANE_Bool
read_files (void)
{
struct stat stat_buf;
DIR *dir;
struct dirent *dir_entry;
FILE *fp;
char file_name[PATH_MAX];
SANE_Char line[PATH_MAX], *word;
SANE_String_Const cp;
backend_entry *current_backend = 0;
type_entry *current_type = 0;
mfg_entry *current_mfg = 0;
model_entry *current_model = 0;
enum level current_level = level_backend;
DBG_INFO ("looking for .desc files in `%s'\n", search_dir);
if (stat (search_dir, &stat_buf) < 0)
{
DBG_ERR ("cannot stat `%s' (%s)\n", search_dir, strerror (errno));
return SANE_FALSE;
}
if (!S_ISDIR (stat_buf.st_mode))
{
DBG_ERR ("`%s' is not a directory\n", search_dir);
return SANE_FALSE;
}
if ((dir = opendir (search_dir)) == 0)
{
DBG_ERR ("cannot read directory `%s' (%s)\n", search_dir,
strerror (errno));
return SANE_FALSE;
}
while ((dir_entry = readdir (dir)) != NULL)
{
if (strlen (dir_entry->d_name) > 5 &&
strcmp (dir_entry->d_name + strlen (dir_entry->d_name) - 5,
".desc") == 0)
{
if (strlen (search_dir)
+ strlen (dir_entry->d_name) + 1 + 1 > PATH_MAX)
{
DBG_ERR ("filename too long\n");
return SANE_FALSE;
}
sprintf (file_name, "%s/%s", search_dir, dir_entry->d_name);
DBG_INFO ("-> reading desc file: %s\n", file_name);
fp = fopen (file_name, "r");
if (!fp)
{
DBG_ERR ("can't open desc file: %s (%s)\n", file_name,
strerror (errno));
return SANE_FALSE;
}
current_backend = 0;
current_type = 0;
current_mfg = 0;
current_model = 0;
while (sanei_config_read (line, sizeof (line), fp))
{
char *string_entry = 0;
word = 0;
cp = get_token (line, &word);
if (!word || cp == line)
{
DBG_DBG ("ignoring empty line\n");
if (word)
free (word);
word = 0;
continue;
}
if (word[0] == ';')
{
DBG_DBG ("ignoring comment line\n");
free (word);
word = 0;
continue;
}
DBG_DBG ("line: %s\n", line);
if (read_keyword (line, ":backend", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
backend_entry *be = first_backend, *prev_be = 0, *new_be =
0;
DBG_INFO ("creating backend entry `%s'\n", string_entry);
new_be = calloc (1, sizeof (backend_entry));
if (!new_be)
{
DBG_ERR ("calloc failed (%s)\n", strerror (errno));
return SANE_FALSE;
}
new_be->name = string_entry;
new_be->status = status_unknown;
new_be->new = SANE_FALSE;
if (!be)
{
first_backend = new_be;
be = new_be;
}
else
{
while (be)
{
int compare =
string_compare (new_be->name, be->name);
if (compare <= 0)
{
backend_entry *be_tmp = be;
be = new_be;
be->next = be_tmp;
if (!prev_be)
first_backend = be;
else
prev_be->next = be;
break;
}
prev_be = be;
be = be->next;
}
if (!be) /* last entry */
{
prev_be->next = new_be;
be = prev_be->next;
}
}
current_backend = be;
current_type = 0;
current_mfg = 0;
current_model = 0;
current_level = level_backend;
continue;
}
if (!current_backend)
{
DBG_ERR ("use `:backend' keyword first\n");
return SANE_FALSE;
}
if (read_keyword (line, ":version", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
if (current_backend->version)
{
DBG_WARN ("overwriting version of backend `%s' to `%s'"
"(was: `%s')\n",
current_backend->name, string_entry,
current_backend->version, current_backend->version);
}
DBG_INFO ("setting version of backend `%s' to `%s'\n",
current_backend->name, string_entry);
current_backend->version = string_entry;
continue;
}
if (read_keyword (line, ":status", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
switch (current_level)
{
case level_backend:
if (current_backend->status != status_unknown)
{
DBG_WARN ("overwriting status of backend `%s'\n",
current_backend->name);
}
if (strcmp (string_entry, ":new") == 0)
{
DBG_WARN ("ignored `%s' status :new, use keyword "
"`:new :yes' instead\n",
current_backend->name);
current_backend->status = status_unknown;
}
else if (strcmp (string_entry, ":alpha") == 0)
{
DBG_WARN
("DEPRECATED backend status `alpha': setting status of backend `%s' to `basic'\n",
current_backend->name);
current_backend->status = status_basic;
}
else if (strcmp (string_entry, ":beta") == 0)
{
DBG_WARN
("DEPRECATED backend status `beta': setting status of backend `%s' to `good'\n",
current_backend->name);
current_backend->status = status_good;
}
else if (strcmp (string_entry, ":stable") == 0)
{
DBG_WARN
("DEPRECATED backend status `stable': setting status of backend `%s' to `complete'\n",
current_backend->name);
current_backend->status = status_complete;
}
else
{
DBG_ERR ("unknown status of backend `%s': `%s'\n",
current_backend->name, string_entry);
current_backend->status = status_unknown;
return SANE_FALSE;
}
break;
case level_model:
if (current_model->status != status_unknown)
{
DBG_WARN ("overwriting status of model `%s' (backend `%s')\n",
current_model->name, current_backend->name);
}
if (strcmp (string_entry, ":alpha") == 0)
{
DBG_WARN
("DEPRECATED status `alpha': setting status of model `%s' to `basic' (backend `%s')\n",
current_model->name, current_backend->name);
current_model->status = status_basic;
}
else if (strcmp (string_entry, ":beta") == 0)
{
DBG_WARN
("DEPRECATED status `beta': setting status of model `%s' to `good' (backend `%s')\n",
current_model->name, current_backend->name);
current_model->status = status_good;
}
else if (strcmp (string_entry, ":stable") == 0)
{
DBG_WARN
("DEPRECATED status `stable': setting status of model `%s' to `complete' (backend `%s')\n",
current_model->name, current_backend->name);
current_model->status = status_complete;
}
else if (strcmp (string_entry, ":minimal") == 0)
{
DBG_INFO
("setting status of model `%s' to `minimal'\n",
current_model->name);
current_model->status = status_minimal;
}
else if (strcmp (string_entry, ":basic") == 0)
{
DBG_INFO
("setting status of model `%s' to `basic'\n",
current_model->name);
current_model->status = status_basic;
}
else if (strcmp (string_entry, ":good") == 0)
{
DBG_INFO
("setting status of model `%s' to `good'\n",
current_model->name);
current_model->status = status_good;
}
else if (strcmp (string_entry, ":complete") == 0)
{
DBG_INFO
("setting status of model `%s' to `complete'\n",
current_model->name);
current_model->status = status_complete;
}
else if (strcmp (string_entry, ":untested") == 0)
{
DBG_INFO
("setting status of model `%s' to `untested'\n",
current_model->name);
current_model->status = status_untested;
}
else if (strcmp (string_entry, ":unsupported") == 0)
{
DBG_INFO
("setting status of model `%s' to `unsupported'\n",
current_model->name);
current_model->status = status_unsupported;
}
else
{
DBG_ERR ("unknown status of model `%s': `%s' (backend `%s')\n",
current_model->name, string_entry, current_backend->name);
current_model->status = status_untested;
return SANE_FALSE;
}
break;
default:
DBG_ERR ("level %d not implemented for :status (backend `%s')\n",
current_level, current_backend->name);
return SANE_FALSE;
}
continue;
}
if (read_keyword (line, ":new", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
if (strcmp (string_entry, ":yes") == 0)
{
DBG_INFO ("backend %s is new in this SANE release\n",
current_backend->name);
current_backend->new = SANE_TRUE;
}
else if (strcmp (string_entry, ":no") == 0)
{
DBG_INFO
("backend %s is NOT new in this SANE release\n",
current_backend->name);
current_backend->new = SANE_FALSE;
}
else
{
DBG_ERR ("unknown :new parameter of backend `%s': "
"`%s'\n", current_backend->name, string_entry);
current_backend->new = SANE_FALSE;
return SANE_FALSE;
}
continue;
}
if (read_keyword (line, ":manpage", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
if (current_backend->manpage)
{
DBG_WARN ("overwriting manpage of backend `%s' to `%s'"
"(was: `%s')\n",
current_backend->name, string_entry,
current_backend->manpage);
}
DBG_INFO ("setting manpage of backend `%s' to `%s'\n",
current_backend->name, string_entry);
current_backend->manpage = string_entry;
continue;
}
if (read_keyword
(line, ":devicetype", param_string,
&string_entry) == SANE_STATUS_GOOD)
{
type_entry *type = 0;
type = current_backend->type;
DBG_INFO ("adding `%s' to list of device types of backend "
"`%s'\n", string_entry, current_backend->name);
if (type)
{
while (type->next)
type = type->next;
type->next = calloc (1, sizeof (type_entry));
type = type->next;
}
else
{
current_backend->type = calloc (1, sizeof (type_entry));
type = current_backend->type;
}
type->type = type_unknown;
if (strcmp (string_entry, ":scanner") == 0)
{
DBG_INFO ("setting device type of backend `%s' to "
"scanner\n", current_backend->name);
type->type = type_scanner;
}
else if (strcmp (string_entry, ":stillcam") == 0)
{
DBG_INFO ("setting device type of backend `%s' to "
"still camera\n", current_backend->name);
type->type = type_stillcam;
}
else if (strcmp (string_entry, ":vidcam") == 0)
{
DBG_INFO ("setting device type of backend `%s' to "
"video camera\n", current_backend->name);
type->type = type_vidcam;
}
else if (strcmp (string_entry, ":api") == 0)
{
DBG_INFO ("setting device type of backend `%s' to "
"API\n", current_backend->name);
type->type = type_api;
}
else if (strcmp (string_entry, ":meta") == 0)
{
DBG_INFO ("setting device type of backend `%s' to "
"meta\n", current_backend->name);
type->type = type_meta;
}
else
{
DBG_ERR ("unknown device type of backend `%s': `%s'\n",
current_backend->name, string_entry);
type->type = type_unknown;
return SANE_FALSE;
}
current_type = type;
current_mfg = 0;
current_model = 0;
continue;
}
if (read_keyword (line, ":desc", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
if (!current_type)
{
DBG_ERR ("use `:devicetype' keyword first (backend `%s')\n",
current_backend->name);
return SANE_FALSE;
}
if (current_type->type < type_meta)
{
DBG_ERR ("use `:desc' for `:api' and `:meta' only (backend `%s')\n",
current_backend->name);
return SANE_FALSE;
}
if (current_type->desc)
{
DBG_WARN ("overwriting description of device type of "
"backend `%s' to `%s' (was: `%s')\n",
current_backend->name, string_entry,
current_type->desc);
}
DBG_INFO ("setting description of backend `%s' to `%s'\n",
current_backend->name, string_entry);
current_type->desc = calloc (1, sizeof (desc_entry));
if (!current_type->desc)
{
DBG_ERR ("calloc failed (%s)\n", strerror (errno));
return SANE_FALSE;
}
current_type->desc->desc = string_entry;
current_level = level_desc;
current_mfg = 0;
current_model = 0;
continue;
}
if (read_keyword (line, ":mfg", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
mfg_entry *mfg = 0;
if (!current_type)
{
DBG_ERR ("use `:devicetype' keyword first (backend `%s')\n",
current_backend->name);
return SANE_FALSE;
}
if (current_type->type >= type_meta)
{
DBG_ERR ("use `:mfg' for hardware devices only (backend `%s')\n",
current_backend->name);
return SANE_FALSE;
}
mfg = current_type->mfg;
if (mfg)
{
while (mfg->next)
mfg = mfg->next;
mfg->next = calloc (1, sizeof (mfg_entry));
mfg = mfg->next;
}
else
{
current_type->mfg = calloc (1, sizeof (mfg_entry));
mfg = current_type->mfg;
}
if (!mfg)
{
DBG_ERR ("calloc failed (%s)\n", strerror (errno));
return SANE_FALSE;
}
mfg->name = string_entry;
DBG_INFO ("adding mfg entry %s to backend `%s'\n",
string_entry, current_backend->name);
current_mfg = mfg;
current_model = 0;
current_level = level_mfg;
continue;
}
if (read_keyword (line, ":model", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
model_entry *model = 0;
if (!current_type)
{
DBG_ERR ("use `:devicetype' keyword first (backend `%s')\n",
current_backend->name);
return SANE_FALSE;
}
if (current_level != level_mfg
&& current_level != level_model)
{
DBG_ERR ("use `:mfg' keyword first (backend `%s')\n",
current_backend->name);
return SANE_FALSE;
}
model = current_mfg->model;
if (model)
{
while (model->next)
model = model->next;
model->next = calloc (1, sizeof (model_entry));
model = model->next;
}
else
{
current_mfg->model = calloc (1, sizeof (model_entry));
model = current_mfg->model;
}
if (!model)
{
DBG_ERR ("calloc failed (%s)\n", strerror (errno));
return SANE_FALSE;
}
model->name = string_entry;
model->status = status_unknown;
DBG_INFO ("adding model entry %s to manufacturer `%s'\n",
string_entry, current_mfg->name);
current_model = model;
current_level = level_model;
continue;
}
if (read_keyword
(line, ":interface", param_string,
&string_entry) == SANE_STATUS_GOOD)
{
if (!current_model)
{
DBG_WARN ("ignored `%s' :interface, only allowed for "
"hardware devices\n", current_backend->name);
continue;
}
if (current_model->interface)
{
DBG_WARN ("overwriting `%s's interface of model "
"`%s' to `%s' (was: `%s')\n",
current_backend->name, current_model->name, string_entry,
current_model->interface);
}
DBG_INFO ("setting interface of model `%s' to `%s'\n",
current_model->name, string_entry);
current_model->interface = string_entry;
continue;
}
if (read_keyword (line, ":url", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
switch (current_level)
{
case level_backend:
current_backend->url =
update_url_list (current_backend->url, string_entry);
DBG_INFO ("adding `%s' to list of urls of backend "
"`%s'\n", string_entry,
current_backend->name);
break;
case level_mfg:
current_mfg->url =
update_url_list (current_mfg->url, string_entry);
DBG_INFO ("adding `%s' to list of urls of mfg "
"`%s'\n", string_entry, current_mfg->name);
break;
case level_desc:
current_type->desc->url =
update_url_list (current_type->desc->url,
string_entry);
DBG_INFO ("adding `%s' to list of urls of description "
"for backend `%s'\n", string_entry,
current_backend->name);
break;
case level_model:
current_model->url =
update_url_list (current_model->url, string_entry);
DBG_INFO ("adding `%s' to list of urls of model "
"`%s'\n", string_entry, current_model->name);
break;
default:
DBG_ERR ("level %d not implemented for :url (backend `%s')\n",
current_level, current_backend->name);
return SANE_FALSE;
}
continue;
}
if (read_keyword (line, ":comment", param_string, &string_entry)
== SANE_STATUS_GOOD)
{
switch (current_level)
{
case level_backend:
current_backend->comment = string_entry;
DBG_INFO ("setting comment of backend %s to `%s'\n",
current_backend->name, string_entry);
break;
case level_mfg:
current_mfg->comment = string_entry;
DBG_INFO
("setting comment of manufacturer %s to `%s'\n",
current_mfg->name, string_entry);
break;
case level_desc:
current_type->desc->comment = string_entry;
DBG_INFO ("setting comment of description for "
"backend %s to `%s'\n", current_backend->name,
string_entry);
break;
case level_model:
current_model->comment = string_entry;
DBG_INFO ("setting comment of model %s to `%s'\n",
current_model->name, string_entry);
break;
default:
DBG_ERR ("level %d not implemented for `:comment' (backend `%s')\n",
current_level, current_backend->name);
return SANE_FALSE;
}
continue;
}
DBG_ERR ("unknown keyword token in line `%s' of file `%s'\n", line, file_name);
return SANE_FALSE;
} /* while (sanei_config_readline) */
fclose (fp);
} /* if (strlen) */
} /* while (direntry) */
if (!first_backend)
{
DBG_ERR ("Couldn't find any .desc file\n");
return SANE_FALSE;
}
return SANE_TRUE;
}
/* Create a model_record_entry based on a model_entry */
static model_record_entry *
create_model_record (model_entry * model)
{
model_record_entry *model_record;
model_record = calloc (1, sizeof (model_record_entry));
if (!model_record)
{
DBG_ERR ("create_model_record: couldn't calloc model_record_entry\n");
exit (1);
}
model_record->name = model->name;
model_record->status = model->status;
model_record->interface = model->interface;
model_record->url = model->url;
model_record->comment = model->comment;
return model_record;
}
/* Calculate the priority of statuses: */
/* minimal, basic, good, complete -> 2, untested -> 1, unsupported -> 0 */
static int
calc_priority (status_entry status)
{
switch (status)
{
case status_untested:
return 1;
case status_unsupported:
return 0;
default:
return 2;
}
}
/* Insert model into list at the alphabetically correct position */
static model_record_entry *
update_model_record_list (model_record_entry * first_model_record,
model_entry * model, backend_entry * be)
{
model_record_entry *model_record = first_model_record;
if (!first_model_record)
{
/* First model for this manufacturer */
first_model_record = create_model_record (model);
model_record = first_model_record;
}
else
{
model_record_entry *prev_model_record = 0;
while (model_record)
{
int compare = string_compare (model->name, model_record->name);
if (compare <= 0)
{
model_record_entry *tmp_model_record = model_record;
if (compare == 0 &&
string_compare (model->interface, model_record->interface)
== 0)
{
/* Two entries for the same model */
int new_priority = calc_priority (model->status);
int old_priority = calc_priority (model_record->status);
if (new_priority < old_priority)
{
DBG_DBG ("update_model_record_list: model %s ignored, backend %s has "
"higher priority\n", model->name, model_record->be->name);
return first_model_record;
}
if (new_priority > old_priority)
{
DBG_DBG ("update_model_record_list: model %s overrides the one from backend %s\n",
model->name, model_record->be->name);
tmp_model_record = model_record->next;
}
}
/* correct position */
model_record = create_model_record (model);
model_record->next = tmp_model_record;
if (!prev_model_record)
first_model_record = model_record;
else
prev_model_record->next = model_record;
break;
}
prev_model_record = model_record;
model_record = model_record->next;
}
if (!model_record) /* last entry */
{
prev_model_record->next = create_model_record (model);
model_record = prev_model_record->next;
}
} /* if (first_model_record) */
model_record->be = be;
DBG_DBG ("update_model_record_list: added model %s\n", model->name);
return first_model_record;
}
/* Insert manufacturer into list at the alphabetically correct position, */
/* create new record if neccessary */
static mfg_record_entry *
update_mfg_record_list (mfg_record_entry * first_mfg_record, mfg_entry * mfg,
backend_entry * be)
{
model_entry *model = mfg->model;
mfg_record_entry *mfg_record = first_mfg_record;
while (mfg_record)
{
if (string_compare (mfg_record->name, mfg->name) == 0)
{
/* Manufacturer already exists */
url_entry *mfg_url = mfg->url;
/* Manufacturer comments and (additional) URLs? */
if (!mfg_record->comment)
mfg_record->comment = mfg->comment;
while (mfg_url && mfg_url->name)
{
mfg_record->url = update_url_list (mfg_record->url,
mfg_url->name);
mfg_url = mfg_url->next;
}
break;
}
mfg_record = mfg_record->next;
}
if (!mfg_record)
{
/* Manufacturer doesn't exist yet */
url_entry *url = mfg->url;
mfg_record = calloc (1, sizeof (mfg_record_entry));
if (!mfg_record)
{
DBG_ERR ("update_mfg_record_list: couldn't calloc "
"mfg_record_entry\n");
exit (1);
}
mfg_record->name = mfg->name;
mfg_record->comment = mfg->comment;
while (url)
{
mfg_record->url = update_url_list (mfg_record->url, url->name);
url = url->next;
}
if (first_mfg_record != 0)
{
/* We already have one manufacturer in the list */
mfg_record_entry *new_mfg_record = mfg_record;
mfg_record_entry *prev_mfg_record = 0;
mfg_record = first_mfg_record;
while (mfg_record)
{
int compare =
string_compare (new_mfg_record->name, mfg_record->name);
if (compare <= 0)
{
mfg_record_entry *tmp_mfg_record = mfg_record;
mfg_record = new_mfg_record;
mfg_record->next = tmp_mfg_record;
if (!prev_mfg_record)
first_mfg_record = mfg_record;
else
prev_mfg_record->next = mfg_record;
break;
}
prev_mfg_record = mfg_record;
mfg_record = mfg_record->next;
}
if (!mfg_record) /* last entry */
{
prev_mfg_record->next = new_mfg_record;
mfg_record = prev_mfg_record->next;
}
}
else
first_mfg_record = mfg_record;
DBG_DBG ("update_mfg_record_list: created mfg %s\n", mfg_record->name);
} /* if (!mfg_record) */
/* create model entries */
while (model)
{
mfg_record->model_record =
update_model_record_list (mfg_record->model_record, model, be);
model = model->next;
}
return first_mfg_record;
}
/* Create a sorted list of manufacturers based on the backends list */
static mfg_record_entry *
create_mfg_list (device_type dev_type)
{
mfg_record_entry *first_mfg_record = 0;
backend_entry *be = first_backend;
DBG_DBG ("create_mfg_list: start\n");
while (be)
{
type_entry *type = be->type;
while (type)
{
if (type->type == dev_type)
{
mfg_entry *mfg = type->mfg;
while (mfg)
{
first_mfg_record =
update_mfg_record_list (first_mfg_record, mfg, be);
mfg = mfg->next;
}
}
type = type->next;
}
be = be->next;
}
DBG_DBG ("create_mfg_list: exit\n");
return first_mfg_record;
}
/* Print an ASCII list with all the information we have */
static void
ascii_print_backends (void)
{
backend_entry *be;
be = first_backend;
while (be)
{
url_entry *url = be->url;
type_entry *type = be->type;
if (be->name)
printf ("backend `%s'\n", be->name);
else
printf ("backend *none*\n");
if (be->version)
printf (" version `%s'\n", be->version);
else
printf (" version *none*\n");
if (be->new)
printf (" NEW!\n");
if (be->manpage)
printf (" manpage `%s'\n", be->manpage);
else
printf (" manpage *none*\n");
if (url)
while (url)
{
printf (" url `%s'\n", url->name);
url = url->next;
}
else
printf (" url *none*\n");
if (be->comment)
printf (" comment `%s'\n", be->comment);
else
printf (" comment *none*\n");
if (type)
while (type)
{
switch (type->type)
{
case type_scanner:
printf (" type scanner\n");
break;
case type_stillcam:
printf (" type stillcam\n");
break;
case type_vidcam:
printf (" type vidcam\n");
break;
case type_meta:
printf (" type meta\n");
break;
case type_api:
printf (" type api\n");
break;
default:
printf (" type *unknown*\n");
break;
}
if (type->desc)
{
url_entry *url = type->desc->url;
printf (" desc `%s'\n", type->desc->desc);
if (url)
while (url)
{
printf (" url `%s'\n", url->name);
url = url->next;
}
else
printf (" url *none*\n");
if (type->desc->comment)
printf (" comment `%s'\n", type->desc->comment);
else
printf (" comment *none*\n");
}
else if (type->type >= type_meta)
printf (" desc *none*\n");
if (type->mfg)
{
mfg_entry *mfg = type->mfg;
while (mfg)
{
model_entry *model = mfg->model;
url_entry *url = mfg->url;
printf (" mfg `%s'\n", mfg->name);
if (url)
while (url)
{
printf (" url `%s'\n", url->name);
url = url->next;
}
else
printf (" url *none*\n");
if (mfg->comment)
printf (" comment `%s'\n", mfg->comment);
else
printf (" comment *none*\n");
if (model)
while (model)
{
url_entry *url = model->url;
printf (" model `%s'\n", model->name);
if (model->interface)
printf (" interface `%s'\n", model->interface);
else
printf (" interface *none*\n");
if (model->status == status_unknown)
model->status = be->status;
switch (model->status)
{
case status_minimal:
printf (" status minimal\n");
break;
case status_basic:
printf (" status basic\n");
break;
case status_good:
printf (" status good\n");
break;
case status_complete:
printf (" status complete\n");
break;
case status_untested:
printf (" status untested\n");
break;
case status_unsupported:
printf (" status unsupported\n");
break;
default:
printf (" status *unknown*\n");
break;
}
if (url)
while (url)
{
printf (" url `%s'\n", url->name);
url = url->next;
}
else
printf (" url *none*\n");
if (model->comment)
printf (" comment `%s'\n", model->comment);
else
printf (" comment *none*\n");
model = model->next;
}
else
printf (" model *none*\n");
mfg = mfg->next;
} /* while (mfg) */
}
else if (type->type < type_meta)
printf (" mfg *none*\n");
type = type->next;
} /* while (type) */
else
printf (" type *none*\n");
be = be->next;
} /* while (be) */
}
/* Generate a name used for HTML tags */
static char *
html_generate_anchor_name (char *manufacturer_name)
{
char *name = strdup (manufacturer_name);
char *pointer = name;
if (!name)
{
DBG_DBG ("html_generate_anchor_name: couldn't strdup\n");
}
while (*pointer)
{
if (!isalnum (*pointer))
*pointer = '-';
else
*pointer = toupper (*pointer);
pointer++;
}
return name;
}
/* Generate one table per backend of all backends providing models */
/* of type dev_type */
static void
html_backends_split_table (device_type dev_type)
{
backend_entry *be = first_backend;
SANE_Bool first = SANE_TRUE;
printf ("Backends: \n");
while (be) /* print link list */
{
type_entry *type = be->type;
SANE_Bool found = SANE_FALSE;
while (type)
{
if (type->type == dev_type)
found = SANE_TRUE;
type = type->next;
}
if (found)
{
if (!first)
printf (", \n");
first = SANE_FALSE;
printf ("%s",
html_generate_anchor_name (be->name), be->name);
}
be = be->next;
}
be = first_backend;
if (first)
printf ("(none)\n");
printf ("
\n");
while (be)
{
type_entry *type = be->type;
while (type)
{
if (type->type == dev_type)
{
mfg_entry *mfg = type->mfg;
model_entry *model;
printf ("\n");
printf ("\n");
if (be->url && be->url->name)
{
url_entry *url = be->url;
printf ("Link(s): \n");
while (url)
{
if (url != be->url)
printf (", ");
printf ("%s", url->name, url->name);
url = url->next;
}
printf ("
\n");
}
if (be->manpage)
printf ("Manual page: %s
\n", be->manpage, be->manpage);
if (be->comment)
printf ("Comment: %s
\n", be->comment);
if (type->desc)
{
if (type->desc->desc)
{
if (type->desc->url && type->desc->url->name)
printf ("Description: "
"%s
\n",
type->desc->url->name, type->desc->desc);
else
printf ("Description: %s
\n",
type->desc->desc);
}
if (type->desc->comment)
printf ("Comment: %s
\n", type->desc->comment);
printf ("
\n");
type = type->next;
continue;
}
printf ("
\n");
if (!mfg)
{
type = type->next;
continue;
}
printf ("\n");
printf ("\n");
printf ("Manufacturer | \n");
printf ("Model | \n");
printf ("Interface | \n");
printf ("Status | \n");
printf ("Comment | \n");
printf ("
\n");
mfg = type->mfg;
while (mfg)
{
model = mfg->model;
if (model)
{
int num_models = 0;
while (model) /* count models for rowspan */
{
model = model->next;
num_models++;
}
model = mfg->model;
printf ("\n");
printf ("\n", num_models);
if (mfg->url && mfg->url->name)
printf ("%s\n", mfg->url->name,
mfg->name);
else
printf ("%s\n", mfg->name);
while (model)
{
enum status_entry status = model->status;
if (model != mfg->model)
printf (" |
\n");
if (model->url && model->url->name)
printf
("%s | \n",
model->url->name, model->name);
else
printf ("%s | \n",
model->name);
if (model->interface)
printf ("%s | \n",
model->interface);
else
printf ("? | \n");
printf ("");
if (status == status_unknown)
status = be->status;
switch (status)
{
case status_minimal:
printf ("minimal");
break;
case status_basic:
printf ("basic");
break;
case status_good:
printf ("good");
break;
case status_complete:
printf ("complete");
break;
case status_untested:
printf ("untested");
break;
case status_unsupported:
printf ("unsupported");
break;
default:
printf ("?");
break;
}
printf (" | \n");
if (model->comment && model->comment[0] != 0)
printf ("%s | \n", model->comment);
else
printf (" | \n");
model = model->next;
printf ("
\n");
} /* while (model) */
} /* if (num_models) */
mfg = mfg->next;
} /* while (mfg) */
printf ("
\n");
} /* if (type->type) */
type = type->next;
} /* while (type) */
be = be->next;
} /* while (be) */
/* printf ("\n");*/
}
/* Generate one table per manufacturer constructed of all backends */
/* providing models of type dev_type */
static void
html_mfgs_table (device_type dev_type)
{
mfg_record_entry *mfg_record = 0, *first_mfg_record = 0;
first_mfg_record = create_mfg_list (dev_type);
mfg_record = first_mfg_record;
printf ("Manufacturers: \n");
while (mfg_record)
{
if (mfg_record != first_mfg_record)
printf (", \n");
printf ("%s",
html_generate_anchor_name (mfg_record->name), mfg_record->name);
mfg_record = mfg_record->next;
}
mfg_record = first_mfg_record;
if (!mfg_record)
printf ("(none)\n");
printf ("
\n");
while (mfg_record)
{
model_record_entry *model_record = mfg_record->model_record;
printf ("\n",
html_generate_anchor_name (mfg_record->name), mfg_record->name);
printf ("\n");
if (mfg_record->url && mfg_record->url->name)
{
url_entry *url = mfg_record->url;
printf ("Link(s): \n");
while (url)
{
if (url != mfg_record->url)
printf (", ");
printf ("%s", url->name, url->name);
url = url->next;
}
printf ("
\n");
}
if (mfg_record->comment)
printf ("Comment: %s
\n", mfg_record->comment);
printf ("
\n");
if (!model_record)
{
mfg_record = mfg_record->next;
continue;
}
printf ("\n");
printf ("\n");
printf ("Model | \n");
printf ("Interface | \n");
printf ("Status | \n");
printf ("Comment | \n");
printf ("Backend | \n");
printf ("Manpage | \n");
printf ("
\n");
while (model_record)
{
enum status_entry status = model_record->status;
if (model_record->url && model_record->url->name)
printf ("%s | \n",
model_record->url->name, model_record->name);
else
printf ("
%s | \n", model_record->name);
if (model_record->interface)
printf ("%s | \n", model_record->interface);
else
printf ("? | \n");
printf ("");
if (status == status_unknown)
status = model_record->be->status;
switch (status)
{
case status_minimal:
printf ("minimal");
break;
case status_basic:
printf ("basic");
break;
case status_good:
printf ("good");
break;
case status_complete:
printf ("complete");
break;
case status_untested:
printf ("untested");
break;
case status_unsupported:
printf ("unsupported");
break;
default:
printf ("?");
break;
}
printf (" | \n");
if (model_record->comment && model_record->comment[0] != 0)
printf ("%s | \n", model_record->comment);
else
printf (" | \n");
printf ("\n");
if (model_record->be->url && model_record->be->url->name)
printf ("%s\n",
model_record->be->url->name, model_record->be->name);
else
printf ("%s", model_record->be->name);
if (model_record->be->version || model_record->be->new)
{
printf (" (");
if (model_record->be->version)
{
printf ("%s", model_record->be->version);
if (model_record->be->new)
printf (", NEW!");
}
else
printf ("NEW!");
printf (")\n");
}
printf (" | \n");
if (model_record->be->manpage)
printf ("%s | \n",
model_record->be->manpage, model_record->be->manpage);
else
printf ("? | \n");
printf ("
\n");
model_record = model_record->next;
} /* while model_record */
printf ("
\n");
mfg_record = mfg_record->next;
} /* while mfg_record */
}
/* Print the HTML headers and an introduction */
static void
html_print_header (void)
{
printf
("\n"
" \n"
"\n");
printf ("%s\n", title);
printf
("\n"
"\n"
"\n"
"
![\"SANE\"](\"http://www.mostang.com/sane/sane.png\")
\n");
printf ("
%s
\n", title);
printf ("
\n" "
\n");
printf ("%s\n", intro);
printf
("This is only a summary!\n"
"Please consult the manpages and the author-supplied webpages\n"
"for more detailed (and usually important) information\n"
"concerning each backend.
\n"
"There are special tables for parallel port and \n"
"USB scanners from \n"
"Jonathan Buzzard.
\n");
printf
("If you have new information or corrections, please contact\n"
"henning@meier-geinitz.de\n"
"with as many details as possible. Also please contact me if your scanner \n"
"isn't mentioned in this list at all.
\n"
"For an explanation of the tables, see the\n"
"legend.\n");
printf
("
There are tables for scanners,\n"
"still cameras,\n"
"video cameras,\n"
"APIs, and\n"
"meta backends.\n");
}
/* Print the HTML footers and contact information */
static void
html_print_footer (void)
{
time_t current_time = time (0);
printf
("
\n"
"[Back]\n"
"\n"
"sane-devel@mostang.com / SANE Development mailing list\n"
"\n" "\n");
printf ("This page was last updated on %s\n",
asctime (localtime (¤t_time)));
printf ("\n");
printf (" \n");
}
/* print parts of the legend */
static void
html_print_legend_backend (void)
{
printf
(" Backend:\n"
" Name of the backend, in parentheses if available:\n"
" Version of backend/driver; newer versions may be\n"
" available from their home sites.
"
" NEW! means brand-new to the\n"
" current release of SANE.
\n"
" UNMAINTAINED means that nobody maintains that backend. Expect no \n"
" new features or newly supported devices. You are welcome to take over \n"
" maintainership.\n"
" \n");
}
static void
html_print_legend_link (void)
{
printf
(" Link(s):\n"
" Link(s) to more extensive and\n"
" detailed information, if it exists, or the email address\n"
" of the author or maintainer.\n");
}
static void
html_print_legend_manual (void)
{
printf
(" Manual Page:\n"
" A link to the man-page online, if it exists.\n");
}
static void
html_print_legend_comment (void)
{
printf
(" Comment:\n"
" More information about the backend or model, e.g. the level of "
" support and possible problems.\n");
}
static void
html_print_legend_manufacturer (void)
{
printf
(" Manufacturer:\n"
" Manufacturer, vendor or brand name of the device.\n");
}
static void
html_print_legend_model (void)
{
printf
(" Model:\n"
" Name of the the device.\n");
}
static void
html_print_legend_interface (void)
{
printf
(" Interface:\n"
" How the device is connected to the computer.\n");
}
static void
html_print_legend_status (void)
{
printf
(" Status:\n"
" Indicates how many of the features the device provides \n"
" are supported by SANE.\n"
" - unsupported"
" means the device is not supported at least by this backend. "
" It may be supported by other backends, however.\n");
printf
("
- untested means the "
" device may be supported but couldn't be tested. Be very "
" careful and report success/failure.\n"
"
- minimal means that the\n"
" device is detected and scans at least in one mode. But the quality \n"
" is bad or important features won't work.\n");
printf
("
- basic means it works at \n"
" least in the most important modes but quality is not perfect.\n"
"
- good means the device is usable \n"
" for day-to-day work. Some rather exotic features may be missing.\n"
"
- complete means the backends \n"
" supports everything the device can do.\n"
"
\n");
}
static void
html_print_legend_description (void)
{
printf
(" Description:\n"
" The scope of application of the backend.\n");
}
/* Print the HTML page with one table of models per backend */
static void
html_print_backends_split (void)
{
if (!title)
title = "SANE: Backends (Drivers)";
if (!intro)
intro = " The following table summarizes the backends/drivers "
"distributed with the latest version of sane-backends, and the hardware "
"or software they support.
";
html_print_header ();
printf ("\n");
html_backends_split_table (type_scanner);
printf ("\n");
html_backends_split_table (type_stillcam);
printf ("\n");
html_backends_split_table (type_vidcam);
printf ("\n");
html_backends_split_table (type_api);
printf ("\n");
html_backends_split_table (type_meta);
printf ("\n"
"\n");
html_print_legend_backend ();
html_print_legend_link ();
html_print_legend_manual ();
html_print_legend_comment ();
html_print_legend_manufacturer ();
html_print_legend_model ();
html_print_legend_interface ();
html_print_legend_status ();
html_print_legend_description ();
printf ("
\n");
html_print_footer ();
}
/* Print the HTML page with one table of models per manufacturer */
static void
html_print_mfgs (void)
{
if (!title)
title = "SANE: Supported Devices";
if (!intro)
intro = " The following table summarizes the devices supported "
"by the latest version of sane-backends.
";
html_print_header ();
printf ("\n");
html_mfgs_table (type_scanner);
printf ("\n");
html_mfgs_table (type_stillcam);
printf ("\n");
html_mfgs_table (type_vidcam);
printf ("\n");
html_backends_split_table (type_api);
printf ("\n");
html_backends_split_table (type_meta);
printf
("\n"
"\n"
"\n");
html_print_legend_model ();
html_print_legend_interface ();
html_print_legend_status ();
html_print_legend_comment ();
html_print_legend_backend ();
html_print_legend_manual ();
html_print_legend_manufacturer ();
html_print_legend_description ();
printf("
\n" "
\n");
html_print_footer ();
}
int
main (int argc, char **argv)
{
program_name = strrchr (argv[0], '/');
if (program_name)
++program_name;
else
program_name = argv[0];
if (!get_options (argc, argv))
return 1;
if (!read_files ())
return 1;
switch (mode)
{
case output_mode_ascii:
ascii_print_backends ();
break;
case output_mode_html_backends_split:
html_print_backends_split ();
break;
case output_mode_html_mfgs:
html_print_mfgs ();
break;
default:
DBG_ERR ("Unknown output mode\n");
return 1;
}
return 0;
}