diff options
Diffstat (limited to 'backend/model-info.c')
-rw-r--r-- | backend/model-info.c | 707 |
1 files changed, 707 insertions, 0 deletions
diff --git a/backend/model-info.c b/backend/model-info.c new file mode 100644 index 0000000..5423483 --- /dev/null +++ b/backend/model-info.c @@ -0,0 +1,707 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + * + * 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 <config.h> +#endif + +#include "model-info.h" + +#include <stdlib.h> +#include <time.h> + +#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; +} |