kopia lustrzana https://gitlab.com/sane-project/backends
				
				
				
			
		
			
				
	
	
		
			261 wiersze
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			261 wiersze
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C
		
	
	
| /* sane - Scanner Access Now Easy.
 | |
| 
 | |
|    Copyright (C) 2019 Touboul Nathane
 | |
|    Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
 | |
| 
 | |
|    This file is part of the SANE package.
 | |
| 
 | |
|    SANE is free software; you can redistribute it and/or modify it under
 | |
|    the terms of the GNU General Public License as published by the Free
 | |
|    Software Foundation; either version 3 of the License, or (at your
 | |
|    option) any later version.
 | |
| 
 | |
|    SANE is distributed in the hope that it will be useful, but WITHOUT
 | |
|    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | |
|    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 | |
|    for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with sane; see the file COPYING.  If not, write to the Free
 | |
|    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 | |
| 
 | |
|    This file implements a SANE backend for eSCL scanners.  */
 | |
| 
 | |
| #define DEBUG_DECLARE_ONLY
 | |
| #include "../include/sane/config.h"
 | |
| 
 | |
| #include "escl.h"
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include <curl/curl.h>
 | |
| 
 | |
| #ifdef PATH_MAX
 | |
| # undef PATH_MAX
 | |
| #endif
 | |
| 
 | |
| #define PATH_MAX 4096
 | |
| 
 | |
| struct uploading
 | |
| {
 | |
|     const char *read_data;
 | |
|     size_t size;
 | |
| };
 | |
| 
 | |
| struct downloading
 | |
| {
 | |
|     char *memory;
 | |
|     size_t size;
 | |
| };
 | |
| 
 | |
| static const char settings[] =
 | |
|     "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                        \
 | |
|     "<scan:ScanSettings xmlns:pwg=\"http://www.pwg.org/schemas/2010/12/sm\" xmlns:scan=\"http://schemas.hp.com/imaging/escl/2011/05/03\">" \
 | |
|     "   <pwg:Version>2.0</pwg:Version>" \
 | |
|     "   <pwg:ScanRegions>" \
 | |
|     "      <pwg:ScanRegion>" \
 | |
|     "          <pwg:ContentRegionUnits>escl:ThreeHundredthsOfInches</pwg:ContentRegionUnits>" \
 | |
|     "          <pwg:Height>%d</pwg:Height>" \
 | |
|     "          <pwg:Width>%d</pwg:Width>" \
 | |
|     "          <pwg:XOffset>%d</pwg:XOffset>" \
 | |
|     "          <pwg:YOffset>%d</pwg:YOffset>" \
 | |
|     "      </pwg:ScanRegion>" \
 | |
|     "   </pwg:ScanRegions>" \
 | |
|     "   <pwg:DocumentFormat>%s</pwg:DocumentFormat>" \
 | |
|     "%s" \
 | |
|     "   <scan:ColorMode>%s</scan:ColorMode>" \
 | |
|     "   <scan:XResolution>%d</scan:XResolution>" \
 | |
|     "   <scan:YResolution>%d</scan:YResolution>" \
 | |
|     "   <pwg:InputSource>%s</pwg:InputSource>" \
 | |
|     "   <scan:InputSource>%s</scan:InputSource>" \
 | |
|     "%s" \
 | |
|     "</scan:ScanSettings>";
 | |
| 
 | |
| /**
 | |
|  * \fn static size_t download_callback(void *str, size_t size, size_t nmemb, void *userp)
 | |
|  * \brief Callback function that stocks in memory the content of the 'job'. Example below :
 | |
|  *        "Trying 192.168.14.150...
 | |
|  *         TCP_NODELAY set
 | |
|  *         Connected to 192.168.14.150 (192.168.14.150) port 80
 | |
|  *         POST /eSCL/ScanJobs HTTP/1.1
 | |
|  *         Host: 192.168.14.150
 | |
|  *         User-Agent: curl/7.55.1
 | |
|  *         Accept: /
 | |
|  *         Content-Length: 605
 | |
|  *         Content-Type: application/x-www-form-urlencoded
 | |
|  *         upload completely sent off: 605 out of 605 bytes
 | |
|  *         < HTTP/1.1 201 Created
 | |
|  *         < MIME-Version: 1.0
 | |
|  *         < Location: http://192.168.14.150/eSCL/ScanJobs/22b54fd0-027b-1000-9bd0-f4a99726e2fa
 | |
|  *         < Content-Length: 0
 | |
|  *         < Connection: close
 | |
|  *         <
 | |
|  *         Closing connection 0"
 | |
|  *
 | |
|  * \return realsize (size of the content needed -> the 'job')
 | |
|  */
 | |
