*
* Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
* See LICENSE for license terms and conditions
*
* Device management
*/
#include "airscan.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
*/
#define DEVICE_HTTP_TIMEOUT_DEVCAPS 20000
#define DEVICE_HTTP_TIMEOUT_PRECHECK 20000
#define DEVICE_HTTP_TIMEOUT_SCAN 30000
#define DEVICE_HTTP_TIMEOUT_LOAD -1
#define DEVICE_HTTP_TIMEOUT_CHECK 20000
#define DEVICE_HTTP_TIMEOUT_CLEANUP 30000
#define DEVICE_HTTP_TIMEOUT_CANCEL 30000
* in a moment of cancel (if any)
*/
#define DEVICE_HTTP_TIMEOUT_CANCELED_OP 10000
*/
enum {
DEVICE_SCANNING = (1 << 0),
final sane_read() */
DEVICE_READING = (1 << 1)
};
*
* OPENED
* |
* V
* PROBING->PROBING_FAILED--------------------------------
* | |
* V |
* -->IDLE |
* | | submit PROTO_OP_SCAN |
* | V |
* | SCANNING---------- |
* | | | async cancel request received, |
* | | | dev->stm_cancel_event signalled |
* | | V |
* | | CANCEL_REQ |
* | | | dev->stm_cancel_event callback |
* | | | |
* | | +------ |
* | | reached | | PROTO_OP_SCAN still pending |
* | | CLEANUP | V |
* | |<----------------CANCEL_DELAYED |
* | | | | | PROTO_OP_SCAN failed |
* | | V V ----------------- |
* | | CANCEL_SENT | |
* | | job | | cancel request | |
* | | finished | | finished | |
* | | V V | |
* | | CANCEL_JOB_DONE CANCEL_REQ_DONE | |
* | | cancel req | | job | |
* | | finished | | finished | |
* | V | | | |
* | CLEANUP | | | |
* | | | | | |
* | V V V V |
* ---DONE<-------------------------------------- |
* | |
* V |
* CLOSED<------------------------------------------------
*/
typedef enum {
DEVICE_STM_OPENED,
DEVICE_STM_PROBING,
DEVICE_STM_PROBING_FAILED,
DEVICE_STM_IDLE,
DEVICE_STM_SCANNING,
DEVICE_STM_CANCEL_REQ,
DEVICE_STM_CANCEL_DELAYED,
DEVICE_STM_CANCEL_SENT,
DEVICE_STM_CANCEL_JOB_DONE,
DEVICE_STM_CANCEL_REQ_DONE,
DEVICE_STM_CLEANUP,
DEVICE_STM_DONE,
DEVICE_STM_CLOSED
} DEVICE_STM_STATE;
*/
struct device {
zeroconf_devinfo *devinfo;
log_ctx *log;
unsigned int flags;
devopt opt;
int checking_http_status;
DEVICE_STM_STATE stm_state;
pthread_cond_t stm_cond;
eloop_event *stm_cancel_event;
http_query *stm_cancel_query;
bool stm_cancel_sent;
eloop_timer *stm_timer;
struct timespec stm_last_fail_time;
proto_ctx proto_ctx;
zeroconf_endpoint *endpoint_current;
SANE_Status job_status;
SANE_Word job_skip_x;
SANE_Word job_skip_y;
image_decoder *decoders[NUM_ID_FORMAT];
SANE_Bool read_non_blocking;
pollable *read_pollable;
block */
http_data_queue *read_queue;
http_data *read_image;
SANE_Byte *read_line_buf;
SANE_Int read_line_num;
SANE_Int read_line_end;
no more lines left in image */
SANE_Int read_line_real_wid;
SANE_Int read_line_off;
SANE_Int read_skip_bytes;
beginning */
bool read_24_to_8;
filter *read_filters;
};
*/
static device **device_table;
*/
static device*
device_find_by_ident (const char *ident);
static void
device_http_cancel (device *dev);
static void
device_http_onerror (void *ptr, error err);
static void
device_proto_set (device *dev, ID_PROTO proto);
static void
device_scanner_capabilities_callback (void *ptr, http_query *q);
static void
device_probe_endpoint (device *dev, zeroconf_endpoint *endpoint);
static void
device_job_set_status (device *dev, SANE_Status status);
static inline DEVICE_STM_STATE
device_stm_state_get (device *dev);
static void
device_stm_state_set (device *dev, DEVICE_STM_STATE state);
static bool
device_stm_cancel_perform (device *dev, SANE_Status status);
static void
device_stm_op_callback (void *ptr, http_query *q);
static void
device_stm_cancel_event_callback (void *data);
static void
device_read_filters_setup (device *dev);
static void
device_read_filters_cleanup (device *dev);
static void
device_management_start_stop (bool start);
*
* May fail. At this case, NULL will be returned and status will be set
*/
static device*
device_new (zeroconf_devinfo *devinfo)
{
device *dev;
int i;
dev = mem_new(device, 1);
dev->devinfo = devinfo;
dev->log = log_ctx_new(dev->devinfo->name, NULL);
log_debug(dev->log, "device created");
dev->proto_ctx.log = dev->log;
dev->proto_ctx.devinfo = dev->devinfo;
dev->proto_ctx.devcaps = &dev->opt.caps;
devopt_init(&dev->opt);
dev->proto_ctx.http = http_client_new(dev->log, dev);
pthread_cond_init(&dev->stm_cond, NULL);
dev->read_pollable = pollable_new();
dev->read_queue = http_data_queue_new();
*
* Note, unfortunately, some devices lies in their supported
* image formats list and may return image in different format,
* that requested. See, for example, #281 for details
*
* So we must instantiate all supported decoders and dynamically
* choose between them, based on the actual image content
*
* The previous approach, when we instantiated only decoders
* that we are about to use, doesn't work anymore
*/
image_decoder_create_all(dev->decoders);
for (i = 0; i < NUM_ID_FORMAT; i ++) {
if (dev->decoders[i] != NULL) {
log_debug(dev->log, "added image decoder: \"%s\"",
id_format_short_name(i));
}
}
device_table = ptr_array_append(device_table, dev);
return dev;
}
*/
static void
device_free (device *dev, const char *log_msg)
{
log_debug(dev->log, "removed from device table");
ptr_array_del(device_table, ptr_array_find(device_table, dev));
device_http_cancel(dev);
if (dev->stm_cancel_event != NULL) {
eloop_event_free(dev->stm_cancel_event);
}
if (dev->stm_timer != NULL) {
eloop_timer_cancel(dev->stm_timer);
}
device_proto_set(dev, ID_PROTO_UNKNOWN);
devopt_cleanup(&dev->opt);
http_client_free(dev->proto_ctx.http);
http_uri_free(dev->proto_ctx.base_uri);
http_uri_free(dev->proto_ctx.base_uri_nozone);
mem_free((char*) dev->proto_ctx.location);
pthread_cond_destroy(&dev->stm_cond);
image_decoder_free_all(dev->decoders);
http_data_queue_free(dev->read_queue);
pollable_free(dev->read_pollable);
device_read_filters_cleanup(dev);
log_debug(dev->log, "device destroyed");
if (log_msg != NULL) {
log_debug(dev->log, "%s", log_msg);
}
log_ctx_free(dev->log);
zeroconf_devinfo_free(dev->devinfo);
mem_free(dev);
}
*/
static void
device_start_probing (void *data)
{
device *dev = data;
device_probe_endpoint(dev, dev->devinfo->endpoints);
}
*/
static SANE_Status
device_io_start (device *dev)
{
dev->stm_cancel_event = eloop_event_new(device_stm_cancel_event_callback, dev);
if (dev->stm_cancel_event == NULL) {
return SANE_STATUS_NO_MEM;
}
device_stm_state_set(dev, DEVICE_STM_PROBING);
eloop_call(device_start_probing, dev);
return SANE_STATUS_GOOD;
}
*/
static device*
device_find_by_ident (const char *ident)
{
size_t i, len = mem_len(device_table);
for (i = 0; i < len; i ++) {
device *dev = device_table[i];
if (!strcmp(dev->devinfo->ident, ident)) {
return dev;
}
}
return NULL;
}
*/
static void
device_table_purge (void)
{
while (mem_len(device_table) > 0) {
device_free(device_table[0], NULL);
}
}
*/
static void
device_proto_set (device *dev, ID_PROTO proto)
{
if (dev->proto_ctx.proto != NULL) {
log_debug(dev->log, "closed protocol \"%s\"",
dev->proto_ctx.proto->name);
dev->proto_ctx.proto->free(dev->proto_ctx.proto);
dev->proto_ctx.proto = NULL;
}
if (proto != ID_PROTO_UNKNOWN) {
dev->proto_ctx.proto = proto_handler_new(proto);
log_assert(dev->log, dev->proto_ctx.proto != NULL);
log_debug(dev->log, "using protocol \"%s\"",
dev->proto_ctx.proto->name);
}
}
*/
static void
device_proto_set_base_uri (device *dev, http_uri *uri)
{
http_uri_free(dev->proto_ctx.base_uri);
dev->proto_ctx.base_uri = uri;
http_uri_free(dev->proto_ctx.base_uri_nozone);
dev->proto_ctx.base_uri_nozone = http_uri_clone(uri);
http_uri_strip_zone_suffux(dev->proto_ctx.base_uri_nozone);
}
*/
static void
device_proto_devcaps_submit (device *dev, void (*callback) (void*, http_query*))
{
http_query *q;
q = dev->proto_ctx.proto->devcaps_query(&dev->proto_ctx);
http_query_timeout(q, DEVICE_HTTP_TIMEOUT_DEVCAPS);
http_query_submit(q, callback);
dev->proto_ctx.query = q;
}
*/
static error
device_proto_devcaps_decode (device *dev, devcaps *caps)
{
return dev->proto_ctx.proto->devcaps_decode(&dev->proto_ctx, caps);
}
*/
static void
device_proto_op_onrxhdr (void *p, http_query *q)
{
device *dev = p;
if (dev->proto_ctx.op == PROTO_OP_LOAD && !dev->stm_cancel_sent) {
http_query_timeout(q, -1);
}
}
*/
static void
device_proto_op_submit (device *dev, PROTO_OP op,
void (*callback) (void*, http_query*))
{
http_query *(*func) (const proto_ctx *ctx) = NULL;
int timeout = -1;
http_query *q;
switch (op) {
case PROTO_OP_NONE: log_internal_error(dev->log); break;
case PROTO_OP_FINISH: log_internal_error(dev->log); break;
case PROTO_OP_PRECHECK:
func = dev->proto_ctx.proto->precheck_query;
timeout = DEVICE_HTTP_TIMEOUT_PRECHECK;
break;
case PROTO_OP_SCAN:
func = dev->proto_ctx.proto->scan_query;
timeout = DEVICE_HTTP_TIMEOUT_SCAN;
break;
case PROTO_OP_LOAD:
func = dev->proto_ctx.proto->load_query;
timeout = DEVICE_HTTP_TIMEOUT_LOAD;
break;
case PROTO_OP_CHECK:
func = dev->proto_ctx.proto->status_query;
timeout = DEVICE_HTTP_TIMEOUT_CHECK;
break;
case PROTO_OP_CLEANUP:
func = dev->proto_ctx.proto->cleanup_query;
timeout = DEVICE_HTTP_TIMEOUT_CLEANUP;
break;
}
log_assert(dev->log, func != NULL);
log_debug(dev->log, "%s: submitting: attempt=%d",
proto_op_name(op), dev->proto_ctx.failed_attempt);
dev->proto_ctx.op = op;
q = func(&dev->proto_ctx);
http_query_timeout(q, timeout);
if (op == PROTO_OP_LOAD) {
http_query_onrxhdr(q, device_proto_op_onrxhdr);
}
http_query_submit(q, callback);
dev->proto_ctx.query = q;
}
*/
static proto_result
device_proto_dummy_decode (const proto_ctx *ctx)
{
proto_result result = {0};
(void) ctx;
result.next = PROTO_OP_FINISH;
return result;
}
*/
static proto_result
device_proto_op_decode (device *dev, PROTO_OP op)
{
proto_result (*func) (const proto_ctx *ctx) = NULL;
proto_result result;
switch (op) {
case PROTO_OP_NONE: log_internal_error(dev->log); break;
case PROTO_OP_PRECHECK:func = dev->proto_ctx.proto->precheck_decode; break;
case PROTO_OP_SCAN: func = dev->proto_ctx.proto->scan_decode; break;
case PROTO_OP_LOAD: func = dev->proto_ctx.proto->load_decode; break;
case PROTO_OP_CHECK: func = dev->proto_ctx.proto->status_decode; break;
case PROTO_OP_CLEANUP: func = device_proto_dummy_decode; break;
case PROTO_OP_FINISH: log_internal_error(dev->log); break;
}
log_assert(dev->log, func != NULL);
log_debug(dev->log, "%s: decoding", proto_op_name(op));
result = func(&dev->proto_ctx);
log_debug(dev->log, "%s: decoded: status=\"%s\" next=%s delay=%d",
proto_op_name(op),
sane_strstatus(result.status),
proto_op_name(result.next),
result.delay);
if (result.next == PROTO_OP_CHECK) {
int http_status = http_query_status(dev->proto_ctx.query);
dev->proto_ctx.failed_op = op;
dev->proto_ctx.failed_http_status = http_status;
}
if (op == PROTO_OP_CHECK) {
dev->proto_ctx.failed_attempt ++;
}
return result;
}
*/
static void
device_http_cancel (device *dev)
{
http_client_cancel(dev->proto_ctx.http);
if (dev->stm_timer != NULL) {
eloop_timer_cancel(dev->stm_timer);
dev->stm_timer = NULL;
}
}
*/
static void
device_http_onerror (void *ptr, error err)
{
device *dev = ptr;
SANE_Status status;
status = err == ERROR_ENOMEM ? SANE_STATUS_NO_MEM : SANE_STATUS_IO_ERROR;
log_debug(dev->log, "cancelling job due to error: %s", ESTRING(err));
if (!device_stm_cancel_perform(dev, status)) {
device_stm_state_set(dev, DEVICE_STM_DONE);
} else {
* completion
*/
device_stm_state_set(dev, DEVICE_STM_CANCEL_JOB_DONE);
}
}
*/
static void
device_probe_endpoint (device *dev, zeroconf_endpoint *endpoint)
{
log_assert(dev->log, endpoint->proto != ID_PROTO_UNKNOWN);
if (dev->endpoint_current == NULL ||
dev->endpoint_current->proto != endpoint->proto) {
device_proto_set(dev, endpoint->proto);
}
dev->endpoint_current = endpoint;
device_proto_set_base_uri(dev, http_uri_clone(endpoint->uri));
device_proto_devcaps_submit (dev, device_scanner_capabilities_callback);
}
*/
static void
device_scanner_capabilities_callback (void *ptr, http_query *q)
{
error err = NULL;
device *dev = ptr;
err = http_query_error(q);
if (err != NULL) {
err = eloop_eprintf("scanner capabilities query: %s", ESTRING(err));
goto DONE;
}
err = device_proto_devcaps_decode (dev, &dev->opt.caps);
if (err != NULL) {
err = eloop_eprintf("scanner capabilities: %s", err);
goto DONE;
}
devcaps_dump(dev->log, &dev->opt.caps, true);
devopt_set_defaults(&dev->opt);
if (!http_uri_equal(http_query_uri(q), http_query_real_uri(q))) {
const char *uri_str = http_uri_str(http_query_uri(q));
const char *real_uri_str = http_uri_str(http_query_real_uri(q));
const char *base_str = http_uri_str(dev->proto_ctx.base_uri);
if (str_has_prefix(uri_str, base_str)) {
const char *tail = uri_str + strlen(base_str);
if (str_has_suffix(real_uri_str, tail)) {
size_t l = strlen(real_uri_str) - strlen(tail);
char *new_uri_str = alloca(l + 1);
http_uri *new_uri;
memcpy(new_uri_str, real_uri_str, l);
new_uri_str[l] = '\0';
log_debug(dev->log, "endpoint URI changed due to redirection:");
log_debug(dev->log, " old URL: %s", base_str);
log_debug(dev->log, " new URL: %s", new_uri_str);
new_uri = http_uri_new(new_uri_str, true);
log_assert(dev->log, new_uri != NULL);
device_proto_set_base_uri(dev, new_uri);
}
}
}
DONE:
if (err != NULL) {
log_debug(dev->log, ESTRING(err));
if (dev->endpoint_current != NULL &&
dev->endpoint_current->next != NULL) {
device_probe_endpoint(dev, dev->endpoint_current->next);
} else {
device_stm_state_set(dev, DEVICE_STM_PROBING_FAILED);
}
} else {
device_stm_state_set(dev, DEVICE_STM_IDLE);
http_client_onerror(dev->proto_ctx.http, device_http_onerror);
}
}
*/
static const char*
device_stm_state_name (DEVICE_STM_STATE state)
{
switch (state) {
case DEVICE_STM_OPENED: return "DEVICE_STM_OPENED";
case DEVICE_STM_PROBING: return "DEVICE_STM_PROBING";
case DEVICE_STM_PROBING_FAILED: return "DEVICE_STM_PROBING_FAILED";
case DEVICE_STM_IDLE: return "DEVICE_STM_IDLE";
case DEVICE_STM_SCANNING: return "DEVICE_STM_SCANNING";
case DEVICE_STM_CANCEL_REQ: return "DEVICE_STM_CANCEL_REQ";
case DEVICE_STM_CANCEL_DELAYED: return "DEVICE_STM_CANCEL_DELAYED";
case DEVICE_STM_CANCEL_SENT: return "DEVICE_STM_CANCEL_SENT";
case DEVICE_STM_CANCEL_JOB_DONE: return "DEVICE_STM_CANCEL_JOB_DONE";
case DEVICE_STM_CANCEL_REQ_DONE: return "DEVICE_STM_CANCEL_REQ_DONE";
case DEVICE_STM_CLEANUP: return "DEVICE_STM_CLEANUP";
case DEVICE_STM_DONE: return "DEVICE_STM_DONE";
case DEVICE_STM_CLOSED: return "DEVICE_STM_CLOSED";
}
return NULL;
}
*/
static inline DEVICE_STM_STATE
device_stm_state_get (device *dev)
{
return __atomic_load_n(&dev->stm_state, __ATOMIC_SEQ_CST);
}
*/
static bool
device_stm_state_working (device *dev)
{
DEVICE_STM_STATE state = device_stm_state_get(dev);
return state > DEVICE_STM_IDLE && state < DEVICE_STM_DONE;
}
*/
static void
device_stm_state_set (device *dev, DEVICE_STM_STATE state)
{
DEVICE_STM_STATE old_state = device_stm_state_get(dev);
if (old_state != state) {
log_debug(dev->log, "%s->%s",
device_stm_state_name(old_state), device_stm_state_name(state));
__atomic_store_n(&dev->stm_state, state, __ATOMIC_SEQ_CST);
pthread_cond_broadcast(&dev->stm_cond);
if (!device_stm_state_working(dev)) {
pollable_signal(dev->read_pollable);
}
}
}
*/
static void
device_stm_cancel_callback (void *ptr, http_query *q)
{
device *dev = ptr;
(void) q;
dev->stm_cancel_query = NULL;
if (device_stm_state_get(dev) == DEVICE_STM_CANCEL_JOB_DONE) {
device_stm_state_set(dev, DEVICE_STM_DONE);
} else {
device_stm_state_set(dev, DEVICE_STM_CANCEL_REQ_DONE);
}
}
*/
static bool
device_stm_cancel_perform (device *dev, SANE_Status status)
{
proto_ctx *ctx = &dev->proto_ctx;
device_job_set_status(dev, status);
if (ctx->location != NULL && !dev->stm_cancel_sent) {
if (ctx->params.src == ID_SOURCE_PLATEN &&
ctx->images_received > 0) {
* and simple wait until job is done
*
* Otherwise Xerox VersaLink B405 remains busy for
* a quite long time without any need
*/
log_debug(dev->log, "cancel skipped as job is almost done");
return false;
} else {
*/
device_stm_state_set(dev, DEVICE_STM_CANCEL_SENT);
log_assert(dev->log, dev->stm_cancel_query == NULL);
dev->stm_cancel_query = ctx->proto->cancel_query(ctx);
http_query_onerror(dev->stm_cancel_query, NULL);
http_query_timeout(dev->stm_cancel_query,
DEVICE_HTTP_TIMEOUT_CANCEL);
http_client_timeout(dev->proto_ctx.http,
DEVICE_HTTP_TIMEOUT_CANCELED_OP);
http_query_submit(dev->stm_cancel_query, device_stm_cancel_callback);
dev->stm_cancel_sent = true;
}
return true;
}
return false;
}
*/
static void
device_stm_cancel_event_callback (void *data)
{
device *dev = data;
log_debug(dev->log, "cancel processing started");
if (!device_stm_cancel_perform(dev, SANE_STATUS_CANCELLED)) {
device_stm_state_set(dev, DEVICE_STM_CANCEL_DELAYED);
}
}
*
* Note, reason must be NULL, if cancel requested from the signal handler
*/
static void
device_stm_cancel_req (device *dev, const char *reason)
{
DEVICE_STM_STATE expected = DEVICE_STM_SCANNING;
bool ok = __atomic_compare_exchange_n(&dev->stm_state, &expected,
DEVICE_STM_CANCEL_REQ, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
if (ok) {
if (reason != NULL) {
log_debug(dev->log, "cancel requested: %s", reason);
}
eloop_event_trigger(dev->stm_cancel_event);
}
}
*/
static void
device_stm_timer_callback (void *data)
{
device *dev = data;
dev->stm_timer = NULL;
device_proto_op_submit(dev, dev->proto_ctx.op, device_stm_op_callback);
}
*/
static void
device_stm_op_callback (void *ptr, http_query *q)
{
device *dev = ptr;
proto_result result = device_proto_op_decode(dev, dev->proto_ctx.op);
(void) q;
if (result.err != NULL) {
log_debug(dev->log, "%s", ESTRING(result.err));
}
if (dev->proto_ctx.op == PROTO_OP_SCAN) {
if (result.data.location != NULL) {
mem_free((char*) dev->proto_ctx.location);
dev->proto_ctx.location = result.data.location;
dev->proto_ctx.failed_attempt = 0;
pthread_cond_broadcast(&dev->stm_cond);
}
} else if (dev->proto_ctx.op == PROTO_OP_LOAD) {
if (result.data.image != NULL) {
http_data_queue_push(dev->read_queue, result.data.image);
dev->proto_ctx.images_received ++;
pollable_signal(dev->read_pollable);
dev->proto_ctx.failed_attempt = 0;
pthread_cond_broadcast(&dev->stm_cond);
}
}
device_job_set_status(dev, result.status);
* current operation is CHECK, FINISH the job
*/
if (dev->stm_cancel_sent) {
if (result.next == PROTO_OP_CLEANUP ||
dev->proto_ctx.op == PROTO_OP_CHECK) {
result.next = PROTO_OP_FINISH;
}
}
if (result.next == PROTO_OP_FINISH) {
if (dev->proto_ctx.images_received == 0) {
* yet set, use SANE_STATUS_IO_ERROR as default
* error code
*/
device_job_set_status(dev, SANE_STATUS_IO_ERROR);
}
if (device_stm_state_get(dev) == DEVICE_STM_CANCEL_SENT) {
device_stm_state_set(dev, DEVICE_STM_CANCEL_JOB_DONE);
} else {
device_stm_state_set(dev, DEVICE_STM_DONE);
}
return;
}
if (result.next == PROTO_OP_CLEANUP) {
device_stm_state_set(dev, DEVICE_STM_CLEANUP);
}
if (device_stm_state_get(dev) == DEVICE_STM_CANCEL_DELAYED) {
if (!device_stm_cancel_perform(dev, SANE_STATUS_CANCELLED)) {
* state
*/
device_stm_state_set(dev, DEVICE_STM_DONE);
return;
}
}
if (result.delay != 0) {
log_assert(dev->log, dev->stm_timer == NULL);
dev->stm_timer = eloop_timer_new(result.delay,
device_stm_timer_callback, dev);
dev->proto_ctx.op = result.next;
return;
}
device_proto_op_submit(dev, result.next, device_stm_op_callback);
}
*/
typedef struct {
SANE_Word off;
SANE_Word len;
SANE_Word skip;
actual resolution */
}
device_geom;
* Computing geometrical scan parameters
*
* Input:
* tl, br - top-left, bottom-rights X/Y, in mm
* minlen, maxlen - device-defined min and max width or height,
* in pixels, assuming 300 dpi
* res - scan resolution
*
* Output:
* Filled device_geom structure
*
* Problem description.
*
* First of all, we use 3 different units to deal with geometrical
* parameters:
* 1) we communicate with frontend in millimeters
* 2) we communicate with scanner in pixels, assuming
* protocol-specific DPI (defined by devcaps::units)
* 3) when we deal with image, sizes are in pixels in real resolution
*
* Second, scanner returns minimal and maximal window size, but
* to simplify frontend's life, we pretend there is no such thing,
* as a minimal width or height, otherwise TL and BR ranges become
* dependent from each other. Instead, we always request image from
* scanner not smaller that scanner's minimum, and clip excessive
* image parts if required.
*
* This all makes things non-trivial. This function handles
* this complexity
*/
static device_geom
device_geom_compute (SANE_Fixed tl, SANE_Fixed br,
SANE_Word minlen, SANE_Word maxlen, SANE_Word res, SANE_Word units)
{
device_geom geom;
geom.off = math_mm2px_res(tl, units);
geom.len = math_mm2px_res(br - tl, units);
geom.skip = 0;
minlen = math_max(minlen, 1);
geom.len = math_bound(geom.len, minlen, maxlen);
if (geom.off + geom.len > maxlen) {
geom.skip = geom.off + geom.len - maxlen;
geom.off -= geom.skip;
geom.skip = math_muldiv(geom.skip, res, units);
}
return geom;
}
*/
static ID_FORMAT
device_choose_format (device *dev, devcaps_source *src)
{
unsigned int formats = src->formats & DEVCAPS_FORMATS_SUPPORTED;
size_t i;
static const ID_FORMAT use[] = {
ID_FORMAT_PNG,
ID_FORMAT_JPEG,
ID_FORMAT_TIFF,
ID_FORMAT_BMP
};
for (i = 0; i < sizeof(use)/sizeof(use[0]); i ++) {
ID_FORMAT fmt = use[i];
if ((formats & (1 << fmt)) != 0) {
return fmt;
}
}
log_internal_error(dev->log);
return ID_FORMAT_UNKNOWN;
}
*/
static void
device_stm_start_scan (device *dev)
{
device_geom geom_x, geom_y;
proto_ctx *ctx = &dev->proto_ctx;
proto_scan_params *params = &ctx->params;
devcaps_source *src = dev->opt.caps.src[dev->opt.src];
SANE_Word x_resolution = dev->opt.resolution;
SANE_Word y_resolution = dev->opt.resolution;
char buf[64];
geom_x = device_geom_compute(dev->opt.tl_x, dev->opt.br_x,
src->min_wid_px, src->max_wid_px, x_resolution, dev->opt.caps.units);
geom_y = device_geom_compute(dev->opt.tl_y, dev->opt.br_y,
src->min_hei_px, src->max_hei_px, y_resolution, dev->opt.caps.units);
dev->job_skip_x = geom_x.skip;
dev->job_skip_y = geom_y.skip;
memset(params, 0, sizeof(*params));
params->x_off = geom_x.off;
params->y_off = geom_y.off;
params->wid = geom_x.len;
params->hei = geom_y.len;
params->x_res = x_resolution;
params->y_res = y_resolution;
params->src = dev->opt.src;
params->colormode = dev->opt.colormode_real;
params->scanintent = dev->opt.scanintent;
params->format = device_choose_format(dev, src);
log_trace(dev->log, "==============================");
log_trace(dev->log, "Starting scan, using the following parameters:");
log_trace(dev->log, " source: %s",
id_source_sane_name(params->src));
log_trace(dev->log, " colormode_emul: %s",
id_colormode_sane_name(dev->opt.colormode_emul));
log_trace(dev->log, " colormode_real: %s",
id_colormode_sane_name(params->colormode));
log_trace(dev->log, " scanintent: %s",
id_scanintent_sane_name(params->scanintent));
log_trace(dev->log, " tl_x: %s mm",
math_fmt_mm(dev->opt.tl_x, buf));
log_trace(dev->log, " tl_y: %s mm",
math_fmt_mm(dev->opt.tl_y, buf));
log_trace(dev->log, " br_x: %s mm",
math_fmt_mm(dev->opt.br_x, buf));
log_trace(dev->log, " br_y: %s mm",
math_fmt_mm(dev->opt.br_y, buf));
log_trace(dev->log, " image size: %dx%d", params->wid, params->hei);
log_trace(dev->log, " image X offset: %d", params->x_off);
log_trace(dev->log, " image Y offset: %d", params->y_off);
log_trace(dev->log, " x_resolution: %d", params->x_res);
log_trace(dev->log, " y_resolution: %d", params->y_res);
log_trace(dev->log, " format: %s",
id_format_short_name(params->format));
log_trace(dev->log, "");
device_stm_state_set(dev, DEVICE_STM_SCANNING);
if (dev->proto_ctx.proto->precheck_query != NULL) {
device_proto_op_submit(dev, PROTO_OP_PRECHECK, device_stm_op_callback);
} else {
device_proto_op_submit(dev, PROTO_OP_SCAN, device_stm_op_callback);
}
}
*/
static void
device_stm_wait_while_working (device *dev)
{
while (device_stm_state_working(dev)) {
eloop_cond_wait(&dev->stm_cond);
}
}
*/
static void
device_stm_cancel_wait (device *dev, const char *reason)
{
device_stm_cancel_req(dev, reason);
device_stm_wait_while_working(dev);
}
*/
static void
device_job_set_status (device *dev, SANE_Status status)
{
*/
switch (status) {
case SANE_STATUS_GOOD:
return;
case SANE_STATUS_CANCELLED:
break;
default:
*/
if (dev->job_status != SANE_STATUS_GOOD) {
return;
}
}
*/
if (status != dev->job_status) {
log_debug(dev->log, "JOB status=%s", sane_strstatus(status));
dev->job_status = status;
if (status == SANE_STATUS_CANCELLED) {
http_data_queue_purge(dev->read_queue);
}
}
}
*/
log_ctx*
device_log_ctx (device *dev)
{
return dev ? dev->log : NULL;
}
*/
device*
device_open (const char *ident, SANE_Status *status)
{
device *dev = NULL;
zeroconf_devinfo *devinfo;
*status = SANE_STATUS_GOOD;
if (ident == NULL || *ident == '\0') {
log_debug(NULL, "device_open: invalid name");
*status = SANE_STATUS_INVAL;
return NULL;
}
dev = device_find_by_ident(ident);
if (dev) {
*status = SANE_STATUS_DEVICE_BUSY;
return NULL;
}
devinfo = zeroconf_devinfo_lookup(ident);
if (devinfo == NULL) {
log_debug(NULL, "device_open(%s): device not found", ident);
*status = SANE_STATUS_INVAL;
return NULL;
}
dev = device_new(devinfo);
*status = device_io_start(dev);
if (*status != SANE_STATUS_GOOD) {
device_free(dev, NULL);
return NULL;
}
while (device_stm_state_get(dev) == DEVICE_STM_PROBING) {
eloop_cond_wait(&dev->stm_cond);
}
if (device_stm_state_get(dev) == DEVICE_STM_PROBING_FAILED) {
device_free(dev, NULL);
*status = SANE_STATUS_IO_ERROR;
return NULL;
}
return dev;
}
* If log_msg is not NULL, it is written to the device log as late as possible
*/
void
device_close (device *dev, const char *log_msg)
{
if (device_stm_state_working(dev)) {
device_stm_cancel_wait(dev, "device close");
}
device_stm_state_set(dev, DEVICE_STM_CLOSED);
device_free(dev, log_msg);
}
*/
const SANE_Option_Descriptor*
device_get_option_descriptor (device *dev, SANE_Int option)
{
if (0 <= option && option < NUM_OPTIONS) {
return &dev->opt.desc[option];
}
return NULL;
}
*/
SANE_Status
device_get_option (device *dev, SANE_Int option, void *value)
{
return devopt_get_option(&dev->opt, option, value);
}
*/
SANE_Status
device_set_option (device *dev, SANE_Int option, void *value, SANE_Word *info)
{
SANE_Status status;
if ((dev->flags & DEVICE_SCANNING) != 0) {
log_debug(dev->log, "device_set_option: already scanning");
return SANE_STATUS_INVAL;
}
status = devopt_set_option(&dev->opt, option, value, info);
if (status == SANE_STATUS_GOOD && opt_is_enhancement(option)) {
device_read_filters_setup(dev);
}
return status;
}
*/
SANE_Status
device_get_parameters (device *dev, SANE_Parameters *params)
{
*params = dev->opt.params;
return SANE_STATUS_GOOD;
}
*/
static void
device_start_do (void *data)
{
device *dev = data;
device_stm_start_scan(dev);
}
*/
static SANE_Status
device_start_wait (device *dev)
{
for (;;) {
DEVICE_STM_STATE state = device_stm_state_get(dev);
switch (state) {
case DEVICE_STM_IDLE:
break;
case DEVICE_STM_SCANNING:
if (dev->proto_ctx.location != NULL) {
return SANE_STATUS_GOOD;
}
break;
case DEVICE_STM_DONE:
return dev->job_status;
default:
return SANE_STATUS_GOOD;
}
eloop_cond_wait(&dev->stm_cond);
}
}
*/
static void
device_start_retry_pause (device *dev)
{
struct timespec now;
int64_t pause_us;
clock_gettime(CLOCK_MONOTONIC, &now);
pause_us = (int64_t) (now.tv_sec - dev->stm_last_fail_time.tv_sec) *
1000000;
pause_us += (int64_t) (now.tv_nsec - dev->stm_last_fail_time.tv_nsec) /
1000;
pause_us = (int64_t) (CONFIG_START_RETRY_INTERVAL * 1000) - pause_us;
if (pause_us > 1000) {
log_debug(dev->log, "sane_start() retried too often; pausing for %d ms",
(int) (pause_us / 1000));
eloop_mutex_unlock();
usleep((useconds_t) pause_us);
eloop_mutex_lock();
}
}
*/
static SANE_Status
device_start_new_job (device *dev)
{
SANE_Status status;
device_start_retry_pause(dev);
dev->stm_cancel_sent = false;
dev->job_status = SANE_STATUS_GOOD;
mem_free((char*) dev->proto_ctx.location);
dev->proto_ctx.location = NULL;
dev->proto_ctx.failed_op = PROTO_OP_NONE;
dev->proto_ctx.failed_attempt = 0;
dev->proto_ctx.images_received = 0;
eloop_call(device_start_do, dev);
log_debug(dev->log, "device_start_wait: waiting");
status = device_start_wait(dev);
log_debug(dev->log, "device_start_wait: %s", sane_strstatus(status));
switch (status) {
case SANE_STATUS_GOOD:
case SANE_STATUS_CANCELLED:
memset(&dev->stm_last_fail_time, 0, sizeof(dev->stm_last_fail_time));
break;
default:
clock_gettime(CLOCK_MONOTONIC, &dev->stm_last_fail_time);
}
if (status == SANE_STATUS_GOOD) {
dev->flags |= DEVICE_READING;
} else {
dev->flags &= ~DEVICE_SCANNING;
if (device_stm_state_get(dev) == DEVICE_STM_DONE) {
device_stm_state_set(dev, DEVICE_STM_IDLE);
}
}
return status;
}
*/
SANE_Status
device_start (device *dev)
{
if ((dev->flags & DEVICE_SCANNING) != 0) {
log_debug(dev->log, "device_start: already scanning");
return SANE_STATUS_INVAL;
}
if (dev->opt.params.lines == 0 || dev->opt.params.pixels_per_line == 0) {
log_debug(dev->log, "device_start: invalid scan window");
return SANE_STATUS_INVAL;
}
dev->flags |= DEVICE_SCANNING;
pollable_reset(dev->read_pollable);
dev->read_non_blocking = SANE_FALSE;
if (device_stm_state_get(dev) == DEVICE_STM_IDLE) {
return device_start_new_job(dev);
}
*/
while (device_stm_state_working(dev)
&& http_data_queue_len(dev->read_queue) == 0) {
log_debug(dev->log, "device_start: waiting for background scan job");
eloop_cond_wait(&dev->stm_cond);
}
* decoding the next one
*/
if (http_data_queue_len(dev->read_queue) > 0) {
dev->flags |= DEVICE_READING;
pollable_signal(dev->read_pollable);
return SANE_STATUS_GOOD;
}
*
* If it failed by itself (but not cancelled), return its status now.
* Otherwise, start new job
*/
log_assert (dev->log, device_stm_state_get(dev) == DEVICE_STM_DONE);
device_stm_state_set(dev, DEVICE_STM_IDLE);
if (dev->job_status != SANE_STATUS_GOOD &&
dev->job_status != SANE_STATUS_CANCELLED) {
dev->flags &= ~DEVICE_SCANNING;
return dev->job_status;
}
return device_start_new_job(dev);
}
*/
void
device_cancel (device *dev)
{
* scan "just in case", which kills scan job running in
* background. So ignore cancel request, if from the API
* point of view we are not "scanning" (i.e., not between
* sane_start() and sane_read() completion)
*/
if ((dev->flags & DEVICE_SCANNING) == 0) {
return;
}
device_stm_cancel_req(dev, NULL);
}
*/
SANE_Status
device_set_io_mode (device *dev, SANE_Bool non_blocking)
{
if ((dev->flags & DEVICE_SCANNING) == 0) {
log_debug(dev->log, "device_set_io_mode: not scanning");
return SANE_STATUS_INVAL;
}
dev->read_non_blocking = non_blocking;
return SANE_STATUS_GOOD;
}
*/
SANE_Status
device_get_select_fd (device *dev, SANE_Int *fd)
{
if ((dev->flags & DEVICE_SCANNING) == 0) {
log_debug(dev->log, "device_get_select_fd: not scanning");
return SANE_STATUS_INVAL;
}
*fd = pollable_get_fd(dev->read_pollable);
return SANE_STATUS_GOOD;
}
*/
static void
device_read_filters_setup (device *dev)
{
device_read_filters_cleanup(dev);
dev->read_filters = filter_chain_push_xlat(NULL, &dev->opt);
filter_chain_dump(dev->read_filters, dev->log);
}
*/
static void
device_read_filters_cleanup (device *dev)
{
filter_chain_free(dev->read_filters);
dev->read_filters = NULL;
}
*/
static SANE_Status
device_read_next (device *dev)
{
error err;
size_t line_capacity;
SANE_Parameters params;
image_decoder *decoder;
int wid, hei;
int skip_lines = 0;
dev->read_image = http_data_queue_pull(dev->read_queue);
if (dev->read_image == NULL) {
return SANE_STATUS_EOF;
}
dev->proto_ctx.format_detected =
image_format_detect(dev->read_image->bytes, dev->read_image->size);
if (dev->proto_ctx.format_detected == ID_FORMAT_UNKNOWN) {
err = eloop_eprintf("Can't detect image format");
goto DONE;
}
decoder = dev->decoders[dev->proto_ctx.format_detected];
if (decoder == NULL) {
err = eloop_eprintf("Unsupported image format \"%s\"",
id_format_short_name(dev->proto_ctx.format_detected));
goto DONE;
}
err = image_decoder_begin(decoder,
dev->read_image->bytes, dev->read_image->size);
if (err != NULL) {
goto DONE;
}
image_decoder_get_params(decoder, ¶ms);
log_trace(dev->log, "==============================");
log_trace(dev->log, "Image received with the following parameters:");
log_trace(dev->log, " format: %s",
id_format_short_name(dev->proto_ctx.format_detected));
log_trace(dev->log, " content type: %s", image_content_type(decoder));
log_trace(dev->log, " frame format: %s",
params.format == SANE_FRAME_GRAY ? "Gray" : "RGB" );
log_trace(dev->log, " image size: %dx%d", params.pixels_per_line,
params.lines);
log_trace(dev->log, " color depth: %d", params.depth);
log_trace(dev->log, "");
dev->read_24_to_8 = false;
if (params.format == SANE_FRAME_RGB &&
dev->opt.params.format == SANE_FRAME_GRAY) {
dev->read_24_to_8 = true;
log_trace(dev->log, "resampling: RGB24->Grayscale8");
} else if (params.format != dev->opt.params.format) {
err = ERROR("Unexpected frame format");
goto DONE;
}
wid = params.pixels_per_line;
hei = params.lines;
*
* The following variants are possible:
*
* <------real image size------><--fill-->
* <---skip---><---returned image size--->
* <------------line capacity------------>
*
* <------------real image size------------>
* <---skip---><--returned image size-->
* <-------------line capacity------------->
*
* Real image size is a size of image after decoder.
* Returned image size is a size of image that we
* return to the client
*
* If device for some reasons unable to handle X/Y
* offset in hardware, we need to skip some bytes (horizontally)
* or lines (vertically)
*
* If real image is smaller that expected, we need to
* fill some bytes/lines with 0xff
*
* Line buffer capacity must be big enough to fit
* real image size (we promised it do decoder) and
* returned image size, whatever is large
*/
if (dev->job_skip_x >= wid || dev->job_skip_y >= hei) {
dev->read_line_end = 0;
dev->read_skip_bytes = 0;
dev->read_line_real_wid = 0;
line_capacity = dev->opt.params.bytes_per_line;
} else {
image_window win;
int bpp = dev->opt.params.format == SANE_FRAME_RGB ? 3 : 1;
int returned_size_and_skip;
win.x_off = dev->job_skip_x;
win.y_off = dev->job_skip_y;
win.wid = wid - dev->job_skip_x;
win.hei = hei - dev->job_skip_y;
err = image_decoder_set_window(decoder, &win);
if (err != NULL) {
goto DONE;
}
dev->read_skip_bytes = 0;
if (win.x_off != dev->job_skip_x) {
dev->read_skip_bytes = bpp * (dev->job_skip_x - win.x_off);
}
if (win.y_off != dev->job_skip_y) {
skip_lines = dev->job_skip_y - win.y_off;
}
line_capacity = win.wid;
if (params.format == SANE_FRAME_RGB) {
line_capacity *= 3;
}
returned_size_and_skip = dev->read_skip_bytes +
dev->opt.params.bytes_per_line;
line_capacity = math_max(line_capacity, returned_size_and_skip);
dev->read_line_real_wid = win.wid;
}
dev->read_line_buf = mem_new(SANE_Byte, line_capacity);
memset(dev->read_line_buf, 0xff, line_capacity);
dev->read_line_num = 0;
dev->read_line_off = dev->opt.params.bytes_per_line;
dev->read_line_end = hei - skip_lines;
for (;skip_lines > 0; skip_lines --) {
err = image_decoder_read_line(decoder, dev->read_line_buf);
if (err != NULL) {
goto DONE;
}
}
pollable_signal(dev->read_pollable);
DONE:
if (err != NULL) {
log_debug(dev->log, ESTRING(err));
http_data_unref(dev->read_image);
dev->read_image = NULL;
return SANE_STATUS_IO_ERROR;
}
return SANE_STATUS_GOOD;
}
*/
static void
device_read_24_to_8_resample (device *dev)
{
int i, len = dev->read_line_real_wid;
uint8_t *in = dev->read_line_buf;
uint8_t *out = dev->read_line_buf;
for (i = 0; i < len; i ++) {
*
* 16777216 == 1 << 24
* 16777216 * 0.299 == 5016387.584 ~= 5016387
* 16777216 * 0.587 == 9848225.792 ~= 9848226
* 16777216 * 0.114 == 1912602.624 ~= 1912603
*
* 5016387 + 9848226 + 1912603 == 16777216
*/
unsigned long Y;
Y = 5016387 * (unsigned long) *in ++;
Y += 9848226 * (unsigned long) *in ++;
Y += 1912603 * (unsigned long) *in ++;
*out ++ = (Y + (1 << 23)) >> 24;
}
if (len < dev->opt.params.bytes_per_line) {
memset(dev->read_line_buf + len, 0xff,
dev->opt.params.bytes_per_line - len);
}
}
*
* Note, actual image size, returned by device, may be slightly different
* from an image size, computed according to scan options and requested
* from device. So here we adjust actual image to fit the expected (and
* promised) parameters.
*
* Alternatively, we could make it problem of frontend. But fronends
* expect image parameters to be accurate just after sane_start() returns,
* so at this case sane_start() will have to wait a long time until image
* is fully available. Taking in account that some popular frontends
* (read "xsane") doesn't allow to cancel scanning before sane_start()
* return, it is not good from the user experience perspective.
*/
static SANE_Status
device_read_decode_line (device *dev)
{
const SANE_Int n = dev->read_line_num;
image_decoder *decoder = dev->decoders[dev->proto_ctx.format_detected];
log_assert(dev->log, decoder != NULL);
if (n == dev->opt.params.lines) {
return SANE_STATUS_EOF;
}
if (n >= dev->read_line_end) {
memset(dev->read_line_buf + dev->read_skip_bytes, 0xff,
dev->opt.params.bytes_per_line);
} else {
error err = image_decoder_read_line(decoder, dev->read_line_buf);
if (err != NULL) {
log_debug(dev->log, ESTRING(err));
return SANE_STATUS_IO_ERROR;
}
if (dev->read_24_to_8) {
device_read_24_to_8_resample(dev);
}
}
filter_chain_apply(dev->read_filters,
dev->read_line_buf, dev->opt.params.bytes_per_line);
dev->read_line_off = 0;
dev->read_line_num ++;
return SANE_STATUS_GOOD;
}
*/
SANE_Status
device_read (device *dev, SANE_Byte *data, SANE_Int max_len, SANE_Int *len_out)
{
SANE_Int len = 0;
SANE_Status status = SANE_STATUS_GOOD;
image_decoder *decoder = dev->decoders[dev->proto_ctx.format_detected];
if (len_out != NULL) {
*len_out = 0;
}
log_assert(dev->log, decoder != NULL);
if ((dev->flags & DEVICE_READING) == 0) {
log_debug(dev->log, "device_read: not scanning");
return SANE_STATUS_INVAL;
}
if (len_out == NULL) {
log_debug(dev->log, "device_read: zero output buffer");
return SANE_STATUS_INVAL;
}
if (dev->read_image == NULL) {
while (device_stm_state_working(dev) &&
http_data_queue_empty(dev->read_queue)) {
if (dev->read_non_blocking) {
*len_out = 0;
return SANE_STATUS_GOOD;
}
eloop_cond_wait(&dev->stm_cond);
}
if (dev->job_status == SANE_STATUS_CANCELLED) {
status = SANE_STATUS_CANCELLED;
goto DONE;
}
if (http_data_queue_empty(dev->read_queue)) {
status = dev->job_status;
log_assert(dev->log, status != SANE_STATUS_GOOD);
goto DONE;
}
status = device_read_next(dev);
if (status != SANE_STATUS_GOOD) {
goto DONE;
}
}
for (len = 0; status == SANE_STATUS_GOOD && len < max_len; ) {
if (dev->read_line_off == dev->opt.params.bytes_per_line) {
status = device_read_decode_line(dev);
} else {
SANE_Int sz = math_min(max_len - len,
dev->opt.params.bytes_per_line - dev->read_line_off);
memcpy(data, dev->read_line_buf + dev->read_skip_bytes +
dev->read_line_off, sz);
data += sz;
dev->read_line_off += sz;
len += sz;
}
}
if (status == SANE_STATUS_IO_ERROR) {
device_job_set_status(dev, SANE_STATUS_IO_ERROR);
device_stm_cancel_req(dev, "I/O error");
}
DONE:
if (status == SANE_STATUS_EOF && len > 0) {
status = SANE_STATUS_GOOD;
}
if (status == SANE_STATUS_GOOD) {
*len_out = len;
return SANE_STATUS_GOOD;
}
dev->flags &= ~(DEVICE_SCANNING | DEVICE_READING);
image_decoder_reset(decoder);
if (dev->read_image != NULL) {
http_data_unref(dev->read_image);
dev->read_image = NULL;
}
mem_free(dev->read_line_buf);
dev->read_line_buf = NULL;
if (device_stm_state_get(dev) == DEVICE_STM_DONE &&
(status != SANE_STATUS_EOF || dev->job_status == SANE_STATUS_GOOD)) {
device_stm_state_set(dev, DEVICE_STM_IDLE);
}
return status;
}
*/
SANE_Status
device_management_init (void)
{
device_table = ptr_array_new(device*);
eloop_add_start_stop_callback(device_management_start_stop);
return SANE_STATUS_GOOD;
}
*/
void
device_management_cleanup (void)
{
if (device_table != NULL) {
log_assert(NULL, mem_len(device_table) == 0);
mem_free(device_table);
device_table = NULL;
}
}
*/
static void
device_management_start_stop (bool start)
{
if (!start) {
device_table_purge();
}
}
*/