Files
Alexander Pevzner 44a8743825 HTTP timeout for "short" requests extended from 5s to 20s
Under certain circumstances, when the Kyocera ECOSYS M2040dn is
connected via ipp-usb, it can take up to 12 seconds to respond to the
eSCL/ScannerStatus after a successful scan.

If we allow the scanner this additional time, it continues to operate
correctly. However, with the default timeouts, this delay can cause
scanning to fail.

This issue is difficult to reproduce; it only occurs with XSane when
scanning from the flatbed.

It may also affect other scanners, but so far, no one has reported a
similar issue.

Everything else appears to be functioning correctly on both the
sane-airscan and ipp-usb sides.

I will continue to investigate this problem on the ipp-usb side. For
now, I have increased the HTTP timeouts with some reasonable reserve for
the "short" operations, such as requesting the scanner status and
similar.

This change is safe; it does not impact scanning when everything is
functioning normally, but it may extend the time required for failure
detection.
2024-12-08 13:04:14 +03:00

1902 lines
54 KiB
C

/* AirScan (a.k.a. eSCL) backend for SANE
*
* 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>
/******************** Constants ********************/
/* HTTP timeouts, by operation, in milliseconds
*/
#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
/* HTTP timeout for operation that was pending
* in a moment of cancel (if any)
*/
#define DEVICE_HTTP_TIMEOUT_CANCELED_OP 10000
/******************** Device management ********************/
/* Device flags
*/
enum {
DEVICE_SCANNING = (1 << 0), /* We are between sane_start() and
final sane_read() */
DEVICE_READING = (1 << 1) /* sane_read() can be called */
};
/* Device state diagram
*
* 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;
/* Device descriptor
*/
struct device {
/* Common part */
zeroconf_devinfo *devinfo; /* Device info */
log_ctx *log; /* Logging context */
unsigned int flags; /* Device flags */
devopt opt; /* Device options */
int checking_http_status; /* HTTP status before CHECK_STATUS */
/* State machinery */
DEVICE_STM_STATE stm_state; /* Device state */
pthread_cond_t stm_cond; /* Signalled when state changes */
eloop_event *stm_cancel_event; /* Signalled to initiate cancel */
http_query *stm_cancel_query; /* CANCEL query */
bool stm_cancel_sent; /* Cancel was sent to device */
eloop_timer *stm_timer; /* Delay timer */
struct timespec stm_last_fail_time;/* Last failed sane_start() time */
/* Protocol handling */
proto_ctx proto_ctx; /* Protocol handler context */
/* I/O handling (AVAHI and HTTP) */
zeroconf_endpoint *endpoint_current; /* Current endpoint to probe */
/* Job status */
SANE_Status job_status; /* Job completion status */
SANE_Word job_skip_x; /* How much pixels to skip, */
SANE_Word job_skip_y; /* from left and top */
/* Image decoders */
image_decoder *decoders[NUM_ID_FORMAT]; /* Decoders by format */
/* Read machinery */
SANE_Bool read_non_blocking; /* Non-blocking I/O mode */
pollable *read_pollable; /* Signalled when read won't
block */
http_data_queue *read_queue; /* Queue of received images */
http_data *read_image; /* Current image */
SANE_Byte *read_line_buf; /* Single-line buffer */
SANE_Int read_line_num; /* Current image line 0-based */
SANE_Int read_line_end; /* If read_line_num>read_line_end
no more lines left in image */
SANE_Int read_line_real_wid; /* Real line width */
SANE_Int read_line_off; /* Current offset in the line */
SANE_Int read_skip_bytes; /* How many bytes to skip at line
beginning */
bool read_24_to_8; /* Resample 24 to 8 bits */
filter *read_filters; /* Chain of image filters */
};
/* Static variables
*/
static device **device_table;
/* Forward declarations
*/
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);
/******************** Device table management ********************/
/* Create a device.
*
* 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;
/* Create device */
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();
/* Setup decoders
*
* 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));
}
}
/* Add to the table */
device_table = ptr_array_append(device_table, dev);
return dev;
}
/* Destroy a device
*/
static void
device_free (device *dev, const char *log_msg)
{
/* Remove device from table */
log_debug(dev->log, "removed from device table");
ptr_array_del(device_table, ptr_array_find(device_table, dev));
/* Stop all pending I/O activity */
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);
}
/* Release all memory */
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);
}
/* Start probing. Called via eloop_call
*/
static void
device_start_probing (void *data)
{
device *dev = data;
device_probe_endpoint(dev, dev->devinfo->endpoints);
}
/* Start device I/O.
*/
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;
}
/* Find device by ident
*/
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;
}
/* Purge device_table
*/
static void
device_table_purge (void)
{
while (mem_len(device_table) > 0) {
device_free(device_table[0], NULL);
}
}
/******************** Underlying protocol operations ********************/
/* Set protocol handler
*/
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);
}
}
/* Set base URI. `uri' ownership is taken by this function
*/
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);
}
/* Query device capabilities
*/
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;
}
/* Decode device capabilities
*/
static error
device_proto_devcaps_decode (device *dev, devcaps *caps)
{
return dev->proto_ctx.proto->devcaps_decode(&dev->proto_ctx, caps);
}
/* http_query_onrxhdr() callback
*/
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);
}
}
/* Submit operation request
*/
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;
}
/* Dummy decode for PROTO_OP_CANCEL and PROTO_OP_CLEANUP
*/
static proto_result
device_proto_dummy_decode (const proto_ctx *ctx)
{
proto_result result = {0};
(void) ctx;
result.next = PROTO_OP_FINISH;
return result;
}
/* Decode operation response
*/
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;
}
/******************** HTTP operations ********************/
/* Cancel pending HTTP request, if any
*/
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;
}
}
/* http_client onerror callback
*/
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 {
/* Scan job known to be done, now waiting for cancel
* completion
*/
device_stm_state_set(dev, DEVICE_STM_CANCEL_JOB_DONE);
}
}
/******************** Protocol initialization ********************/
/* Probe next device address
*/
static void
device_probe_endpoint (device *dev, zeroconf_endpoint *endpoint)
{
/* Switch 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));
/* Fetch device capabilities */
device_proto_devcaps_submit (dev, device_scanner_capabilities_callback);
}
/* Scanner capabilities fetch callback
*/
static void
device_scanner_capabilities_callback (void *ptr, http_query *q)
{
error err = NULL;
device *dev = ptr;
/* Check request status */
err = http_query_error(q);
if (err != NULL) {
err = eloop_eprintf("scanner capabilities query: %s", ESTRING(err));
goto DONE;
}
/* Parse XML response */
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);
/* Update endpoint address in case of HTTP redirection */
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);
}
}
}
/* Cleanup and exit */
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);
}
}
/******************** Scan state machinery ********************/
/* Get state name, for debugging
*/
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;
}
/* Get state
*/
static inline DEVICE_STM_STATE
device_stm_state_get (device *dev)
{
return __atomic_load_n(&dev->stm_state, __ATOMIC_SEQ_CST);
}
/* Check if device is in working state
*/
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;
}
/* Set state
*/
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);
}
}
}
/* cancel_query() callback
*/
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);
}
}
/* Perform cancel, if possible
*/
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) {
/* If we are not expecting more images, skip cancel
* 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 {
/* Otherwise, perform a normal cancel operation
*/
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;
}
/* stm_cancel_event callback
*/
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);
}
}
/* Request cancel.
*
* 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);
}
}
/* stm_timer callback
*/
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);
}
/* Operation 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));
}
/* Save useful result, if any */
if (dev->proto_ctx.op == PROTO_OP_SCAN) {
if (result.data.location != NULL) {
mem_free((char*) dev->proto_ctx.location); /* Just in case */
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);
}
}
/* Update job status */
device_job_set_status(dev, result.status);
/* If CANCEL was sent, and next operation is CLEANUP or
* 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;
}
}
/* Check for FINISH */
if (result.next == PROTO_OP_FINISH) {
if (dev->proto_ctx.images_received == 0) {
/* If no images received, and no error status
* 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;
}
/* Handle switch to PROTO_OP_CLEANUP state */
if (result.next == PROTO_OP_CLEANUP) {
device_stm_state_set(dev, DEVICE_STM_CLEANUP);
}
/* Handle delayed cancellation */
if (device_stm_state_get(dev) == DEVICE_STM_CANCEL_DELAYED) {
if (!device_stm_cancel_perform(dev, SANE_STATUS_CANCELLED)) {
/* Finish the job, if we has not yet reached cancellable
* state
*/
device_stm_state_set(dev, DEVICE_STM_DONE);
return;
}
}
/* Handle delay */
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;
}
/* Submit next operation */
device_proto_op_submit(dev, result.next, device_stm_op_callback);
}
/* Geometrical scan parameters
*/
typedef struct {
SANE_Word off; /* Requested X/Y offset, in pixels assuming 300 DPI */
SANE_Word len; /* Requested width/height, in pixels, assuming 300 DPI */
SANE_Word skip; /* Pixels to skip in returned image, in pixels assuming
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;
}
/* Choose image format
*/
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;
}
/* Request scan
*/
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];
/* Prepare window parameters */
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;
/* Fill proto_scan_params structure */
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);
/* Dump parameters */
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, "");
/* Submit a request */
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);
}
}
/* Wait until device leaves the working state
*/
static void
device_stm_wait_while_working (device *dev)
{
while (device_stm_state_working(dev)) {
eloop_cond_wait(&dev->stm_cond);
}
}
/* Cancel scanning and wait until device leaves the working state
*/
static void
device_stm_cancel_wait (device *dev, const char *reason)
{
device_stm_cancel_req(dev, reason);
device_stm_wait_while_working(dev);
}
/******************** Scan Job management ********************/
/* Set job status. If status already set, it will not be changed
*/
static void
device_job_set_status (device *dev, SANE_Status status)
{
/* Check status, new and present
*/
switch (status) {
case SANE_STATUS_GOOD:
return;
case SANE_STATUS_CANCELLED:
break;
default:
/* If error already is pending, leave it as is
*/
if (dev->job_status != SANE_STATUS_GOOD) {
return;
}
}
/* Update status
*/
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);
}
}
}
/******************** API helpers ********************/
/* Get device's logging context
*/
log_ctx*
device_log_ctx (device *dev)
{
return dev ? dev->log : NULL;
}
/* Open a device
*/
device*
device_open (const char *ident, SANE_Status *status)
{
device *dev = NULL;
zeroconf_devinfo *devinfo;
*status = SANE_STATUS_GOOD;
/* Validate arguments */
if (ident == NULL || *ident == '\0') {
log_debug(NULL, "device_open: invalid name");
*status = SANE_STATUS_INVAL;
return NULL;
}
/* Already opened? */
dev = device_find_by_ident(ident);
if (dev) {
*status = SANE_STATUS_DEVICE_BUSY;
return NULL;
}
/* Obtain device endpoints */
devinfo = zeroconf_devinfo_lookup(ident);
if (devinfo == NULL) {
log_debug(NULL, "device_open(%s): device not found", ident);
*status = SANE_STATUS_INVAL;
return NULL;
}
/* Create a device */
dev = device_new(devinfo);
*status = device_io_start(dev);
if (*status != SANE_STATUS_GOOD) {
device_free(dev, NULL);
return NULL;
}
/* Wait until device is initialized */
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;
}
/* Close the device
* 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)
{
/* Cancel job in progress, if any */
if (device_stm_state_working(dev)) {
device_stm_cancel_wait(dev, "device close");
}
/* Close the device */
device_stm_state_set(dev, DEVICE_STM_CLOSED);
device_free(dev, log_msg);
}
/* Get option descriptor
*/
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;
}
/* Get device option
*/
SANE_Status
device_get_option (device *dev, SANE_Int option, void *value)
{
return devopt_get_option(&dev->opt, option, value);
}
/* Set device option
*/
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;
}
/* Get current scan parameters
*/
SANE_Status
device_get_parameters (device *dev, SANE_Parameters *params)
{
*params = dev->opt.params;
return SANE_STATUS_GOOD;
}
/* Start scanning operation - runs on a context of event loop thread
*/
static void
device_start_do (void *data)
{
device *dev = data;
device_stm_start_scan(dev);
}
/* Wait until new job is started
*/
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);
}
}
/* Enforce CONFIG_START_RETRY_INTERVAL
*/
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();
}
}
/* Start new scanning job
*/
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;
}
/* Start scanning operation
*/
SANE_Status
device_start (device *dev)
{
/* Already scanning? */
if ((dev->flags & DEVICE_SCANNING) != 0) {
log_debug(dev->log, "device_start: already scanning");
return SANE_STATUS_INVAL;
}
/* Don's start if window is not valid */
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;
}
/* Update state */
dev->flags |= DEVICE_SCANNING;
pollable_reset(dev->read_pollable);
dev->read_non_blocking = SANE_FALSE;
/* Scanner idle? Start new job */
if (device_stm_state_get(dev) == DEVICE_STM_IDLE) {
return device_start_new_job(dev);
}
/* Previous job still running. Synchronize with it
*/
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);
}
/* If we have more buffered images, just start
* 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;
}
/* Seems that previous job has finished.
*
* 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;
}
/* Start new scan job */
return device_start_new_job(dev);
}
/* Cancel scanning operation
*/
void
device_cancel (device *dev)
{
/* Note, xsane calls sane_cancel() after each successful
* 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);
}
/* Set I/O mode
*/
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;
}
/* Get select file descriptor
*/
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;
}
/******************** Read machinery ********************/
/* Setup read_filters
*/
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);
}
/* Cleanup read_filters
*/
static void
device_read_filters_cleanup (device *dev)
{
filter_chain_free(dev->read_filters);
dev->read_filters = NULL;
}
/* Pull next image from the read queue and start decoding
*/
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;
/* Pull next image out of the queue */
dev->read_image = http_data_queue_pull(dev->read_queue);
if (dev->read_image == NULL) {
return SANE_STATUS_EOF;
}
/* Guess format and choose decoder */
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;
}
/* Start new image decoding */
err = image_decoder_begin(decoder,
dev->read_image->bytes, dev->read_image->size);
if (err != NULL) {
goto DONE;
}
/* Obtain and dump image parameters */
image_decoder_get_params(decoder, &params);
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, "");
/* Validate image parameters */
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) {
/* This is what we cannot handle */
err = ERROR("Unexpected frame format");
goto DONE;
}
wid = params.pixels_per_line;
hei = params.lines;
/* Setup image clipping
*
* 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) {
/* Trivial case - just skip everything */
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;
}
/* Initialize image decoding */
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;
}
}
/* Wake up reader */
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;
}
/* Perform 24 to 8 bit image resampling for a single line
*/
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 ++) {
/* Y = R * 0.299 + G * 0.587 + B * 0.114
*
* 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);
}
}
/* Decode next image line
*
* 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;
}
/* Read scanned image
*/
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; /* Must return 0, if status is not GOOD */
}
log_assert(dev->log, decoder != NULL);
/* Check device state */
if ((dev->flags & DEVICE_READING) == 0) {
log_debug(dev->log, "device_read: not scanning");
return SANE_STATUS_INVAL;
}
/* Validate arguments */
if (len_out == NULL) {
log_debug(dev->log, "device_read: zero output buffer");
return SANE_STATUS_INVAL;
}
/* Wait until device is ready */
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;
}
}
/* Read line by line */
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");
}
/* Cleanup and exit */
DONE:
if (status == SANE_STATUS_EOF && len > 0) {
status = SANE_STATUS_GOOD;
}
if (status == SANE_STATUS_GOOD) {
*len_out = len;
return SANE_STATUS_GOOD;
}
/* Scan and read finished - cleanup device */
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;
}
/******************** Initialization/cleanup ********************/
/* Initialize device management
*/
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;
}
/* Cleanup device management
*/
void
device_management_cleanup (void)
{
if (device_table != NULL) {
log_assert(NULL, mem_len(device_table) == 0);
mem_free(device_table);
device_table = NULL;
}
}
/* Start/stop device management
*/
static void
device_management_start_stop (bool start)
{
if (!start) {
device_table_purge();
}
}
/* vim:ts=8:sw=4:et
*/