| static size_t
 | |
| download_callback(void *str, size_t size, size_t nmemb, void *userp)
 | |
| {
 | |
|     struct downloading *download = (struct downloading *)userp;
 | |
|     size_t realsize = size * nmemb;
 | |
|     char *content = realloc(download->memory, download->size + realsize + 1);
 | |
| 
 | |
|     if (content == NULL) {
 | |
|         DBG( 1, "Not enough memory (realloc returned NULL)\n");
 | |
|         return (0);
 | |
|     }
 | |
|     download->memory = content;
 | |
|     memcpy(&(download->memory[download->size]), str, realsize);
 | |
|     download->size = download->size + realsize;
 | |
|     download->memory[download->size] = 0;
 | |
|     return (realsize);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \fn char *escl_newjob (capabilities_t *scanner, const ESCL_Device *device, SANE_Status *status)
 | |
|  * \brief Function that, using curl, uploads the data (composed by the scanner capabilities) to the
 | |
|  *        server to download the 'job' and recover the 'new job' (char *result), in LOCATION.
 | |
|  *        This function is called in the 'sane_start' function and it's the equivalent of the
 | |
|  *        following curl command : "curl -v POST -d cap.xml http(s)://'ip':'port'/eSCL/ScanJobs".
 | |
|  *
 | |
|  * \return result (the 'new job', situated in LOCATION)
 | |
|  */
 | |
| char *
 | |
| escl_newjob (capabilities_t *scanner, const ESCL_Device *device, SANE_Status *status)
 | |
| {
 | |
|     CURL *curl_handle = NULL;
 | |
|     int off_x = 0, off_y = 0;
 | |
|     struct uploading *upload = NULL;
 | |
|     struct downloading *download = NULL;
 | |
|     const char *scan_jobs = "/eSCL/ScanJobs";
 | |
|     char cap_data[PATH_MAX] = { 0 };
 | |
|     char *location = NULL;
 | |
|     char *result = NULL;
 | |
|     char *temporary = NULL;
 | |
|     char *f_ext = "";
 | |
|     char *format_ext = NULL;
 | |
|     char duplex_mode[1024] = { 0 };
 | |
| 
 | |
|     *status = SANE_STATUS_GOOD;
 | |
|     if (device == NULL || scanner == NULL) {
 | |
|         *status = SANE_STATUS_NO_MEM;
 | |
|         DBG( 1, "Create NewJob : the name or the scan are invalid.\n");
 | |
|         return (NULL);
 | |
|     }
 | |
|     upload = (struct uploading *)calloc(1, sizeof(struct uploading));
 | |
|     if (upload == NULL) {
 | |
|         *status = SANE_STATUS_NO_MEM;
 | |
|         DBG( 1, "Create NewJob : memory allocation failure\n");
 | |
|         return (NULL);
 | |
|     }
 | |
|     download = (struct downloading *)calloc(1, sizeof(struct downloading));
 | |
|     if (download == NULL) {
 | |
|         free(upload);
 | |
|         DBG( 1, "Create NewJob : memory allocation failure\n");
 | |
|         *status = SANE_STATUS_NO_MEM;
 | |
|         return (NULL);
 | |
|     }
 | |
|     curl_handle = curl_easy_init();
 | |
|     if (scanner->caps[scanner->source].format_ext == 1)
 | |
|     {
 | |
|         char f_ext_tmp[1024];
 | |
|         snprintf(f_ext_tmp, sizeof(f_ext_tmp),
 | |
| 			"   <scan:DocumentFormatExt>%s</scan:DocumentFormatExt>",
 | |
|     			scanner->caps[scanner->source].default_format);
 | |
|         format_ext = f_ext_tmp;
 | |
|     }
 | |
|     else
 | |
|       format_ext = f_ext;
 | |
|     if(scanner->source > PLATEN && scanner->Sources[ADFDUPLEX]) {
 | |
|        snprintf(duplex_mode, sizeof(duplex_mode),
 | |
| 		       "   <scan:Duplex>%s</scan:Duplex>",
 | |
| 		       scanner->source == ADFDUPLEX ? "true" : "false");
 | |
|     }
 | |
|     DBG( 1, "Create NewJob : %s\n", scanner->caps[scanner->source].default_format);
 | |
|     if (scanner->caps[scanner->source].pos_x > scanner->caps[scanner->source].width)
 | |
|          off_x = (scanner->caps[scanner->source].pos_x > scanner->caps[scanner->source].width) / 2;
 | |
|     if (scanner->caps[scanner->source].pos_y > scanner->caps[scanner->source].height)
 | |
|          off_y = (scanner->caps[scanner->source].pos_y > scanner->caps[scanner->source].height) / 2;
 | |
|     if (curl_handle != NULL) {
 | |
| 		char *source = (scanner->source == PLATEN ? "Platen" : "Feeder");
 | |
|         snprintf(cap_data, sizeof(cap_data), settings,
 | |
| 			scanner->caps[scanner->source].height,
 | |
| 			scanner->caps[scanner->source].width,
 | |
| 			off_x,
 | |
| 			off_y,
 | |
| 			scanner->caps[scanner->source].default_format,
 | |
| 			format_ext,
 | |
| 			scanner->caps[scanner->source].default_color,
 | |
| 			scanner->caps[scanner->source].default_resolution,
 | |
| 			scanner->caps[scanner->source].default_resolution,
 | |
| 			source,
 | |
| 			source,
 | |
| 			duplex_mode[0] == 0 ? "" : duplex_mode);
 | |
|         DBG( 1, "Create NewJob : %s\n", cap_data);
 | |
|         upload->read_data = strdup(cap_data);
 | |
|         upload->size = strlen(cap_data);
 | |
|         download->memory = malloc(1);
 | |
|         download->size = 0;
 | |
|         escl_curl_url(curl_handle, device, scan_jobs);
 | |
|         curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
 | |
|         curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, upload->read_data);
 | |
|         curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, upload->size);
 | |
|         curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, download_callback);
 | |
