/* model-info.c -- per model information objects and cache
* Copyright (C) 2010 SEIKO EPSON CORPORATION
*
* License: GPLv2+|iscan
* Authors: AVASYS CORPORATION
*
* This file is part of the SANE backend distributed with Image Scan!
*
* Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License
* along with this package. If not, see .
*
*
* Linking Image Scan!'s SANE backend statically or dynamically with
* other modules is making a combined work based on this SANE backend.
* Thus, the terms and conditions of the GNU General Public License
* cover the whole combination.
*
* As a special exception, the copyright holders of Image Scan!'s SANE
* backend give you permission to link Image Scan!'s SANE backend with
* SANE frontends that communicate with Image Scan!'s SANE backend
* solely through the SANE Application Programming Interface,
* regardless of the license terms of these SANE frontends, and to
* copy and distribute the resulting combined work under terms of your
* choice, provided that every copy of the combined work is
* accompanied by a complete copy of the source code of Image Scan!'s
* SANE backend (the version of Image Scan!'s SANE backend used to
* produce the combined work), being distributed under the terms of
* the GNU General Public License plus this exception. An independent
* module is a module which is not derived from or based on Image
* Scan!'s SANE backend.
*
* As a special exception, the copyright holders of Image Scan!'s SANE
* backend give you permission to link Image Scan!'s SANE backend with
* independent modules that communicate with Image Scan!'s SANE
* backend solely through the "Interpreter" interface, regardless of
* the license terms of these independent modules, and to copy and
* distribute the resulting combined work under terms of your choice,
* provided that every copy of the combined work is accompanied by a
* complete copy of the source code of Image Scan!'s SANE backend (the
* version of Image Scan!'s SANE backend used to produce the combined
* work), being distributed under the terms of the GNU General Public
* License plus this exception. An independent module is a module
* which is not derived from or based on Image Scan!'s SANE backend.
*
* Note that people who make modified versions of Image Scan!'s SANE
* backend are not obligated to grant special exceptions for their
* modified versions; it is their choice whether to do so. The GNU
* General Public License gives permission to release a modified
* version without this exception; this exception also makes it
* possible to release a modified version which carries forward this
* exception.
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include "model-info.h"
#include
#include
#include "list.h"
#include "message.h"
#include "utils.h"
#include "get-infofile.h"
#include "xmlreader.h"
/*! \brief Points to our model info cache.
*
* Its value is guaranteed to be \c NULL \e outside the scope of a
* model_info_cache_init()/model_info_cache_exit() pair.
*/
static list *_cache = NULL;
/*! \brief Directory to search for resource files
*
* The _model_info_ctor() needs this.
*/
static const char *_datadir = NULL;
/*! \brief Collects all (supported) model information
*/
typedef struct
{
char *fw_name; /* key for _model_info_cache_get_info */
char *overseas; /* model name */
char *japan; /* model name */
char *name; /* points to one of overseas, japan or
* fw_name, never NULL */
scan_command_t *command; /* command customisation info */
EpsonScanHard profile; /* colour profiles */
bool from_file; /* origin of our data, used to control
* which resources need to be released
* at destruction time */
capability_data_t *dfault;
capability_data_t *adf_duplex;
} _model_info_t;
typedef enum
{
FEED,
FOCUS,
EJECT,
} _command_id_t;
/* Model info cache implementation details */
static _model_info_t * _model_info_cache_get_info (const char *fw_name,
SANE_Status *status);
/* Model info implementation details */
static _model_info_t * _model_info_ctor (const char *fw_name,
SANE_Status *status);
static void _model_info_dtor (void *p);
static char * _model_info_guess_name (const _model_info_t *self);
static SANE_Status _model_info_merge_file (_model_info_t *self);
static void _model_info_merge_data (_model_info_t *self,
xmlNodePtr node);
static bool _model_info_set_cmd (const _model_info_t *self,
unsigned char *cmd, _command_id_t id);
/*! \brief Sets up model info cache support.
*
* Model specific information will be looked for in \a pkgdatadir.
*
* \returns An opaque pointer to the cache. An additional \a status
* will be returned as well if the argument is not \c NULL.
*/
void *
model_info_cache_init (const char *pkgdatadir, SANE_Status *status)
{
SANE_Status s = SANE_STATUS_GOOD;
log_call ("(%s, %p)", pkgdatadir, status);
require (pkgdatadir);
if (_cache)
{
err_minor ("been here, done that");
if (0 != strcmp_c (_datadir, pkgdatadir))
{
err_major ("already using %s", _datadir);
}
if (status) *status = s;
return _cache;
}
_datadir = strdup (pkgdatadir);
_cache = list_create ();
if (!_datadir || !_cache)
{
_cache = model_info_cache_exit (_cache);
s = SANE_STATUS_NO_MEM;
}
if (0 != atexit (xmlCleanupParser))
{
err_minor ("could not register XML parser cleanup function");
}
/* ?FIXME?
* Check for existence/readability of _pkgdatadir and log its
* absence/presence. It is _not_ fatal if the directory does
* not exist or is not readable. We just want to note it via
* an err_minor(). If not readable, we may want to return a
* SANE_STATUS_ACCESS_DENIED.
*/
if (status) *status = s;
return _cache;
}
/*! \brief Tears down model info cache support.
*
* Releases all resources associated with the model info cache.
* The \a self argument should be an opaque pointer obtained via a
* call to model_info_cache_init().
*
* Note that for error recovery purposes, model_info_cache_init() may
* call this function with an empty _cache or no cache at all.
*
* \returns \c NULL, always
*/
void *
model_info_cache_exit (void *self)
{
log_call ("(%p)", self);
require (_cache == self);
const_delete (_datadir, char *);
list_destroy (_cache, _model_info_dtor);
_datadir = NULL;
_cache = NULL;
promise (!_cache);
return _cache;
}
/*! \brief Attempts to find model information for \a fw_name.
*
* \return A pointer to the model information, \c NULL if not found.
*/
const void *
model_info_cache_get_info (const char *fw_name, SANE_Status *status)
{
/* Just forward to the internal implementation. The return
* statement will handle the pointer type conversion.
*/
return _model_info_cache_get_info (fw_name, status);
}
/*! \brief Returns a best-effort model name based on \a fw_name.
*
* The caller gets to manage the memory occupied by the string that
* is returned. Note that \c NULL may be returned.
*/
char *
model_info_cache_get_model (const char *fw_name)
{
SANE_Status s = SANE_STATUS_GOOD;
_model_info_t *m = NULL;
log_call ("(%s)", fw_name);
require (_cache && _datadir);
if (!fw_name || 0 == strlen (fw_name))
{
err_minor ("%s", sane_strstatus (SANE_STATUS_INVAL));
return strdup ("(unknown model)");
}
m = _model_info_cache_get_info (fw_name, &s);
if (!m)
{
err_minor ("%s", sane_strstatus (s));
return strdup (fw_name); /* best we can do */
}
return strdup (m->name);
}
/*! \brief Returns a reference to the model name.
*
* Resources associated with the reference are owned by \a self. The
* caller should \e not attempt to release them.
*/
const char *
model_info_get_name (const void *self)
{
require (self); /* ?FIXME? check if in _cache? */
return ((const _model_info_t *) self)->name;
}
/*! \brief Returns a reference to the model's colour profiles.
*
* Resources associated with the reference are owned by \a self. The
* caller should \e not attempt to release them.
*/
const EpsonScanHard
model_info_get_profile (const void *self)
{
require (self); /* ?FIXME? check if in _cache? */
return ((const _model_info_t *) self)->profile;
}
/*! \brief Modify selected commands in the \a cmd specification
*
* This function caters to quirks in the command level specification
* reported by the device. Especially commands for hardware options
* may be affected.
*
* \return \c true if commands have been modified, \c false otherwise
*/
bool
model_info_customise_commands (const void *self, EpsonCmd cmd)
{
bool customised = false;
_model_info_t *self_ = NULL;
require (self); /* ?FIXME? check if in _cache? */
if (!cmd)
{
err_minor ("%s", sane_strstatus (SANE_STATUS_INVAL));
return customised;
}
self_ = (_model_info_t *) self;
customised |= _model_info_set_cmd (self_, &cmd->set_focus_position, FOCUS);
customised |= _model_info_set_cmd (self_, &cmd->feed, FEED);
customised |= _model_info_set_cmd (self_, &cmd->eject, EJECT);
return customised;
}
/*! \brief Attempts to find model information for \a fw_name.
*
* Checks for existing information in the cache before it attempts to
* add new model information. Takes care to preserve the cache's cur
* member so as not to invalidate existing "iterators".
*
* \return A pointer to the model information. \c NULL if not found,
* in case anything went wrong trying to add the info to the
* cache or the caller passed in garbage.
*/
static _model_info_t *
_model_info_cache_get_info (const char *fw_name, SANE_Status *status)
{
SANE_Status s = SANE_STATUS_GOOD;
_model_info_t *info = NULL;
list_entry *cur = NULL;
bool found = false;
log_call ("(%s)", fw_name);
require (_cache && _datadir);
if (!fw_name || 0 == strlen (fw_name))
{
if (status) *status = SANE_STATUS_INVAL;
return NULL;
}
cur = _cache->cur; /* check whether cached */
list_reset (_cache);
while (!found && (info = list_next (_cache)))
{
found = (0 == strcmp_c (info->fw_name, fw_name));
}
_cache->cur = cur;
if (!found) /* try to add info to cache */
{
info = _model_info_ctor (fw_name, &s);
if (!info || !list_append (_cache, info))
{
_model_info_dtor (info);
info = NULL;
}
}
if (status) *status = s;
return info;
}
/*! \brief Creates and initialises a model info object.
*
* \return A valid model info object or \c NULL if unable to acquire
* the necessary memory resources.
*/
static _model_info_t *
_model_info_ctor (const char *fw_name, SANE_Status *status)
{
SANE_Status s = SANE_STATUS_GOOD;
_model_info_t *self = NULL;
log_call ("(%s)", fw_name);
require (fw_name);
self = t_calloc (1, _model_info_t);
if (!self)
{
if (status) *status = SANE_STATUS_NO_MEM;
return NULL;
}
self->fw_name = strdup (fw_name);
if (!self->fw_name)
{
_model_info_dtor (self);
if (status) *status = SANE_STATUS_NO_MEM;
return NULL;
}
/* Set defaults using data defined in the source code. The various
* getters decide a decent default in case self->fw_name is not one
* of the names for which we have data in our sources.
*/
self->overseas = get_scanner_data (self->fw_name, MODEL_OVERSEAS);
self->japan = get_scanner_data (self->fw_name, MODEL_JAPAN);
self->profile = get_epson_scan_hard (self->fw_name);
self->command = get_scan_command (self->fw_name);
self->from_file = false;
s = _model_info_merge_file (self);
self->name = _model_info_guess_name (self);
if (self) /* make sure things are compliant */
{
promise (self->fw_name && self->name);
promise ( self->name == self->fw_name
|| self->name == self->overseas
|| self->name == self->japan);
promise (self->profile);
promise (self->command);
}
if (status) *status = s;
return self;
}
/*! \brief Destroys a model object.
*
* Releases any resources acquired throughout the object's life time.
*/
static void
_model_info_dtor (void *self)
{
_model_info_t *p = (_model_info_t *) self;
if (!p) return;
if (p->from_file)
{
/* :FIXME: p->profile may have been acquired as several
* individual arrays at construction. Check!
*/
if (p->profile != get_epson_scan_hard (p->fw_name))
delete (p->profile);
if (p->command != get_scan_command (p->fw_name))
delete (p->command);
delete (p->overseas);
delete (p->japan);
if(p->dfault)
{
delete (p->dfault->option);
delete (p->dfault->mode);
}
delete (p->dfault);
if (p->adf_duplex)
{
delete (p->adf_duplex->option);
delete (p->adf_duplex->mode);
}
delete (p->adf_duplex);
}
delete (p->fw_name);
delete (p);
}
/*! \brief Returns a best effort guess for model name on the device
*
* This functions implements the policy used to decide which of the
* various model names \c self->name should point to. The function
* is intended for use at construction of \a self.
*/
static char *
_model_info_guess_name (const _model_info_t *self)
{
require (self);
if (self->japan && self->overseas)
{
time_t lt = time (NULL);
struct tm *ptr = localtime (<);
if (ptr && 0 == strncmp_c ("JST", ptr->tm_zone, 3))
{
return self->japan;
}
else
{
return self->overseas;
}
}
if (self->japan) return self->japan;
if (self->overseas) return self->overseas;
return self->fw_name;
}
static char *
get_path_name (const char *hex_name, char *path_name, size_t path_size)
{
char *path;
size_t size = snprintf (path_name, path_size, "%s%s%s%s",
_datadir, FILE_SEP_STR, hex_name,
".xml");
if (size > -1 || size < path_size)
return path_name;
if (size > -1)
path_size = size + 1;
else
path_size *= 2;
if (0 == path_size)
path_size = 512;
path = t_realloc (path_name, path_size, char);
if (!path)
{
delete (path_name);
return NULL;
}
path_name = path;
return get_path_name (hex_name, path_name, path_size);
}
/*! \brief Attempts to merge model information from a data file.
*/
static SANE_Status
_model_info_merge_file (_model_info_t *self)
{
xmlDocPtr doc = NULL;
char *path_name = NULL;
char *hex_name = NULL;
require (self);
hex_name = fw_name_to_hex (self->fw_name);
if (!hex_name) return SANE_STATUS_NO_MEM;
path_name = get_path_name (hex_name, NULL, 0);
delete (hex_name); /* no longer needed */
if (!path_name) return SANE_STATUS_NO_MEM;
log_data ("%s", path_name);
doc = xmlReadFile (path_name, NULL,
XML_PARSE_NOERROR | XML_PARSE_NOWARNING);
delete (path_name); /* no longer needed */
if (doc)
{
_model_info_merge_data (self, xmlDocGetRootElement (doc));
xmlFreeDoc (doc);
}
else
{
xmlErrorPtr p = xmlGetLastError ();
if (p) err_minor ("%s", p->message);
}
return SANE_STATUS_GOOD;
}
static void
_model_info_merge_data (_model_info_t *self, xmlNodePtr node)
{
/* Define the XML element tags that we are able to merge.
*/
const xmlChar *device = (xmlChar *) "device";
const xmlChar *profiles = (xmlChar *) "profile-set";
const xmlChar *commands = (xmlChar *) "command-set";
const xmlChar *capabilities = (xmlChar *) "capabilities";
const xmlChar *cap_option = (xmlChar *) "option";
const xmlChar *cap_mode = (xmlChar *) "mode";
char *tmp;
require (self);
if (!node) return;
node = node->xmlChildrenNode;
while (node)
{
if (!xmlIsBlankNode (node))
{
if (0 == xmlStrcmp (node->name, device))
{
char *tmp = NULL;
tmp = parseDevices (node, MODEL_OVERSEAS);
if (tmp) self->overseas = tmp;
tmp = parseDevices (node, MODEL_JAPAN);
if (tmp) self->japan = tmp;
}
else if (0 == xmlStrcmp (node->name, profiles))
{
EpsonScanHard profile = parseProfiles (node);
if (profile) self->profile = profile;
}
else if (0 == xmlStrcmp (node->name, commands))
{
scan_command_t *command = parseCommands_set (node);
if (command) self->command = command;
}
else if (0 == xmlStrcmp (node->name, capabilities))
{
tmp = (char *)xmlGetProp(node, (const xmlChar *) cap_mode);
if(strcmp_c(tmp, "duplex") == 0){
capability_data_t *capability = parseCapabilities (node);
if (capability) {
self->adf_duplex = capability;
self->adf_duplex->option = (char *)xmlGetProp(node, (const xmlChar *) cap_option);
self->adf_duplex->mode = tmp;
}
}else {
capability_data_t *capability = parseCapabilities (node);
if (capability) {
self->dfault = capability;
self->dfault->option = (char *)xmlGetProp(node, (const xmlChar *) cap_option);
self->dfault->mode = tmp;
}
}
}
}
node = node->next;
}
self->from_file = true;
}
/*! \brief Customises a single command
*
* \return \c true if \a cmd was modified, \c false otherwise
*/
static bool
_model_info_set_cmd (const _model_info_t *self, unsigned char *cmd,
_command_id_t id)
{
unsigned char cmd_ = ILLEGAL_CMD;
require (self && cmd);
if (FEED == id) cmd_ = self->command->feed;
if (FOCUS == id) cmd_ = self->command->set_focus_position;
if (EJECT == id) cmd_ = self->command->eject;
if (ILLEGAL_CMD != cmd_)
{
*cmd = cmd_;
return true;
}
return false;
}
bool
model_info_has_lock_commands (const void *self)
{
_model_info_t *self_ = NULL;
require (self);
self_ = (_model_info_t *) self;
return (self_->command->lock && self_->command->unlock);
}
scan_area_t
model_info_max_scan_area(const void *self, const char *option, const char *mode)
{
_model_info_t *self_ = NULL;
scan_area_t scan_area;
require (self);
require (option);
scan_area.width = SANE_FIX(-1);
scan_area.height = SANE_FIX(-1);
self_ = (_model_info_t *) self;
if(strcmp(option, "adf") == 0 && strcmp_c(mode, "duplex") == 0){
if(self_->adf_duplex){
scan_area.width = SANE_FIX (self_->adf_duplex->width * MM_PER_INCH / self_->adf_duplex->base);
scan_area.height = SANE_FIX (self_->adf_duplex->height * MM_PER_INCH / self_->adf_duplex->base);
}
}else {
if(self_->dfault){
scan_area.width = SANE_FIX (self_->dfault->width * MM_PER_INCH / self_->dfault->base);
scan_area.height = SANE_FIX (self_->dfault->height * MM_PER_INCH / self_->dfault->base);
}
}
return scan_area;
}