1/* sane - Scanner Access Now Easy. 2 3 Copyright (C) 2019 Touboul Nathane 4 Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com> 5 6 This file is part of the SANE package. 7 8 SANE is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free 10 Software Foundation; either version 3 of the License, or (at your 11 option) any later version. 12 13 SANE is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with sane; see the file COPYING. 20 If not, see <https://www.gnu.org/licenses/>. 21 22 This file implements a SANE backend for eSCL scanners. */ 23 24#define DEBUG_DECLARE_ONLY 25#include "../include/sane/config.h" 26 27#include "escl.h" 28 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32 33#include <libxml/parser.h> 34 35struct idle 36{ 37 char *memory; 38 size_t size; 39}; 40 41/** 42 * \fn static size_t memory_callback_s(void *contents, size_t size, size_t nmemb, void *userp) 43 * \brief Callback function that stocks in memory the content of the scanner status. 44 * 45 * \return realsize (size of the content needed -> the scanner status) 46 */ 47static size_t 48memory_callback_s(void *contents, size_t size, size_t nmemb, void *userp) 49{ 50 size_t realsize = size * nmemb; 51 struct idle *mem = (struct idle *)userp; 52 53 char *str = realloc(mem->memory, mem->size + realsize + 1); 54 if (str == NULL) { 55 DBG(1, "not enough memory (realloc returned NULL)\n"); 56 return (0); 57 } 58 mem->memory = str; 59 memcpy(&(mem->memory[mem->size]), contents, realsize); 60 mem->size = mem->size + realsize; 61 mem->memory[mem->size] = 0; 62 return (realsize); 63} 64 65/** 66 * \fn static int find_nodes_s(xmlNode *node) 67 * \brief Function that browses the xml file and parses it, to find the xml children node. 68 * --> to recover the scanner status. 69 * 70 * \return 0 if a xml child node is found, 1 otherwise 71 */ 72static int 73find_nodes_s(xmlNode *node) 74{ 75 xmlNode *child = node->children; 76 77 while (child) { 78 if (child->type == XML_ELEMENT_NODE) 79 return (0); 80 child = child->next; 81 } 82 return (1); 83} 84 85static void 86print_xml_job_status(xmlNode *node, 87 SANE_Status *job, 88 int *image) 89{ 90 while (node) { 91 if (node->type == XML_ELEMENT_NODE) { 92 if (find_nodes_s(node)) { 93 if (strcmp((const char *)node->name, "JobState") == 0) { 94 const char *state = (const char *)xmlNodeGetContent(node); 95 if (!strcmp(state, "Processing")) { 96 *job = SANE_STATUS_DEVICE_BUSY; 97 DBG(10, "jobId Processing SANE_STATUS_DEVICE_BUSY\n"); 98 } 99 else if (!strcmp(state, "Completed")) { 100 *job = SANE_STATUS_GOOD; 101 DBG(10, "jobId Completed SANE_STATUS_GOOD\n"); 102 } 103 else if (strcmp((const char *)node->name, "ImagesToTransfer") == 0) { 104 const char *state = (const char *)xmlNodeGetContent(node); 105 *image = atoi(state); 106 } 107 } 108 } 109 } 110 print_xml_job_status(node->children, job, image); 111 node = node->next; 112 } 113} 114 115static void 116print_xml_platen_and_adf_status(xmlNode *node, 117 SANE_Status *platen, 118 SANE_Status *adf, 119 const char* jobId, 120 SANE_Status *job, 121 int *image) 122{ 123 while (node) { 124 if (node->type == XML_ELEMENT_NODE) { 125 if (find_nodes_s(node)) { 126 if (strcmp((const char *)node->name, "State") == 0) { 127 DBG(10, "State\t"); 128 const char *state = (const char *)xmlNodeGetContent(node); 129 if (!strcmp(state, "Idle")) { 130 DBG(10, "Idle SANE_STATUS_GOOD\n"); 131 *platen = SANE_STATUS_GOOD; 132 } else if (!strcmp(state, "Processing")) { 133 DBG(10, "Processing SANE_STATUS_DEVICE_BUSY\n"); 134 *platen = SANE_STATUS_DEVICE_BUSY; 135 } else { 136 DBG(10, "%s SANE_STATUS_UNSUPPORTED\n", state); 137 *platen = SANE_STATUS_UNSUPPORTED; 138 } 139 } 140 // Thank's Alexander Pevzner (pzz@apevzner.com) 141 else if (adf && strcmp((const char *)node->name, "AdfState") == 0) { 142 const char *state = (const char *)xmlNodeGetContent(node); 143 if (!strcmp(state, "ScannerAdfLoaded")){ 144 DBG(10, "ScannerAdfLoaded SANE_STATUS_GOOD\n"); 145 *adf = SANE_STATUS_GOOD; 146 } else if (!strcmp(state, "ScannerAdfJam")) { 147 DBG(10, "ScannerAdfJam SANE_STATUS_JAMMED\n"); 148 *adf = SANE_STATUS_JAMMED; 149 } else if (!strcmp(state, "ScannerAdfDoorOpen")) { 150 DBG(10, "ScannerAdfDoorOpen SANE_STATUS_COVER_OPEN\n"); 151 *adf = SANE_STATUS_COVER_OPEN; 152 } else if (!strcmp(state, "ScannerAdfProcessing")) { 153 /* Kyocera version */ 154 DBG(10, "ScannerAdfProcessing SANE_STATUS_NO_DOC\n"); 155 *adf = SANE_STATUS_NO_DOCS; 156 } else if (!strcmp(state, "ScannerAdfEmpty")) { 157 DBG(10, "ScannerAdfEmpty SANE_STATUS_NO_DOCS\n"); 158 /* Cannon TR4500, EPSON XP-7100 */ 159 *adf = SANE_STATUS_NO_DOCS; 160 } else { 161 DBG(10, "%s SANE_STATUS_NO_DOCS\n", state); 162 *adf = SANE_STATUS_UNSUPPORTED; 163 } 164 } 165 else if (jobId && job && strcmp((const char *)node->name, "JobUri") == 0) { 166 if (strstr((const char *)xmlNodeGetContent(node), jobId)) { 167 print_xml_job_status(node, job, image); 168 } 169 } 170 } 171 } 172 print_xml_platen_and_adf_status(node->children, 173 platen, 174 adf, 175 jobId, 176 job, 177 image); 178 node = node->next; 179 } 180} 181 182/** 183 * \fn SANE_Status escl_status(const ESCL_Device *device) 184 * \brief Function that finally recovers the scanner status ('Idle', or not), using curl. 185 * This function is called in the 'sane_open' function and it's the equivalent of 186 * the following curl command : "curl http(s)://'ip':'port'/eSCL/ScannerStatus". 187 * 188 * \return status (if everything is OK, status = SANE_STATUS_GOOD, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL) 189 */ 190SANE_Status 191escl_status(const ESCL_Device *device, 192 int source, 193 const char* jobId, 194 SANE_Status *job) 195{ 196 SANE_Status status = SANE_STATUS_DEVICE_BUSY; 197 SANE_Status platen= SANE_STATUS_DEVICE_BUSY; 198 SANE_Status adf= SANE_STATUS_DEVICE_BUSY; 199 CURL *curl_handle = NULL; 200 struct idle *var = NULL; 201 xmlDoc *data = NULL; 202 xmlNode *node = NULL; 203 const char *scanner_status = "/eSCL/ScannerStatus"; 204 int image = -1; 205 int pass = 0; 206reload: 207 208 if (device == NULL) 209 return (SANE_STATUS_NO_MEM); 210 status = SANE_STATUS_DEVICE_BUSY; 211 platen= SANE_STATUS_DEVICE_BUSY; 212 adf= SANE_STATUS_DEVICE_BUSY; 213 var = (struct idle*)calloc(1, sizeof(struct idle)); 214 if (var == NULL) 215 return (SANE_STATUS_NO_MEM); 216 var->memory = malloc(1); 217 var->size = 0; 218 curl_handle = curl_easy_init(); 219 220 escl_curl_url(curl_handle, device, scanner_status); 221 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, memory_callback_s); 222 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)var); 223 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); 224 curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 3L); 225 CURLcode res = curl_easy_perform(curl_handle); 226 if (res != CURLE_OK) { 227 DBG( 1, "The scanner didn't respond: %s\n", curl_easy_strerror(res)); 228 status = SANE_STATUS_INVAL; 229 goto clean_data; 230 } 231 DBG( 10, "eSCL : Status : %s.\n", var->memory); 232 data = xmlReadMemory(var->memory, var->size, "file.xml", NULL, 0); 233 if (data == NULL) { 234 status = SANE_STATUS_NO_MEM; 235 goto clean_data; 236 } 237 node = xmlDocGetRootElement(data); 238 if (node == NULL) { 239 status = SANE_STATUS_NO_MEM; 240 goto clean; 241 } 242 /* Decode Job status */ 243 // Thank's Alexander Pevzner (pzz@apevzner.com) 244 print_xml_platen_and_adf_status(node, &platen, &adf, jobId, job, &image); 245 if (platen != SANE_STATUS_GOOD && 246 platen != SANE_STATUS_UNSUPPORTED) { 247 status = platen; 248 } else if (source == PLATEN) { 249 status = platen; 250 } else { 251 status = adf; 252 } 253 DBG (10, "STATUS : %s\n", sane_strstatus(status)); 254clean: 255 xmlFreeDoc(data); 256clean_data: 257 xmlCleanupParser(); 258 xmlMemoryDump(); 259 curl_easy_cleanup(curl_handle); 260 free(var->memory); 261 free(var); 262 if (pass == 0 && 263 source != PLATEN && 264 image == 0 && 265 (status == SANE_STATUS_GOOD || 266 status == SANE_STATUS_UNSUPPORTED || 267 status == SANE_STATUS_DEVICE_BUSY)) { 268 pass = 1; 269 goto reload; 270 } 271 return (status); 272} 273