|         curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, (void *)download);
 | |
|         CURLcode res = curl_easy_perform(curl_handle);
 | |
|         if (res != CURLE_OK) {
 | |
|             DBG( 1, "Create NewJob : the scanner responded incorrectly: %s\n", curl_easy_strerror(res));
 | |
|             *status = SANE_STATUS_INVAL;
 | |
|         }
 | |
|         else {
 | |
|             if (download->memory != NULL) {
 | |
|                 char *tmp_location = strstr(download->memory, "Location:");
 | |
|                 if (tmp_location) {
 | |
|                     temporary = strchr(tmp_location, '\r');
 | |
|                     if (temporary == NULL)
 | |
|                         temporary = strchr(tmp_location, '\n');
 | |
|                     if (temporary != NULL) {
 | |
|                        *temporary = '\0';
 | |
|                        location = strrchr(tmp_location,'/');
 | |
|                        if (location) {
 | |
|                           result = strdup(location);
 | |
|                           DBG( 1, "Create NewJob : %s\n", result);
 | |
|                           *temporary = '\n';
 | |
|                        }
 | |
|                     }
 | |
|                     if (result == NULL) {
 | |
|                         DBG( 1, "Error : Create NewJob, no location: %s\n", download->memory);
 | |
|                         *status = SANE_STATUS_INVAL;
 | |
|                     }
 | |
|                     free(download->memory);
 | |
|                 }
 | |
|                 else {
 | |
|                     DBG( 1, "Create NewJob : The creation of the failed job: %s\n", download->memory);
 | |
|                     // If "409 Conflict" appear it means that there is no paper in feeder
 | |
|                     if (strstr(download->memory, "409 Conflict") != NULL)
 | |
|                         *status = SANE_STATUS_NO_DOCS;
 | |
|                     // If "503 Service Unavailable" appear, it means that device is busy (scanning in progress)
 | |
|                     else if (strstr(download->memory, "503 Service Unavailable") != NULL)
 | |
|                         *status = SANE_STATUS_DEVICE_BUSY;
 | |
|                     else
 | |
|                         *status = SANE_STATUS_INVAL;
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 *status = SANE_STATUS_NO_MEM;
 | |
|                 DBG( 1, "Create NewJob : The creation of the failed job\n");
 | |
|                 return (NULL);
 | |
|             }
 | |
|         }
 | |
|         curl_easy_cleanup(curl_handle);
 | |
|     }
 | |
|     if (upload != NULL)
 | |
|         free(upload);
 | |
|     if (download != NULL)
 | |
|         free(download);
 | |
|     return (result);
 | |
| }
 |