You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2220 lines
79 KiB
2220 lines
79 KiB
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
* Copyright (C) 2016 Mopria Alliance, Inc.
|
|
* Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
#ifndef __USE_UNIX98
|
|
#define __USE_UNIX98
|
|
#endif
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <semaphore.h>
|
|
#include <printer_capabilities_types.h>
|
|
|
|
#include "ifc_print_job.h"
|
|
#include "wprint_debug.h"
|
|
#include "plugin_db.h"
|
|
|
|
#include "ifc_status_monitor.h"
|
|
|
|
#include "ippstatus_monitor.h"
|
|
#include "ippstatus_capabilities.h"
|
|
#include "ipp_print.h"
|
|
#include "ipphelper.h"
|
|
|
|
#include "lib_printable_area.h"
|
|
#include "wprint_io_plugin.h"
|
|
#include "../plugins/media.h"
|
|
|
|
#define TAG "lib_wprint"
|
|
|
|
/* As expected by target devices */
|
|
#define USERAGENT_PREFIX "wPrintAndroid"
|
|
|
|
#define USE_PWG_OVER_PCLM 0
|
|
|
|
#if (USE_PWG_OVER_PCLM != 0)
|
|
#define _DEFAULT_PRINT_FORMAT PRINT_FORMAT_PWG
|
|
#define _DEFAULT_PCL_TYPE PCLPWG
|
|
#else // (USE_PWG_OVER_PCLM != 0)
|
|
#define _DEFAULT_PRINT_FORMAT PRINT_FORMAT_PCLM
|
|
#define _DEFAULT_PCL_TYPE PCLm
|
|
#endif // (USE_PWG_OVER_PCLM != 0)
|
|
|
|
#define _MAX_SPOOLED_JOBS 100
|
|
#define _MAX_MSGS (_MAX_SPOOLED_JOBS * 5)
|
|
|
|
#define _MAX_PAGES_PER_JOB 1000
|
|
|
|
#define MAX_IDLE_WAIT (5 * 60)
|
|
|
|
#define DEFAULT_RESOLUTION (300)
|
|
|
|
// When searching for a supported resolution this is the max resolution we will consider.
|
|
#define MAX_SUPPORTED_RESOLUTION (720)
|
|
|
|
#define MAX_DONE_WAIT (5 * 60)
|
|
#define MAX_START_WAIT (45)
|
|
|
|
#define IO_PORT_FILE 0
|
|
|
|
/*
|
|
* The following macros allow for up to 8 bits (256) for spooled job id#s and
|
|
* 24 bits (16 million) of a running sequence number to provide a reasonably
|
|
* unique job handle
|
|
*/
|
|
|
|
// _ENCODE_HANDLE() is only called from _get_handle()
|
|
#define _ENCODE_HANDLE(X) ( (((++_running_number) & 0xffffff) << 8) | ((X) & 0xff) )
|
|
#define _DECODE_HANDLE(X) ((X) & 0xff)
|
|
|
|
#undef snprintf
|
|
#undef vsnprintf
|
|
|
|
typedef enum {
|
|
JOB_STATE_FREE, // queue element free
|
|
JOB_STATE_QUEUED, // job queued and waiting to be run
|
|
JOB_STATE_RUNNING, // job running (printing)
|
|
JOB_STATE_BLOCKED, // print job blocked due to printer stall/error
|
|
JOB_STATE_CANCEL_REQUEST, // print job cancelled by user,
|
|
JOB_STATE_CANCELLED, // print job cancelled by user, waiting to be freed
|
|
JOB_STATE_COMPLETED, // print job completed successfully, waiting to be freed
|
|
JOB_STATE_ERROR, // job could not be run due to error
|
|
JOB_STATE_CORRUPTED, // job could not be run due to error
|
|
|
|
NUM_JOB_STATES
|
|
} _job_state_t;
|
|
|
|
typedef enum {
|
|
TOP_MARGIN = 0,
|
|
LEFT_MARGIN,
|
|
RIGHT_MARGIN,
|
|
BOTTOM_MARGIN,
|
|
|
|
NUM_PAGE_MARGINS
|
|
} _page_margins_t;
|
|
|
|
typedef enum {
|
|
MSG_RUN_JOB, MSG_QUIT,
|
|
} wprint_msg_t;
|
|
|
|
typedef struct {
|
|
wprint_msg_t id;
|
|
wJob_t job_id;
|
|
} _msg_t;
|
|
|
|
/*
|
|
* Define an entry in the job queue
|
|
*/
|
|
typedef struct {
|
|
wJob_t job_handle;
|
|
_job_state_t job_state;
|
|
unsigned int blocked_reasons;
|
|
wprint_status_cb_t cb_fn;
|
|
char *printer_addr;
|
|
port_t port_num;
|
|
wprint_plugin_t *plugin;
|
|
ifc_print_job_t *print_ifc;
|
|
char *mime_type;
|
|
char *pathname;
|
|
bool is_dir;
|
|
bool last_page_seen;
|
|
int num_pages;
|
|
msg_q_id pageQ;
|
|
msg_q_id saveQ;
|
|
|
|
wprint_job_params_t job_params;
|
|
bool cancel_ok;
|
|
bool use_secure_uri;
|
|
|
|
const ifc_status_monitor_t *status_ifc;
|
|
char debug_path[MAX_PATHNAME_LENGTH + 1];
|
|
char printer_uri[1024];
|
|
int job_debug_fd;
|
|
int page_debug_fd;
|
|
|
|
/* A buffer of bytes containing the certificate received while setting up this job, if any. */
|
|
uint8 *certificate;
|
|
int certificate_len;
|
|
} _job_queue_t;
|
|
|
|
/*
|
|
* An entry for queued pages
|
|
*/
|
|
typedef struct {
|
|
int page_num;
|
|
bool pdf_page;
|
|
bool last_page;
|
|
bool corrupted;
|
|
char filename[MAX_PATHNAME_LENGTH + 1];
|
|
unsigned int top_margin;
|
|
unsigned int left_margin;
|
|
unsigned int right_margin;
|
|
unsigned int bottom_margin;
|
|
} _page_t;
|
|
|
|
/*
|
|
* Entry for a registered plugin
|
|
*/
|
|
typedef struct {
|
|
port_t port_num;
|
|
const wprint_io_plugin_t *io_plugin;
|
|
} _io_plugin_t;
|
|
|
|
static _job_queue_t _job_queue[_MAX_SPOOLED_JOBS];
|
|
static msg_q_id _msgQ;
|
|
|
|
static pthread_t _job_status_tid;
|
|
static pthread_t _job_tid;
|
|
|
|
static pthread_mutex_t _q_lock;
|
|
static pthread_mutexattr_t _q_lock_attr;
|
|
|
|
static sem_t _job_end_wait_sem;
|
|
static sem_t _job_start_wait_sem;
|
|
|
|
static _io_plugin_t _io_plugins[2];
|
|
|
|
char g_osName[MAX_ID_STRING_LENGTH + 1] = {0};
|
|
char g_appName[MAX_ID_STRING_LENGTH + 1] = {0};
|
|
char g_appVersion[MAX_ID_STRING_LENGTH + 1] = {0};
|
|
|
|
/*
|
|
* Convert a pcl_t type to a human-readable string
|
|
*/
|
|
static char *getPCLTypeString(pcl_t pclenum) {
|
|
switch (pclenum) {
|
|
case PCLNONE:
|
|
return "PCL_NONE";
|
|
case PCLm:
|
|
return "PCLm";
|
|
case PCLJPEG:
|
|
return "PCL_JPEG";
|
|
case PCLPWG:
|
|
return "PWG-Raster";
|
|
default:
|
|
return "unkonwn PCL Type";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return a _job_queue_t item by its job_handle or NULL if not found.
|
|
*/
|
|
static _job_queue_t *_get_job_desc(wJob_t job_handle) {
|
|
unsigned long index;
|
|
if (job_handle == WPRINT_BAD_JOB_HANDLE) {
|
|
return NULL;
|
|
}
|
|
index = _DECODE_HANDLE(job_handle);
|
|
if ((index < _MAX_SPOOLED_JOBS) && (_job_queue[index].job_handle == job_handle) &&
|
|
(_job_queue[index].job_state != JOB_STATE_FREE)) {
|
|
return (&_job_queue[index]);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Functions below to fill out the _debug_stream_ifc interface
|
|
*/
|
|
|
|
static void _stream_dbg_end_job(wJob_t job_handle) {
|
|
_job_queue_t *jq = _get_job_desc(job_handle);
|
|
if (jq && (jq->job_debug_fd >= 0)) {
|
|
close(jq->job_debug_fd);
|
|
jq->job_debug_fd = -1;
|
|
}
|
|
}
|
|
|
|
static void _stream_dbg_start_job(wJob_t job_handle, const char *ext) {
|
|
_stream_dbg_end_job(job_handle);
|
|
_job_queue_t *jq = _get_job_desc(job_handle);
|
|
if (jq && jq->debug_path[0]) {
|
|
char filename[MAX_PATHNAME_LENGTH + 1];
|
|
snprintf(filename, MAX_PATHNAME_LENGTH, "%s/jobstream.%s", jq->debug_path, ext);
|
|
filename[MAX_PATHNAME_LENGTH] = 0;
|
|
jq->job_debug_fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
|
|
}
|
|
}
|
|
|
|
static void _stream_dbg_job_data(wJob_t job_handle, const unsigned char *buff,
|
|
unsigned long nbytes) {
|
|
_job_queue_t *jq = _get_job_desc(job_handle);
|
|
ssize_t bytes_written;
|
|
if (jq && (jq->job_debug_fd >= 0)) {
|
|
while (nbytes > 0) {
|
|
bytes_written = write(jq->job_debug_fd, buff, nbytes);
|
|
if (bytes_written < 0) {
|
|
return;
|
|
}
|
|
nbytes -= bytes_written;
|
|
buff += bytes_written;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _stream_dbg_end_page(wJob_t job_handle) {
|
|
_job_queue_t *jq = _get_job_desc(job_handle);
|
|
if (jq && (jq->page_debug_fd >= 0)) {
|
|
close(jq->page_debug_fd);
|
|
jq->page_debug_fd = -1;
|
|
}
|
|
}
|
|
|
|
static void _stream_dbg_page_data(wJob_t job_handle, const unsigned char *buff,
|
|
unsigned long nbytes) {
|
|
_job_queue_t *jq = _get_job_desc(job_handle);
|
|
ssize_t bytes_written;
|
|
if (jq && (jq->page_debug_fd >= 0)) {
|
|
while (nbytes > 0) {
|
|
bytes_written = write(jq->page_debug_fd, buff, nbytes);
|
|
if (bytes_written < 0) {
|
|
return;
|
|
}
|
|
nbytes -= bytes_written;
|
|
buff += bytes_written;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define PPM_IDENTIFIER "P6\n"
|
|
#define PPM_HEADER_LENGTH 128
|
|
|
|
static void _stream_dbg_start_page(wJob_t job_handle, int page_number, int width, int height) {
|
|
_stream_dbg_end_page(job_handle);
|
|
_job_queue_t *jq = _get_job_desc(job_handle);
|
|
if (jq && jq->debug_path[0]) {
|
|
union {
|
|
char filename[MAX_PATHNAME_LENGTH + 1];
|
|
char ppm_header[PPM_HEADER_LENGTH + 1];
|
|
} buff;
|
|
snprintf(buff.filename, MAX_PATHNAME_LENGTH, "%s/page%4.4d.ppm", jq->debug_path,
|
|
page_number);
|
|
buff.filename[MAX_PATHNAME_LENGTH] = 0;
|
|
jq->page_debug_fd = open(buff.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
|
|
int length = snprintf(buff.ppm_header, sizeof(buff.ppm_header), "%s\n#%*c\n%d %d\n%d\n",
|
|
PPM_IDENTIFIER, 0, ' ', width, height, 255);
|
|
int padding = sizeof(buff.ppm_header) - length;
|
|
snprintf(buff.ppm_header, sizeof(buff.ppm_header), "%s\n#%*c\n%d %d\n%d\n",
|
|
PPM_IDENTIFIER, padding, ' ', width, height, 255);
|
|
_stream_dbg_page_data(job_handle, (const unsigned char *) buff.ppm_header,
|
|
PPM_HEADER_LENGTH);
|
|
}
|
|
}
|
|
|
|
static const ifc_wprint_debug_stream_t _debug_stream_ifc = {
|
|
.debug_start_job = _stream_dbg_start_job, .debug_job_data = _stream_dbg_job_data,
|
|
.debug_end_job = _stream_dbg_end_job, .debug_start_page = _stream_dbg_start_page,
|
|
.debug_page_data = _stream_dbg_page_data, .debug_end_page = _stream_dbg_end_page
|
|
};
|
|
|
|
/*
|
|
* Return the debug stream interface corresponding to the specified job handle
|
|
*/
|
|
const ifc_wprint_debug_stream_t *getDebugStreamIfc(wJob_t handle) {
|
|
_job_queue_t *jq = _get_job_desc(handle);
|
|
if (jq) {
|
|
return (jq->debug_path[0] == 0) ? NULL : &_debug_stream_ifc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const ifc_wprint_t _wprint_ifc = {
|
|
.msgQCreate = msgQCreate, .msgQDelete = msgQDelete,
|
|
.msgQSend = msgQSend, .msgQReceive = msgQReceive, .msgQNumMsgs = msgQNumMsgs,
|
|
.get_debug_stream_ifc = getDebugStreamIfc
|
|
};
|
|
|
|
static pcl_t _default_pcl_type = _DEFAULT_PCL_TYPE;
|
|
|
|
static const ifc_print_job_t *_printer_file_connect(const ifc_wprint_t *wprint_ifc) {
|
|
return printer_connect(IO_PORT_FILE);
|
|
}
|
|
|
|
static const ifc_printer_capabilities_t *_get_caps_ifc(port_t port_num) {
|
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(_io_plugins); i++) {
|
|
if (_io_plugins[i].port_num == port_num) {
|
|
if (_io_plugins[i].io_plugin == NULL) {
|
|
return NULL;
|
|
}
|
|
if (_io_plugins[i].io_plugin->getCapsIFC == NULL) {
|
|
return NULL;
|
|
} else {
|
|
return (_io_plugins[i].io_plugin->getCapsIFC(&_wprint_ifc));
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const ifc_status_monitor_t *_get_status_ifc(port_t port_num) {
|
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(_io_plugins); i++) {
|
|
if (_io_plugins[i].port_num == port_num) {
|
|
if (_io_plugins[i].io_plugin == NULL) {
|
|
return NULL;
|
|
}
|
|
if (_io_plugins[i].io_plugin->getStatusIFC == NULL) {
|
|
return NULL;
|
|
} else {
|
|
return (_io_plugins[i].io_plugin->getStatusIFC(&_wprint_ifc));
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const ifc_print_job_t *_get_print_ifc(port_t port_num) {
|
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(_io_plugins); i++) {
|
|
if (_io_plugins[i].port_num == port_num) {
|
|
if (_io_plugins[i].io_plugin == NULL) {
|
|
return NULL;
|
|
}
|
|
if (_io_plugins[i].io_plugin->getPrintIFC == NULL) {
|
|
return NULL;
|
|
} else {
|
|
return (_io_plugins[i].io_plugin->getPrintIFC(&_wprint_ifc));
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Lock the semaphore for this module
|
|
*/
|
|
static void _lock(void) {
|
|
pthread_mutex_lock(&_q_lock);
|
|
}
|
|
|
|
/*
|
|
* Unlock the semaphore for this module
|
|
*/
|
|
static void _unlock(void) {
|
|
pthread_mutex_unlock(&_q_lock);
|
|
}
|
|
|
|
static wJob_t _get_handle(void) {
|
|
static unsigned long _running_number = 0;
|
|
wJob_t job_handle = WPRINT_BAD_JOB_HANDLE;
|
|
int i, index, size;
|
|
char *ptr;
|
|
|
|
for (i = 0; i < _MAX_SPOOLED_JOBS; i++) {
|
|
index = (i + _running_number) % _MAX_SPOOLED_JOBS;
|
|
|
|
if (_job_queue[index].job_state == JOB_STATE_FREE) {
|
|
size = MAX_MIME_LENGTH + MAX_PRINTER_ADDR_LENGTH + MAX_PATHNAME_LENGTH + 4;
|
|
ptr = malloc(size);
|
|
if (ptr) {
|
|
memset(&_job_queue[index], 0, sizeof(_job_queue_t));
|
|
memset(ptr, 0, size);
|
|
|
|
_job_queue[index].job_debug_fd = -1;
|
|
_job_queue[index].page_debug_fd = -1;
|
|
_job_queue[index].printer_addr = ptr;
|
|
|
|
ptr += (MAX_PRINTER_ADDR_LENGTH + 1);
|
|
_job_queue[index].mime_type = ptr;
|
|
ptr += (MAX_MIME_LENGTH + 1);
|
|
_job_queue[index].pathname = ptr;
|
|
|
|
_job_queue[index].job_state = JOB_STATE_QUEUED;
|
|
_job_queue[index].job_handle = _ENCODE_HANDLE(index);
|
|
|
|
job_handle = _job_queue[index].job_handle;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return job_handle;
|
|
}
|
|
|
|
static int _recycle_handle(wJob_t job_handle) {
|
|
_job_queue_t *jq = _get_job_desc(job_handle);
|
|
|
|
if (jq == NULL) {
|
|
return ERROR;
|
|
} else if ((jq->job_state == JOB_STATE_CANCELLED) || (jq->job_state == JOB_STATE_ERROR) ||
|
|
(jq->job_state == JOB_STATE_CORRUPTED) || (jq->job_state == JOB_STATE_COMPLETED)) {
|
|
if (jq->print_ifc != NULL) {
|
|
jq->print_ifc->destroy(jq->print_ifc);
|
|
}
|
|
|
|
jq->print_ifc = NULL;
|
|
if (jq->status_ifc != NULL) {
|
|
jq->status_ifc->destroy(jq->status_ifc);
|
|
}
|
|
jq->status_ifc = NULL;
|
|
if (jq->job_params.useragent != NULL) {
|
|
free((void *) jq->job_params.useragent);
|
|
}
|
|
if (jq->job_params.certificate != NULL) {
|
|
free((void *) jq->job_params.certificate);
|
|
}
|
|
free(jq->printer_addr);
|
|
jq->job_state = JOB_STATE_FREE;
|
|
if (jq->job_debug_fd != -1) {
|
|
close(jq->job_debug_fd);
|
|
}
|
|
jq->job_debug_fd = -1;
|
|
if (jq->page_debug_fd != -1) {
|
|
close(jq->page_debug_fd);
|
|
}
|
|
jq->page_debug_fd = -1;
|
|
jq->debug_path[0] = 0;
|
|
if (jq->certificate) {
|
|
free(jq->certificate);
|
|
jq->certificate = NULL;
|
|
}
|
|
return OK;
|
|
} else {
|
|
return ERROR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Stops the job status thread if it exists
|
|
*/
|
|
static int _stop_status_thread(_job_queue_t *jq) {
|
|
if (!pthread_equal(_job_status_tid, pthread_self()) && (jq && jq->status_ifc)) {
|
|
(jq->status_ifc->stop)(jq->status_ifc);
|
|
_unlock();
|
|
pthread_join(_job_status_tid, 0);
|
|
_lock();
|
|
_job_status_tid = pthread_self();
|
|
return OK;
|
|
} else {
|
|
return ERROR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handles a new status message from the printer. Based on the status of wprint and the printer,
|
|
* this function will start/end a job, send another page, or return blocking errors.
|
|
*/
|
|
static void _job_status_callback(const printer_state_dyn_t *new_status,
|
|
const printer_state_dyn_t *old_status, void *param) {
|
|
wprint_job_callback_params_t cb_param;
|
|
_job_queue_t *jq = (_job_queue_t *) param;
|
|
unsigned int i, blocked_reasons;
|
|
print_status_t statusnew, statusold;
|
|
|
|
statusnew = new_status->printer_status & ~PRINTER_IDLE_BIT;
|
|
statusold = old_status->printer_status & ~PRINTER_IDLE_BIT;
|
|
cb_param.certificate = jq->certificate;
|
|
cb_param.certificate_len = jq->certificate_len;
|
|
|
|
LOGD("_job_status_callback(): current printer state: %d", statusnew);
|
|
blocked_reasons = 0;
|
|
for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
|
|
if (new_status->printer_reasons[i] == PRINT_STATUS_MAX_STATE) {
|
|
break;
|
|
}
|
|
LOGD("_job_status_callback(): blocking reason %d: %d", i, new_status->printer_reasons[i]);
|
|
blocked_reasons |= (1 << new_status->printer_reasons[i]);
|
|
}
|
|
|
|
switch (statusnew) {
|
|
case PRINT_STATUS_UNKNOWN:
|
|
if ((new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE)
|
|
|| (new_status->printer_reasons[0] == PRINT_STATUS_UNKNOWN)) {
|
|
sem_post(&_job_start_wait_sem);
|
|
sem_post(&_job_end_wait_sem);
|
|
_lock();
|
|
if ((new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE)
|
|
&& ((jq->print_ifc != NULL) && (jq->print_ifc->enable_timeout != NULL))) {
|
|
jq->print_ifc->enable_timeout(jq->print_ifc, 1);
|
|
}
|
|
_unlock();
|
|
}
|
|
break;
|
|
|
|
case PRINT_STATUS_IDLE:
|
|
if ((statusold > PRINT_STATUS_IDLE) || (statusold == PRINT_STATUS_CANCELLED)) {
|
|
// Print is over but the job wasn't ended correctly
|
|
if (jq->is_dir && !jq->last_page_seen) {
|
|
wprintPage(jq->job_handle, jq->num_pages + 1, NULL, true, false, 0, 0, 0, 0);
|
|
}
|
|
sem_post(&_job_end_wait_sem);
|
|
}
|
|
break;
|
|
|
|
case PRINT_STATUS_CANCELLED:
|
|
sem_post(&_job_start_wait_sem);
|
|
if ((jq->print_ifc != NULL) && (jq->print_ifc->enable_timeout != NULL)) {
|
|
jq->print_ifc->enable_timeout(jq->print_ifc, 1);
|
|
}
|
|
if (statusold != PRINT_STATUS_CANCELLED) {
|
|
LOGI("status requested job cancel");
|
|
if (new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE) {
|
|
sem_post(&_job_start_wait_sem);
|
|
sem_post(&_job_end_wait_sem);
|
|
if ((jq->print_ifc != NULL) && (jq->print_ifc->enable_timeout != NULL)) {
|
|
jq->print_ifc->enable_timeout(jq->print_ifc, 1);
|
|
}
|
|
}
|
|
_lock();
|
|
jq->job_params.cancelled = true;
|
|
_unlock();
|
|
}
|
|
if (new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE) {
|
|
sem_post(&_job_start_wait_sem);
|
|
sem_post(&_job_end_wait_sem);
|
|
}
|
|
break;
|
|
|
|
case PRINT_STATUS_PRINTING:
|
|
sem_post(&_job_start_wait_sem);
|
|
_lock();
|
|
if ((jq->job_state != JOB_STATE_RUNNING) || (jq->blocked_reasons != blocked_reasons)) {
|
|
jq->job_state = JOB_STATE_RUNNING;
|
|
jq->blocked_reasons = blocked_reasons;
|
|
if (jq->cb_fn) {
|
|
cb_param.state = JOB_RUNNING;
|
|
cb_param.blocked_reasons = jq->blocked_reasons;
|
|
cb_param.job_done_result = OK;
|
|
|
|
jq->cb_fn(jq->job_handle, (void *) &cb_param);
|
|
}
|
|
}
|
|
_unlock();
|
|
break;
|
|
|
|
case PRINT_STATUS_UNABLE_TO_CONNECT:
|
|
sem_post(&_job_start_wait_sem);
|
|
_lock();
|
|
_stop_status_thread(jq);
|
|
|
|
jq->blocked_reasons = blocked_reasons;
|
|
jq->job_params.cancelled = true;
|
|
jq->job_state = JOB_STATE_ERROR;
|
|
if (jq->cb_fn) {
|
|
cb_param.state = JOB_DONE;
|
|
cb_param.blocked_reasons = blocked_reasons;
|
|
cb_param.job_done_result = ERROR;
|
|
|
|
jq->cb_fn(jq->job_handle, (void *) &cb_param);
|
|
}
|
|
|
|
if (jq->print_ifc != NULL) {
|
|
jq->print_ifc->destroy(jq->print_ifc);
|
|
jq->print_ifc = NULL;
|
|
}
|
|
|
|
if (jq->status_ifc != NULL) {
|
|
jq->status_ifc->destroy(jq->status_ifc);
|
|
jq->status_ifc = NULL;
|
|
}
|
|
|
|
_unlock();
|
|
sem_post(&_job_end_wait_sem);
|
|
break;
|
|
|
|
default:
|
|
// an error has occurred, report it back to the client
|
|
sem_post(&_job_start_wait_sem);
|
|
_lock();
|
|
|
|
if ((jq->job_state != JOB_STATE_BLOCKED) || (jq->blocked_reasons != blocked_reasons)) {
|
|
jq->job_state = JOB_STATE_BLOCKED;
|
|
jq->blocked_reasons = blocked_reasons;
|
|
if (jq->cb_fn) {
|
|
cb_param.state = JOB_BLOCKED;
|
|
cb_param.blocked_reasons = blocked_reasons;
|
|
cb_param.job_done_result = OK;
|
|
|
|
jq->cb_fn(jq->job_handle, (void *) &cb_param);
|
|
}
|
|
}
|
|
_unlock();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void *_job_status_thread(void *param) {
|
|
_job_queue_t *jq = (_job_queue_t *) param;
|
|
(jq->status_ifc->start)(jq->status_ifc, _job_status_callback, param);
|
|
return NULL;
|
|
}
|
|
|
|
static int _start_status_thread(_job_queue_t *jq) {
|
|
sigset_t allsig, oldsig;
|
|
int result = ERROR;
|
|
|
|
if ((jq == NULL) || (jq->status_ifc == NULL)) {
|
|
return result;
|
|
}
|
|
|
|
result = OK;
|
|
sigfillset(&allsig);
|
|
#if CHECK_PTHREAD_SIGMASK_STATUS
|
|
result = pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
|
|
#else // else CHECK_PTHREAD_SIGMASK_STATUS
|
|
pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
|
|
#endif // CHECK_PTHREAD_SIGMASK_STATUS
|
|
if (result == OK) {
|
|
result = pthread_create(&_job_status_tid, 0, _job_status_thread, jq);
|
|
if ((result == ERROR) && (_job_status_tid != pthread_self())) {
|
|
#if USE_PTHREAD_CANCEL
|
|
pthread_cancel(_job_status_tid);
|
|
#else // else USE_PTHREAD_CANCEL
|
|
pthread_kill(_job_status_tid, SIGKILL);
|
|
#endif // USE_PTHREAD_CANCEL
|
|
_job_status_tid = pthread_self();
|
|
}
|
|
}
|
|
|
|
if (result == OK) {
|
|
sched_yield();
|
|
#if CHECK_PTHREAD_SIGMASK_STATUS
|
|
result = pthread_sigmask(SIG_SETMASK, &oldsig, 0);
|
|
#else // else CHECK_PTHREAD_SIGMASK_STATUS
|
|
pthread_sigmask(SIG_SETMASK, &oldsig, 0);
|
|
#endif // CHECK_PTHREAD_SIGMASK_STATUS
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Return true unless the server gave an unexpected certificate
|
|
*/
|
|
static bool _is_certificate_allowed(_job_queue_t *jq) {
|
|
int result = true;
|
|
|
|
// Compare certificates if both are known
|
|
if (jq->job_params.certificate && jq->certificate) {
|
|
if (jq->job_params.certificate_len != jq->certificate_len) {
|
|
LOGD("_is_certificate_allowed: certificate length mismatch allowed=%d, received=%d",
|
|
jq->job_params.certificate_len, jq->certificate_len);
|
|
result = false;
|
|
} else if (0 != memcmp(jq->job_params.certificate, jq->certificate, jq->certificate_len)) {
|
|
LOGD("_is_certificate_allowed: certificate content mismatch");
|
|
result = false;
|
|
} else {
|
|
LOGD("_is_certificate_allowed: certificate match, len=%d",
|
|
jq->job_params.certificate_len);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Callback from lower layers containing certificate data, if any.
|
|
*/
|
|
static int _validate_certificate(wprint_connect_info_t *connect_info, uint8 *data, int data_len) {
|
|
_job_queue_t *jq = connect_info->user;
|
|
LOGD("_validate_certificate: %s://%s:%d%s handling server cert len=%d for job %ld",
|
|
connect_info->uri_scheme, connect_info->printer_addr, connect_info->port_num,
|
|
connect_info->uri_path, data_len, jq->job_handle);
|
|
|
|
// Free any old certificate we have and save new certificate data
|
|
if (jq->certificate) {
|
|
free(jq->certificate);
|
|
jq->certificate = NULL;
|
|
}
|
|
jq->certificate = (uint8 *)malloc(data_len);
|
|
int error = 0;
|
|
if (jq->certificate == NULL) {
|
|
LOGD("_validate_certificate: malloc failed");
|
|
error = -1;
|
|
} else {
|
|
memcpy(jq->certificate, data, data_len);
|
|
jq->certificate_len = data_len;
|
|
if (!_is_certificate_allowed(jq)) {
|
|
LOGD("_validate_certificate: received certificate disallowed.");
|
|
error = -1;
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Initialize the status interface (so we can use it to query for printer status.
|
|
*/
|
|
static void _initialize_status_ifc(_job_queue_t *jq) {
|
|
wprint_connect_info_t connect_info;
|
|
connect_info.printer_addr = jq->printer_addr;
|
|
connect_info.uri_path = jq->printer_uri;
|
|
connect_info.port_num = jq->port_num;
|
|
if (jq->use_secure_uri) {
|
|
connect_info.uri_scheme = IPPS_PREFIX;
|
|
connect_info.user = jq;
|
|
connect_info.validate_certificate = _validate_certificate;
|
|
} else {
|
|
connect_info.uri_scheme = IPP_PREFIX;
|
|
connect_info.validate_certificate = NULL;
|
|
}
|
|
connect_info.timeout = DEFAULT_IPP_TIMEOUT;
|
|
|
|
// Initialize the status interface with this connection info
|
|
jq->status_ifc->init(jq->status_ifc, &connect_info);
|
|
}
|
|
|
|
/*
|
|
* Runs a print job. Contains logic for what to do given different printer statuses.
|
|
*/
|
|
static void *_job_thread(void *param) {
|
|
wprint_job_callback_params_t cb_param = { 0 };
|
|
_msg_t msg;
|
|
wJob_t job_handle;
|
|
_job_queue_t *jq;
|
|
_page_t page;
|
|
int i;
|
|
status_t job_result;
|
|
int corrupted = 0;
|
|
|
|
while (OK == msgQReceive(_msgQ, (char *) &msg, sizeof(msg), WAIT_FOREVER)) {
|
|
if (msg.id == MSG_RUN_JOB) {
|
|
LOGI("_job_thread(): Received message: MSG_RUN_JOB");
|
|
} else {
|
|
LOGI("_job_thread(): Received message: MSG_QUIT");
|
|
}
|
|
|
|
if (msg.id == MSG_QUIT) {
|
|
break;
|
|
}
|
|
|
|
job_handle = msg.job_id;
|
|
|
|
// check if this is a valid job_handle that is still active
|
|
_lock();
|
|
|
|
jq = _get_job_desc(job_handle);
|
|
|
|
// set state to running and invoke the plugin, there is one
|
|
if (jq) {
|
|
if (jq->job_state != JOB_STATE_QUEUED) {
|
|
_unlock();
|
|
continue;
|
|
}
|
|
corrupted = 0;
|
|
job_result = OK;
|
|
jq->job_params.plugin_data = NULL;
|
|
|
|
// clear out the semaphore just in case
|
|
while (sem_trywait(&_job_start_wait_sem) == OK) {
|
|
}
|
|
while (sem_trywait(&_job_end_wait_sem) == OK) {
|
|
}
|
|
|
|
// initialize the status ifc
|
|
if (jq->status_ifc != NULL) {
|
|
_initialize_status_ifc(jq);
|
|
}
|
|
// wait for the printer to be idle
|
|
if ((jq->status_ifc != NULL) && (jq->status_ifc->get_status != NULL)) {
|
|
int retry = 0;
|
|
bool idle = false;
|
|
printer_state_dyn_t printer_state;
|
|
while (!idle) {
|
|
print_status_t status;
|
|
jq->status_ifc->get_status(jq->status_ifc, &printer_state);
|
|
status = printer_state.printer_status & ~PRINTER_IDLE_BIT;
|
|
|
|
// Pass along any certificate received in future callbacks
|
|
cb_param.certificate = jq->certificate;
|
|
cb_param.certificate_len = jq->certificate_len;
|
|
|
|
// Presume we found an idle state
|
|
idle = true;
|
|
if (status == PRINT_STATUS_IDLE) {
|
|
printer_state.printer_status = PRINT_STATUS_IDLE;
|
|
jq->blocked_reasons = 0;
|
|
} else if (status == PRINT_STATUS_UNKNOWN
|
|
&& printer_state.printer_reasons[0] == PRINT_STATUS_UNKNOWN) {
|
|
// no status available, break out and hope for the best
|
|
printer_state.printer_status = PRINT_STATUS_IDLE;
|
|
} else if ((status == PRINT_STATUS_UNKNOWN || status == PRINT_STATUS_SVC_REQUEST)
|
|
&& ((printer_state.printer_reasons[0] == PRINT_STATUS_UNABLE_TO_CONNECT)
|
|
|| (printer_state.printer_reasons[0] == PRINT_STATUS_OFFLINE))) {
|
|
if (_is_certificate_allowed(jq)) {
|
|
LOGD("%s: Received an Unable to Connect message", __func__);
|
|
jq->blocked_reasons = BLOCKED_REASON_UNABLE_TO_CONNECT;
|
|
} else {
|
|
LOGD("%s: Bad certificate", __func__);
|
|
jq->blocked_reasons = BLOCKED_REASON_BAD_CERTIFICATE;
|
|
}
|
|
} else if (printer_state.printer_status & PRINTER_IDLE_BIT) {
|
|
LOGD("%s: printer blocked but appears to be in an idle state. "
|
|
"Allowing job to proceed", __func__);
|
|
printer_state.printer_status = PRINT_STATUS_IDLE;
|
|
} else if (retry >= MAX_IDLE_WAIT) {
|
|
jq->blocked_reasons |= BLOCKED_REASONS_PRINTER_BUSY;
|
|
} else if (!jq->job_params.cancelled) {
|
|
// Printer still appears busy, so stay in loop, notify, and poll again.
|
|
idle = false;
|
|
int blocked_reasons = 0;
|
|
for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
|
|
if (printer_state.printer_reasons[i] == PRINT_STATUS_MAX_STATE) {
|
|
break;
|
|
}
|
|
blocked_reasons |= (1 << printer_state.printer_reasons[i]);
|
|
}
|
|
if (blocked_reasons == 0) {
|
|
blocked_reasons |= BLOCKED_REASONS_PRINTER_BUSY;
|
|
}
|
|
|
|
if ((jq->job_state != JOB_STATE_BLOCKED)
|
|
|| (jq->blocked_reasons != blocked_reasons)) {
|
|
jq->job_state = JOB_STATE_BLOCKED;
|
|
jq->blocked_reasons = blocked_reasons;
|
|
if (jq->cb_fn) {
|
|
cb_param.state = JOB_BLOCKED;
|
|
cb_param.blocked_reasons = blocked_reasons;
|
|
cb_param.job_done_result = OK;
|
|
|
|
jq->cb_fn(jq->job_handle, (void *) &cb_param);
|
|
}
|
|
}
|
|
_unlock();
|
|
sleep(1);
|
|
_lock();
|
|
retry++;
|
|
}
|
|
}
|
|
|
|
if (jq->job_params.cancelled) {
|
|
job_result = CANCELLED;
|
|
} else {
|
|
job_result = (((printer_state.printer_status & ~PRINTER_IDLE_BIT) ==
|
|
PRINT_STATUS_IDLE) ? OK : ERROR);
|
|
}
|
|
}
|
|
|
|
_job_status_tid = pthread_self();
|
|
if (job_result == OK) {
|
|
if (jq->print_ifc) {
|
|
job_result = jq->print_ifc->init(jq->print_ifc, jq->printer_addr,
|
|
jq->port_num, jq->printer_uri, jq->use_secure_uri);
|
|
if (job_result == ERROR) {
|
|
jq->blocked_reasons = BLOCKED_REASON_UNABLE_TO_CONNECT;
|
|
}
|
|
}
|
|
}
|
|
if (job_result == OK) {
|
|
_start_status_thread(jq);
|
|
}
|
|
|
|
/* call the plugin's start_job method, if no other job is running
|
|
use callback to notify the client */
|
|
|
|
if ((job_result == OK) && jq->cb_fn) {
|
|
cb_param.state = JOB_RUNNING;
|
|
cb_param.blocked_reasons = 0;
|
|
cb_param.job_done_result = OK;
|
|
|
|
jq->cb_fn(job_handle, (void *) &cb_param);
|
|
}
|
|
|
|
jq->job_params.page_num = -1;
|
|
if (job_result == OK) {
|
|
if (jq->print_ifc != NULL) {
|
|
LOGD("_job_thread: Calling validate_job");
|
|
if (jq->print_ifc->validate_job != NULL) {
|
|
job_result = jq->print_ifc->validate_job(jq->print_ifc, &jq->job_params);
|
|
}
|
|
|
|
/* PDF format plugin's start_job and end_job are to be called for each copy,
|
|
* inside the for-loop for num_copies.
|
|
*/
|
|
|
|
// Do not call start_job unless validate_job returned OK
|
|
if ((job_result == OK) && (jq->print_ifc->start_job != NULL) &&
|
|
(strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) != 0)) {
|
|
jq->print_ifc->start_job(jq->print_ifc, &jq->job_params);
|
|
}
|
|
}
|
|
|
|
// Do not call start_job unless validate_job returned OK
|
|
if (job_result == OK && jq->plugin->start_job != NULL) {
|
|
job_result = jq->plugin->start_job(job_handle, (void *) &_wprint_ifc,
|
|
(void *) jq->print_ifc, &(jq->job_params));
|
|
}
|
|
}
|
|
|
|
if (job_result == OK) {
|
|
jq->job_params.page_num = 0;
|
|
}
|
|
|
|
// multi-page print job
|
|
if (jq->is_dir && (job_result == OK)) {
|
|
int per_copy_page_num;
|
|
for (i = 0; (i < jq->job_params.num_copies) &&
|
|
((job_result == OK) || (job_result == CORRUPT)) &&
|
|
(!jq->job_params.cancelled); i++) {
|
|
if ((i > 0) &&
|
|
jq->job_params.copies_supported &&
|
|
(strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0)) {
|
|
LOGD("_job_thread multi_page: breaking out copies supported");
|
|
break;
|
|
}
|
|
bool pdf_printed = false;
|
|
if (jq->print_ifc->start_job != NULL &&
|
|
(strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0)) {
|
|
jq->print_ifc->start_job(jq->print_ifc, &jq->job_params);
|
|
}
|
|
|
|
per_copy_page_num = 0;
|
|
jq->job_state = JOB_STATE_RUNNING;
|
|
|
|
// while there is a page to print
|
|
_unlock();
|
|
|
|
while (OK == msgQReceive(jq->pageQ, (char *) &page, sizeof(page),
|
|
WAIT_FOREVER)) {
|
|
_lock();
|
|
|
|
// check for any printing problems so far
|
|
if (jq->print_ifc->check_status) {
|
|
if (jq->print_ifc->check_status(jq->print_ifc) == ERROR) {
|
|
job_result = ERROR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* take empty filename as cue to break out of the loop
|
|
* but we have to do last_page processing
|
|
*/
|
|
|
|
// all copies are clubbed together as a single print job
|
|
if (page.last_page && ((i == jq->job_params.num_copies - 1) ||
|
|
(jq->job_params.copies_supported &&
|
|
strcmp(jq->job_params.print_format,
|
|
PRINT_FORMAT_PDF) == 0))) {
|
|
jq->job_params.last_page = page.last_page;
|
|
} else {
|
|
jq->job_params.last_page = false;
|
|
}
|
|
|
|
if (strlen(page.filename) > 0) {
|
|
per_copy_page_num++;
|
|
{
|
|
jq->job_params.page_num++;
|
|
}
|
|
if (page.pdf_page) {
|
|
jq->job_params.page_num = page.page_num;
|
|
} else {
|
|
jq->job_params.page_num = per_copy_page_num;
|
|
}
|
|
|
|
// setup page margin information
|
|
jq->job_params.print_top_margin += page.top_margin;
|
|
jq->job_params.print_left_margin += page.left_margin;
|
|
jq->job_params.print_right_margin += page.right_margin;
|
|
jq->job_params.print_bottom_margin += page.bottom_margin;
|
|
|
|
jq->job_params.copy_num = (i + 1);
|
|
jq->job_params.copy_page_num = page.page_num;
|
|
jq->job_params.page_backside = !(per_copy_page_num & 0x1);
|
|
jq->job_params.page_corrupted = (page.corrupted ? 1 : 0);
|
|
jq->job_params.page_printing = true;
|
|
_unlock();
|
|
|
|
if (!page.corrupted) {
|
|
LOGD("_job_thread(): page not corrupt, calling plugin's print_page"
|
|
" function for page #%d", page.page_num);
|
|
if (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) != 0) {
|
|
job_result = jq->plugin->print_page(&(jq->job_params),
|
|
jq->mime_type,
|
|
page.filename);
|
|
} else if (!pdf_printed) {
|
|
// for PDF plugin, print_page prints entire document,
|
|
// so need to be called only once
|
|
job_result = jq->plugin->print_page(&(jq->job_params),
|
|
jq->mime_type,
|
|
page.filename);
|
|
pdf_printed = true;
|
|
}
|
|
} else {
|
|
LOGD("_job_thread(): page IS corrupt, printing blank page for "
|
|
"page #%d", page.page_num);
|
|
job_result = CORRUPT;
|
|
if ((jq->job_params.duplex != DUPLEX_MODE_NONE) &&
|
|
(jq->plugin->print_blank_page != NULL)) {
|
|
jq->plugin->print_blank_page(job_handle, &(jq->job_params));
|
|
}
|
|
}
|
|
_lock();
|
|
|
|
jq->job_params.print_top_margin -= page.top_margin;
|
|
jq->job_params.print_left_margin -= page.left_margin;
|
|
jq->job_params.print_right_margin -= page.right_margin;
|
|
jq->job_params.print_bottom_margin -= page.bottom_margin;
|
|
jq->job_params.page_printing = false;
|
|
|
|
// make sure we only count corrupted pages once
|
|
if (page.corrupted == false) {
|
|
page.corrupted = ((job_result == CORRUPT) ? true : false);
|
|
corrupted += (job_result == CORRUPT);
|
|
}
|
|
}
|
|
|
|
// make sure we always print an even number of pages in duplex jobs
|
|
if (page.last_page && (jq->job_params.duplex != DUPLEX_MODE_NONE)
|
|
&& !(jq->job_params.page_backside)
|
|
&& (jq->plugin->print_blank_page != NULL)) {
|
|
_unlock();
|
|
jq->plugin->print_blank_page(job_handle, &(jq->job_params));
|
|
_lock();
|
|
}
|
|
|
|
// if multiple copies are requested, save the contents of the pageQ message
|
|
if (jq->saveQ && !jq->job_params.cancelled && (job_result != ERROR)) {
|
|
job_result = msgQSend(jq->saveQ, (char *) &page,
|
|
sizeof(page), NO_WAIT, MSG_Q_FIFO);
|
|
|
|
// swap pageQ and saveQ
|
|
if (page.last_page && !jq->job_params.last_page) {
|
|
msg_q_id tmpQ = jq->pageQ;
|
|
jq->pageQ = jq->saveQ;
|
|
jq->saveQ = tmpQ;
|
|
|
|
// defensive programming
|
|
while (msgQNumMsgs(tmpQ) > 0) {
|
|
msgQReceive(tmpQ, (char *) &page, sizeof(page), NO_WAIT);
|
|
LOGE("pageQ inconsistencies, discarding page #%d, file %s",
|
|
page.page_num, page.filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (page.last_page || jq->job_params.cancelled) {
|
|
// Leave the sempahore locked
|
|
break;
|
|
}
|
|
|
|
// unlock to go back to the top of the while loop
|
|
_unlock();
|
|
} // while there is another page
|
|
|
|
if ((strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0) &&
|
|
(jq->print_ifc->end_job)) {
|
|
int end_job_result = jq->print_ifc->end_job(jq->print_ifc);
|
|
if (job_result == OK) {
|
|
if (end_job_result == ERROR) {
|
|
job_result = ERROR;
|
|
} else if (end_job_result == CANCELLED) {
|
|
job_result = CANCELLED;
|
|
}
|
|
}
|
|
}
|
|
} // for each copy of the job
|
|
} else if (job_result == OK) {
|
|
// single page job
|
|
for (i = 0; ((i < jq->job_params.num_copies) && (job_result == OK)); i++) {
|
|
if ((i > 0) && jq->job_params.copies_supported &&
|
|
(strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0)) {
|
|
LOGD("_job_thread single_page: breaking out copies supported");
|
|
break;
|
|
}
|
|
|
|
// check for any printing problems so far
|
|
if ((jq->print_ifc != NULL) && (jq->print_ifc->check_status)) {
|
|
if (jq->print_ifc->check_status(jq->print_ifc) == ERROR) {
|
|
job_result = ERROR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
jq->job_state = JOB_STATE_RUNNING;
|
|
jq->job_params.page_num++;
|
|
jq->job_params.last_page = (i == (jq->job_params.num_copies - 1));
|
|
jq->job_params.copy_num = (i + 1);
|
|
jq->job_params.copy_page_num = 1;
|
|
jq->job_params.page_corrupted = (job_result == CORRUPT);
|
|
jq->job_params.page_printing = true;
|
|
|
|
_unlock();
|
|
job_result = jq->plugin->print_page(&(jq->job_params), jq->mime_type,
|
|
jq->pathname);
|
|
|
|
if ((jq->job_params.duplex != DUPLEX_MODE_NONE)
|
|
&& (jq->plugin->print_blank_page != NULL)) {
|
|
jq->plugin->print_blank_page(job_handle,
|
|
&(jq->job_params));
|
|
}
|
|
|
|
_lock();
|
|
jq->job_params.page_printing = false;
|
|
|
|
corrupted += (job_result == CORRUPT);
|
|
} // for each copy
|
|
}
|
|
|
|
// if we started the job end it
|
|
if (jq->job_params.page_num >= 0) {
|
|
// if the job was cancelled without sending anything through, print a blank sheet
|
|
if ((jq->job_params.page_num == 0)
|
|
&& (jq->plugin->print_blank_page != NULL)) {
|
|
jq->plugin->print_blank_page(job_handle, &(jq->job_params));
|
|
}
|
|
if (jq->plugin->end_job != NULL) {
|
|
jq->plugin->end_job(&(jq->job_params));
|
|
}
|
|
if ((jq->print_ifc != NULL) && (jq->print_ifc->end_job) &&
|
|
(strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) != 0)) {
|
|
int end_job_result = jq->print_ifc->end_job(jq->print_ifc);
|
|
if (job_result == OK) {
|
|
if (end_job_result == ERROR) {
|
|
job_result = ERROR;
|
|
} else if (end_job_result == CANCELLED) {
|
|
job_result = CANCELLED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we started to print, wait for idle
|
|
if ((jq->job_params.page_num > 0) && (jq->status_ifc != NULL)) {
|
|
int retry, result;
|
|
_unlock();
|
|
|
|
for (retry = 0, result = ERROR; ((result == ERROR) && (retry <= MAX_START_WAIT));
|
|
retry++) {
|
|
if (retry != 0) {
|
|
sleep(1);
|
|
}
|
|
result = sem_trywait(&_job_start_wait_sem);
|
|
}
|
|
|
|
if (result == OK) {
|
|
for (retry = 0, result = ERROR; ((result == ERROR) && (retry <= MAX_DONE_WAIT));
|
|
retry++) {
|
|
if (retry != 0) {
|
|
_lock();
|
|
if (jq->job_params.cancelled && !jq->cancel_ok) {
|
|
/* The user tried to cancel and it either didn't go through
|
|
* or the printer doesn't support cancel through an OID.
|
|
* Either way it's pointless to sit here waiting for idle when
|
|
* may never come, so we'll bail out early
|
|
*/
|
|
retry = (MAX_DONE_WAIT + 1);
|
|
}
|
|
_unlock();
|
|
sleep(1);
|
|
if (retry == MAX_DONE_WAIT) {
|
|
_lock();
|
|
if (!jq->job_params.cancelled &&
|
|
(jq->blocked_reasons
|
|
& (BLOCKED_REASON_OUT_OF_PAPER
|
|
| BLOCKED_REASON_JAMMED
|
|
| BLOCKED_REASON_DOOR_OPEN))) {
|
|
retry = (MAX_DONE_WAIT - 1);
|
|
}
|
|
_unlock();
|
|
}
|
|
}
|
|
result = sem_trywait(&_job_end_wait_sem);
|
|
}
|
|
} else {
|
|
LOGD("_job_thread(): the job never started");
|
|
}
|
|
_lock();
|
|
}
|
|
|
|
// make sure page_num doesn't stay as a negative number
|
|
jq->job_params.page_num = MAX(0, jq->job_params.page_num);
|
|
_stop_status_thread(jq);
|
|
|
|
if (corrupted != 0) {
|
|
job_result = CORRUPT;
|
|
}
|
|
|
|
LOGI("job_thread(): with job_state value: %d ", jq->job_state);
|
|
if ((jq->job_state == JOB_STATE_COMPLETED) || (jq->job_state == JOB_STATE_ERROR)
|
|
|| (jq->job_state == JOB_STATE_CANCELLED)
|
|
|| (jq->job_state == JOB_STATE_CORRUPTED)
|
|
|| (jq->job_state == JOB_STATE_FREE)) {
|
|
LOGI("_job_thread(): job finished early: do not send callback again");
|
|
} else {
|
|
switch (job_result) {
|
|
case OK:
|
|
if (!jq->job_params.cancelled) {
|
|
jq->job_state = JOB_STATE_COMPLETED;
|
|
jq->blocked_reasons = 0;
|
|
break;
|
|
} else {
|
|
job_result = CANCELLED;
|
|
}
|
|
case CANCELLED:
|
|
jq->job_state = JOB_STATE_CANCELLED;
|
|
jq->blocked_reasons = BLOCKED_REASONS_CANCELLED;
|
|
if (!jq->cancel_ok) {
|
|
jq->blocked_reasons |= BLOCKED_REASON_PARTIAL_CANCEL;
|
|
}
|
|
break;
|
|
case CORRUPT:
|
|
LOGE("_job_thread(): %d file(s) in the job were corrupted", corrupted);
|
|
jq->job_state = JOB_STATE_CORRUPTED;
|
|
jq->blocked_reasons = 0;
|
|
break;
|
|
case ERROR:
|
|
default:
|
|
LOGE("_job_thread(): ERROR plugin->start_job(%ld): %s => %s", job_handle,
|
|
jq->mime_type, jq->job_params.print_format);
|
|
job_result = ERROR;
|
|
jq->job_state = JOB_STATE_ERROR;
|
|
break;
|
|
} // job_result
|
|
|
|
// end of job callback
|
|
if (jq->cb_fn) {
|
|
cb_param.state = JOB_DONE;
|
|
cb_param.blocked_reasons = jq->blocked_reasons;
|
|
cb_param.job_done_result = job_result;
|
|
|
|
jq->cb_fn(job_handle, (void *) &cb_param);
|
|
}
|
|
|
|
if (jq->print_ifc != NULL) {
|
|
jq->print_ifc->destroy(jq->print_ifc);
|
|
jq->print_ifc = NULL;
|
|
}
|
|
|
|
if (jq->status_ifc != NULL) {
|
|
jq->status_ifc->destroy(jq->status_ifc);
|
|
jq->status_ifc = NULL;
|
|
}
|
|
}
|
|
} else {
|
|
LOGI("_job_thread(): job %ld not in queue .. maybe cancelled", job_handle);
|
|
}
|
|
|
|
_unlock();
|
|
LOGI("_job_thread(): job finished: %ld", job_handle);
|
|
}
|
|
|
|
sem_post(&_job_end_wait_sem);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Starts the wprint background job thread
|
|
*/
|
|
static int _start_thread(void) {
|
|
sigset_t allsig, oldsig;
|
|
int result;
|
|
|
|
_job_tid = pthread_self();
|
|
|
|
result = OK;
|
|
sigfillset(&allsig);
|
|
#if CHECK_PTHREAD_SIGMASK_STATUS
|
|
result = pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
|
|
#else // else CHECK_PTHREAD_SIGMASK_STATUS
|
|
pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
|
|
#endif // CHECK_PTHREAD_SIGMASK_STATUS
|
|
if (result == OK) {
|
|
result = pthread_create(&_job_tid, 0, _job_thread, NULL);
|
|
if ((result == ERROR) && (_job_tid != pthread_self())) {
|
|
#if USE_PTHREAD_CANCEL
|
|
pthread_cancel(_job_tid);
|
|
#else // else USE_PTHREAD_CANCEL
|
|
pthread_kill(_job_tid, SIGKILL);
|
|
#endif // USE_PTHREAD_CANCEL
|
|
_job_tid = pthread_self();
|
|
}
|
|
}
|
|
|
|
if (result == OK) {
|
|
sched_yield();
|
|
#if CHECK_PTHREAD_SIGMASK_STATUS
|
|
result = pthread_sigmask(SIG_SETMASK, &oldsig, 0);
|
|
#else // else CHECK_PTHREAD_SIGMASK_STATUS
|
|
pthread_sigmask(SIG_SETMASK, &oldsig, 0);
|
|
#endif // CHECK_PTHREAD_SIGMASK_STATUS
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Waits for the job thread to reach a stopped state
|
|
*/
|
|
static int _stop_thread(void) {
|
|
if (!pthread_equal(_job_tid, pthread_self())) {
|
|
pthread_join(_job_tid, 0);
|
|
_job_tid = pthread_self();
|
|
return OK;
|
|
} else {
|
|
return ERROR;
|
|
}
|
|
}
|
|
|
|
static const wprint_io_plugin_t _file_io_plugin = {
|
|
.version = WPRINT_PLUGIN_VERSION(_INTERFACE_MINOR_VERSION),
|
|
.port_num = PORT_FILE, .getCapsIFC = NULL, .getStatusIFC = NULL,
|
|
.getPrintIFC = _printer_file_connect,};
|
|
|
|
static const wprint_io_plugin_t _ipp_io_plugin = {
|
|
.version = WPRINT_PLUGIN_VERSION(_INTERFACE_MINOR_VERSION),
|
|
.port_num = PORT_IPP, .getCapsIFC = ipp_status_get_capabilities_ifc,
|
|
.getStatusIFC = ipp_status_get_monitor_ifc, .getPrintIFC = ipp_get_print_ifc,};
|
|
|
|
static void _setup_io_plugins() {
|
|
_io_plugins[0].port_num = PORT_FILE;
|
|
_io_plugins[0].io_plugin = &_file_io_plugin;
|
|
|
|
_io_plugins[1].port_num = PORT_IPP;
|
|
_io_plugins[1].io_plugin = &_ipp_io_plugin;
|
|
}
|
|
|
|
extern wprint_plugin_t *libwprintplugin_pcl_reg(void);
|
|
|
|
extern wprint_plugin_t *libwprintplugin_pdf_reg(void);
|
|
|
|
static void _setup_print_plugins() {
|
|
plugin_reset();
|
|
plugin_add(libwprintplugin_pcl_reg());
|
|
plugin_add(libwprintplugin_pdf_reg());
|
|
}
|
|
|
|
bool wprintIsRunning() {
|
|
return _msgQ != 0;
|
|
}
|
|
|
|
int wprintInit(void) {
|
|
int count = 0;
|
|
|
|
_setup_print_plugins();
|
|
_setup_io_plugins();
|
|
|
|
_msgQ = msgQCreate(_MAX_MSGS, sizeof(_msg_t));
|
|
|
|
if (!_msgQ) {
|
|
LOGE("ERROR: cannot create msgQ");
|
|
return ERROR;
|
|
}
|
|
|
|
sem_init(&_job_end_wait_sem, 0, 0);
|
|
sem_init(&_job_start_wait_sem, 0, 0);
|
|
|
|
signal(SIGPIPE, SIG_IGN); // avoid broken pipe process shutdowns
|
|
pthread_mutexattr_settype(&_q_lock_attr, PTHREAD_MUTEX_RECURSIVE_NP);
|
|
pthread_mutex_init(&_q_lock, &_q_lock_attr);
|
|
|
|
if (_start_thread() != OK) {
|
|
LOGE("could not start job thread");
|
|
return ERROR;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static const printer_capabilities_t _default_cap = {.color = true, .borderless = true,
|
|
.numSupportedMediaSizes = 0, .numSupportedMediaTrays = 0,
|
|
.numSupportedMediaTypes = 0,};
|
|
|
|
/*
|
|
* Check if a media size is supported
|
|
*/
|
|
static bool is_supported(media_size_t media_size) {
|
|
int i;
|
|
for (i = 0; i < SUPPORTED_MEDIA_SIZE_COUNT; i++) {
|
|
if (SupportedMediaSizes[i].media_size == media_size) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Return true if the specified int array of the supplied length contains a value.
|
|
*/
|
|
static bool int_array_contains(const int *array, int length, int value) {
|
|
for (int i = 0; i < length; i++) {
|
|
if (array[i] == value) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Checks printers reported media sizes and validates that wprint supports them
|
|
*/
|
|
static void _validate_supported_media_sizes(printer_capabilities_t *printer_cap) {
|
|
if (printer_cap == NULL) return;
|
|
|
|
if (printer_cap->numSupportedMediaSizes == 0) {
|
|
unsigned int i = 0;
|
|
printer_cap->supportedMediaSizes[i++] = ISO_A4;
|
|
printer_cap->supportedMediaSizes[i++] = US_LETTER;
|
|
printer_cap->supportedMediaSizes[i++] = INDEX_CARD_4X6;
|
|
printer_cap->supportedMediaSizes[i++] = INDEX_CARD_5X7;
|
|
printer_cap->numSupportedMediaSizes = i;
|
|
} else {
|
|
unsigned int read, write;
|
|
for (read = write = 0; read < printer_cap->numSupportedMediaSizes; read++) {
|
|
if (is_supported(printer_cap->supportedMediaSizes[read])) {
|
|
printer_cap->supportedMediaSizes[write++] =
|
|
printer_cap->supportedMediaSizes[read];
|
|
}
|
|
}
|
|
printer_cap->numSupportedMediaSizes = write;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Checks printers numSupportedMediaTrays. If none, then add Auto.
|
|
*/
|
|
static void _validate_supported_media_trays(printer_capabilities_t *printer_cap) {
|
|
if (printer_cap == NULL) return;
|
|
|
|
if (printer_cap->numSupportedMediaTrays == 0) {
|
|
printer_cap->supportedMediaTrays[0] = TRAY_SRC_AUTO_SELECT;
|
|
printer_cap->numSupportedMediaTrays = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add a printer's supported input formats to the capabilities struct
|
|
*/
|
|
static void _collect_supported_input_formats(printer_capabilities_t *printer_caps) {
|
|
unsigned long long input_formats = 0;
|
|
plugin_get_passthru_input_formats(&input_formats);
|
|
|
|
// remove things the printer can't support
|
|
if (!printer_caps->canPrintPDF) {
|
|
input_formats &= ~(1 << INPUT_MIME_TYPE_PDF);
|
|
}
|
|
if (!printer_caps->canPrintPCLm) {
|
|
input_formats &= ~(1 << INPUT_MIME_TYPE_PCLM);
|
|
}
|
|
if (!printer_caps->canPrintPWG) {
|
|
input_formats &= ~(1 << INPUT_MIME_TYPE_PWG);
|
|
}
|
|
printer_caps->supportedInputMimeTypes = input_formats;
|
|
}
|
|
|
|
/*
|
|
* Check the print resolutions supported by the printer and verify that wprint supports them.
|
|
* If nothing is found, the desired resolution is selected.
|
|
*/
|
|
static unsigned int _findCloseResolutionSupported(int desiredResolution, int maxResolution,
|
|
const printer_capabilities_t *printer_cap) {
|
|
int closeResolution = 0;
|
|
int closeDifference = 0;
|
|
unsigned int index = 0;
|
|
for (index = 0; index < printer_cap->numSupportedResolutions; index++) {
|
|
int resolution = printer_cap->supportedResolutions[index];
|
|
if (resolution == desiredResolution) {
|
|
// An exact match wins.. stop looking.
|
|
return resolution;
|
|
} else {
|
|
int difference = abs(desiredResolution - resolution);
|
|
if ((closeResolution == 0) || (difference < closeDifference)) {
|
|
if (resolution <= maxResolution) {
|
|
// We found a better match now.. record it but keep looking.
|
|
closeResolution = resolution;
|
|
closeDifference = difference;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we get here we did not find an exact match.
|
|
if (closeResolution == 0) {
|
|
// We did not find anything.. just pick the desired value.
|
|
closeResolution = desiredResolution;
|
|
}
|
|
return closeResolution;
|
|
}
|
|
|
|
status_t wprintGetCapabilities(const wprint_connect_info_t *connect_info,
|
|
printer_capabilities_t *printer_cap) {
|
|
LOGD("wprintGetCapabilities: Enter");
|
|
status_t result = ERROR;
|
|
int index;
|
|
int port_num = connect_info->port_num;
|
|
const ifc_printer_capabilities_t *caps_ifc = NULL;
|
|
|
|
memcpy(printer_cap, &_default_cap, sizeof(printer_capabilities_t));
|
|
|
|
caps_ifc = _get_caps_ifc(((port_num == 0) ? PORT_FILE : PORT_IPP));
|
|
LOGD("wprintGetCapabilities: after getting caps ifc: %p", caps_ifc);
|
|
switch (port_num) {
|
|
case PORT_FILE:
|
|
printer_cap->duplex = 1;
|
|
printer_cap->borderless = 1;
|
|
printer_cap->canPrintPCLm = (_default_pcl_type == PCLm);
|
|
printer_cap->canPrintPWG = (_default_pcl_type == PCLPWG);
|
|
printer_cap->stripHeight = STRIPE_HEIGHT;
|
|
result = OK;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (caps_ifc != NULL) {
|
|
caps_ifc->init(caps_ifc, connect_info);
|
|
result = caps_ifc->get_capabilities(caps_ifc, printer_cap);
|
|
caps_ifc->destroy(caps_ifc);
|
|
}
|
|
|
|
_validate_supported_media_sizes(printer_cap);
|
|
_collect_supported_input_formats(printer_cap);
|
|
_validate_supported_media_trays(printer_cap);
|
|
|
|
printer_cap->isSupported = (printer_cap->canPrintPCLm || printer_cap->canPrintPDF ||
|
|
printer_cap->canPrintPWG);
|
|
|
|
if (result == OK) {
|
|
LOGD("\tmake: %s", printer_cap->make);
|
|
LOGD("\thas color: %d", printer_cap->color);
|
|
LOGD("\tcan duplex: %d", printer_cap->duplex);
|
|
LOGD("\tcan rotate back page: %d", printer_cap->canRotateDuplexBackPage);
|
|
LOGD("\tcan print borderless: %d", printer_cap->borderless);
|
|
LOGD("\tcan print pdf: %d", printer_cap->canPrintPDF);
|
|
LOGD("\tcan print pclm: %d", printer_cap->canPrintPCLm);
|
|
LOGD("\tcan print pwg: %d", printer_cap->canPrintPWG);
|
|
LOGD("\tsource application name supported: %d", printer_cap->docSourceAppName);
|
|
LOGD("\tsource application version supported: %d", printer_cap->docSourceAppVersion);
|
|
LOGD("\tsource os name supported: %d", printer_cap->docSourceOsName);
|
|
LOGD("\tsource os version supported: %d", printer_cap->docSourceOsVersion);
|
|
LOGD("\tprinter supported: %d", printer_cap->isSupported);
|
|
LOGD("\tstrip height: %d", printer_cap->stripHeight);
|
|
LOGD("\tinkjet: %d", printer_cap->inkjet);
|
|
LOGD("\tresolutions supported:");
|
|
for (index = 0; index < printer_cap->numSupportedResolutions; index++) {
|
|
LOGD("\t (%d dpi)", printer_cap->supportedResolutions[index]);
|
|
}
|
|
}
|
|
LOGD("wprintGetCapabilities: Exit");
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Returns a preferred print format supported by the printer
|
|
*/
|
|
static char *_get_print_format(const char *mime_type, const wprint_job_params_t *job_params,
|
|
const printer_capabilities_t *cap) {
|
|
char *print_format = NULL;
|
|
|
|
errno = OK;
|
|
|
|
if (((strcmp(mime_type, MIME_TYPE_PDF) == 0) && cap->canPrintPDF)) {
|
|
// For content type=photo and a printer that supports both PCLm and PDF,
|
|
// prefer PCLm over PDF.
|
|
if (job_params && (strcasecmp(job_params->docCategory, "photo") == 0) &&
|
|
cap->canPrintPCLm) {
|
|
print_format = PRINT_FORMAT_PCLM;
|
|
LOGI("_get_print_format(): print_format switched from PDF to PCLm");
|
|
} else {
|
|
print_format = PRINT_FORMAT_PDF;
|
|
}
|
|
} else if (cap->canPrintPCLm || cap->canPrintPDF) {
|
|
// PCLm is a subset of PDF
|
|
print_format = PRINT_FORMAT_PCLM;
|
|
#if (USE_PWG_OVER_PCLM != 0)
|
|
if (cap->canPrintPWG) {
|
|
print_format = PRINT_FORMAT_PWG;
|
|
}
|
|
#endif // (USE_PWG_OVER_PCLM != 0)
|
|
} else if (cap->canPrintPWG) {
|
|
print_format = PRINT_FORMAT_PWG;
|
|
} else {
|
|
errno = EBADRQC;
|
|
}
|
|
|
|
if (print_format != NULL) {
|
|
LOGI("\t_get_print_format(): print_format: %s", print_format);
|
|
}
|
|
|
|
return print_format;
|
|
}
|
|
|
|
status_t wprintGetDefaultJobParams(wprint_job_params_t *job_params) {
|
|
status_t result = ERROR;
|
|
static const wprint_job_params_t _default_job_params = {.print_format = _DEFAULT_PRINT_FORMAT,
|
|
.pcl_type = _DEFAULT_PCL_TYPE, .media_size = US_LETTER, .media_type = MEDIA_PLAIN,
|
|
.duplex = DUPLEX_MODE_NONE, .dry_time = DUPLEX_DRY_TIME_NORMAL,
|
|
.color_space = COLOR_SPACE_COLOR, .media_tray = TRAY_SRC_AUTO_SELECT,
|
|
.pixel_units = DEFAULT_RESOLUTION, .render_flags = 0, .num_copies =1,
|
|
.borderless = false, .cancelled = false, .renderInReverseOrder = false,
|
|
.ipp_1_0_supported = false, .ipp_2_0_supported = false, .epcl_ipp_supported = false,
|
|
.strip_height = STRIPE_HEIGHT, .docCategory = {0},
|
|
.copies_supported = false};
|
|
|
|
if (job_params == NULL) return result;
|
|
|
|
memcpy(job_params, &_default_job_params, sizeof(_default_job_params));
|
|
|
|
return OK;
|
|
}
|
|
|
|
status_t wprintGetFinalJobParams(wprint_job_params_t *job_params,
|
|
const printer_capabilities_t *printer_cap) {
|
|
int i;
|
|
status_t result = ERROR;
|
|
float margins[NUM_PAGE_MARGINS];
|
|
|
|
if (job_params == NULL) {
|
|
return result;
|
|
}
|
|
result = OK;
|
|
|
|
job_params->accepts_pclm = printer_cap->canPrintPCLm;
|
|
job_params->accepts_pdf = printer_cap->canPrintPDF;
|
|
job_params->media_default = printer_cap->mediaDefault;
|
|
|
|
if (printer_cap->ePclIppVersion == 1) {
|
|
job_params->epcl_ipp_supported = true;
|
|
}
|
|
|
|
if (printer_cap->canCopy) {
|
|
job_params->copies_supported = true;
|
|
}
|
|
|
|
if (printer_cap->ippVersionMajor == 2) {
|
|
job_params->ipp_1_0_supported = true;
|
|
job_params->ipp_2_0_supported = true;
|
|
} else if (printer_cap->ippVersionMajor == 1) {
|
|
job_params->ipp_1_0_supported = true;
|
|
job_params->ipp_2_0_supported = false;
|
|
}
|
|
|
|
if (!printer_cap->color) {
|
|
job_params->color_space = COLOR_SPACE_MONO;
|
|
}
|
|
|
|
if (printer_cap->canPrintPCLm || printer_cap->canPrintPDF) {
|
|
job_params->pcl_type = PCLm;
|
|
#if (USE_PWG_OVER_PCLM != 0)
|
|
if ( printer_cap->canPrintPWG) {
|
|
job_params->pcl_type = PCLPWG;
|
|
}
|
|
#endif // (USE_PWG_OVER_PCLM != 0)
|
|
} else if (printer_cap->canPrintPWG) {
|
|
job_params->pcl_type = PCLPWG;
|
|
}
|
|
|
|
LOGD("wprintGetFinalJobParams: Using PCL Type %s", getPCLTypeString(job_params->pcl_type));
|
|
|
|
// set strip height
|
|
job_params->strip_height = printer_cap->stripHeight;
|
|
|
|
// make sure the number of copies is valid
|
|
if (job_params->num_copies <= 0) {
|
|
job_params->num_copies = 1;
|
|
}
|
|
|
|
// If printing photo and HIGH quality is supported, specify it.
|
|
if (strcasecmp(job_params->docCategory, "photo") == 0 && int_array_contains(
|
|
printer_cap->supportedQuality, printer_cap->numSupportedQuality, IPP_QUALITY_HIGH)) {
|
|
job_params->print_quality = IPP_QUALITY_HIGH;
|
|
}
|
|
|
|
// confirm that the media size is supported
|
|
for (i = 0; i < printer_cap->numSupportedMediaSizes; i++) {
|
|
if (job_params->media_size == printer_cap->supportedMediaSizes[i]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= printer_cap->numSupportedMediaSizes) {
|
|
job_params->media_size = ISO_A4;
|
|
job_params->media_tray = TRAY_SRC_AUTO_SELECT;
|
|
}
|
|
|
|
// check that we support the media tray
|
|
for (i = 0; i < printer_cap->numSupportedMediaTrays; i++) {
|
|
if (job_params->media_tray == printer_cap->supportedMediaTrays[i]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// media tray not supported, default to automatic
|
|
if (i >= printer_cap->numSupportedMediaTrays) {
|
|
job_params->media_tray = TRAY_SRC_AUTO_SELECT;
|
|
}
|
|
|
|
if (printer_cap->isMediaSizeNameSupported == true) {
|
|
job_params->media_size_name = true;
|
|
} else {
|
|
job_params->media_size_name = false;
|
|
}
|
|
|
|
// verify borderless setting
|
|
if ((job_params->borderless == true) && !printer_cap->borderless) {
|
|
job_params->borderless = false;
|
|
}
|
|
|
|
// borderless and margins don't get along
|
|
if (job_params->borderless &&
|
|
((job_params->job_top_margin > 0.0f) || (job_params->job_left_margin > 0.0f) ||
|
|
(job_params->job_right_margin > 0.0f)
|
|
|| (job_params->job_bottom_margin > 0.0f))) {
|
|
job_params->borderless = false;
|
|
}
|
|
|
|
// verify duplex setting
|
|
if ((job_params->duplex != DUPLEX_MODE_NONE) && !printer_cap->duplex) {
|
|
job_params->duplex = DUPLEX_MODE_NONE;
|
|
}
|
|
|
|
// borderless and duplex don't get along either
|
|
if (job_params->borderless && (job_params->duplex != DUPLEX_MODE_NONE)) {
|
|
job_params->duplex = DUPLEX_MODE_NONE;
|
|
}
|
|
|
|
if ((job_params->duplex == DUPLEX_MODE_BOOK)
|
|
&& !printer_cap->canRotateDuplexBackPage) {
|
|
job_params->render_flags |= RENDER_FLAG_ROTATE_BACK_PAGE;
|
|
}
|
|
|
|
if (job_params->render_flags & RENDER_FLAG_ROTATE_BACK_PAGE) {
|
|
LOGD("wprintGetFinalJobParams: Duplex is on and device needs back page rotated.");
|
|
}
|
|
|
|
if ((job_params->duplex == DUPLEX_MODE_NONE) && !printer_cap->faceDownTray) {
|
|
job_params->renderInReverseOrder = true;
|
|
} else {
|
|
job_params->renderInReverseOrder = false;
|
|
}
|
|
|
|
if (job_params->render_flags & RENDER_FLAG_AUTO_SCALE) {
|
|
job_params->render_flags |= AUTO_SCALE_RENDER_FLAGS;
|
|
} else if (job_params->render_flags & RENDER_FLAG_AUTO_FIT) {
|
|
job_params->render_flags |= AUTO_FIT_RENDER_FLAGS;
|
|
}
|
|
|
|
job_params->pixel_units = _findCloseResolutionSupported(DEFAULT_RESOLUTION,
|
|
MAX_SUPPORTED_RESOLUTION, printer_cap);
|
|
|
|
printable_area_get_default_margins(job_params, printer_cap, &margins[TOP_MARGIN],
|
|
&margins[LEFT_MARGIN], &margins[RIGHT_MARGIN], &margins[BOTTOM_MARGIN]);
|
|
printable_area_get(job_params, margins[TOP_MARGIN], margins[LEFT_MARGIN], margins[RIGHT_MARGIN],
|
|
margins[BOTTOM_MARGIN]);
|
|
|
|
job_params->accepts_app_name = printer_cap->docSourceAppName;
|
|
job_params->accepts_app_version = printer_cap->docSourceAppVersion;
|
|
job_params->accepts_os_name = printer_cap->docSourceOsName;
|
|
job_params->accepts_os_version = printer_cap->docSourceOsVersion;
|
|
|
|
return result;
|
|
}
|
|
|
|
wJob_t wprintStartJob(const char *printer_addr, port_t port_num,
|
|
const wprint_job_params_t *job_params, const printer_capabilities_t *printer_cap,
|
|
const char *mime_type, const char *pathname, wprint_status_cb_t cb_fn,
|
|
const char *debugDir, const char *scheme) {
|
|
wJob_t job_handle = WPRINT_BAD_JOB_HANDLE;
|
|
_msg_t msg;
|
|
struct stat stat_buf;
|
|
bool is_dir = false;
|
|
_job_queue_t *jq;
|
|
wprint_plugin_t *plugin = NULL;
|
|
char *print_format;
|
|
ifc_print_job_t *print_ifc;
|
|
|
|
if (mime_type == NULL) {
|
|
errno = EINVAL;
|
|
return job_handle;
|
|
}
|
|
|
|
print_format = _get_print_format(mime_type, job_params, printer_cap);
|
|
if (print_format == NULL) return job_handle;
|
|
|
|
// check to see if we have an appropriate plugin
|
|
if (OK == stat(pathname, &stat_buf)) {
|
|
if (S_ISDIR(stat_buf.st_mode)) {
|
|
is_dir = true;
|
|
} else if (stat_buf.st_size == 0) {
|
|
errno = EBADF;
|
|
return job_handle;
|
|
}
|
|
} else {
|
|
errno = ENOENT;
|
|
return job_handle;
|
|
}
|
|
|
|
// Make sure we have job_params
|
|
if (job_params == NULL) {
|
|
errno = ECOMM;
|
|
return job_handle;
|
|
}
|
|
|
|
plugin = plugin_search(mime_type, print_format);
|
|
_lock();
|
|
|
|
if (plugin) {
|
|
job_handle = _get_handle();
|
|
if (job_handle == WPRINT_BAD_JOB_HANDLE) {
|
|
errno = EAGAIN;
|
|
}
|
|
} else {
|
|
errno = ENOSYS;
|
|
LOGE("wprintStartJob(): ERROR: no plugin found for %s => %s", mime_type, print_format);
|
|
}
|
|
|
|
if (job_handle != WPRINT_BAD_JOB_HANDLE) {
|
|
print_ifc = (ifc_print_job_t *) _get_print_ifc(((port_num == 0) ? PORT_FILE : PORT_IPP));
|
|
|
|
// fill out the job queue record
|
|
jq = _get_job_desc(job_handle);
|
|
if (jq == NULL) {
|
|
_recycle_handle(job_handle);
|
|
job_handle = WPRINT_BAD_JOB_HANDLE;
|
|
_unlock();
|
|
return job_handle;
|
|
}
|
|
|
|
if (debugDir != NULL) {
|
|
strncpy(jq->debug_path, debugDir, MAX_PATHNAME_LENGTH);
|
|
jq->debug_path[MAX_PATHNAME_LENGTH] = 0;
|
|
}
|
|
|
|
strncpy(jq->printer_addr, printer_addr, MAX_PRINTER_ADDR_LENGTH);
|
|
strncpy(jq->mime_type, mime_type, MAX_MIME_LENGTH);
|
|
strncpy(jq->pathname, pathname, MAX_PATHNAME_LENGTH);
|
|
|
|
jq->port_num = port_num;
|
|
jq->cb_fn = cb_fn;
|
|
jq->print_ifc = print_ifc;
|
|
jq->cancel_ok = true; // assume cancel is ok
|
|
jq->plugin = plugin;
|
|
memcpy(jq->printer_uri, printer_cap->httpResource,
|
|
MIN(ARRAY_SIZE(printer_cap->httpResource), ARRAY_SIZE(jq->printer_uri)));
|
|
|
|
jq->status_ifc = _get_status_ifc(((port_num == 0) ? PORT_FILE : PORT_IPP));
|
|
|
|
memcpy((char *) &(jq->job_params), job_params, sizeof(wprint_job_params_t));
|
|
|
|
jq->use_secure_uri = (strstr(scheme, IPPS_PREFIX) != NULL);
|
|
|
|
size_t useragent_len = strlen(USERAGENT_PREFIX) + strlen(jq->job_params.docCategory) + 1;
|
|
char *useragent = (char *) malloc(useragent_len);
|
|
if (useragent != NULL) {
|
|
snprintf(useragent, useragent_len, USERAGENT_PREFIX "%s", jq->job_params.docCategory);
|
|
jq->job_params.useragent = useragent;
|
|
}
|
|
|
|
// Make a copy of the job_params certificate if it is present
|
|
if (job_params->certificate) {
|
|
jq->job_params.certificate = malloc(job_params->certificate_len);
|
|
if (jq->job_params.certificate) {
|
|
memcpy(jq->job_params.certificate, job_params->certificate,
|
|
job_params->certificate_len);
|
|
}
|
|
}
|
|
|
|
jq->job_params.page_num = 0;
|
|
jq->job_params.print_format = print_format;
|
|
if (strcmp(print_format, PRINT_FORMAT_PCLM) == 0) {
|
|
if (printer_cap->canPrintPCLm || printer_cap->canPrintPDF) {
|
|
jq->job_params.pcl_type = PCLm;
|
|
} else {
|
|
jq->job_params.pcl_type = PCLNONE;
|
|
}
|
|
}
|
|
|
|
if (strcmp(print_format, PRINT_FORMAT_PWG) == 0) {
|
|
if (printer_cap->canPrintPWG) {
|
|
jq->job_params.pcl_type = PCLPWG;
|
|
} else {
|
|
jq->job_params.pcl_type = PCLNONE;
|
|
}
|
|
}
|
|
|
|
// if the pathname is a directory, then this is a multi-page job with individual pages
|
|
if (is_dir) {
|
|
jq->is_dir = true;
|
|
jq->num_pages = 0;
|
|
|
|
// create a pageQ for queuing page information
|
|
jq->pageQ = msgQCreate(_MAX_PAGES_PER_JOB, sizeof(_page_t));
|
|
|
|
// create a secondary page Q for subsequently saving page data for copies #2 to n
|
|
if (jq->job_params.num_copies > 1) {
|
|
jq->saveQ = msgQCreate(_MAX_PAGES_PER_JOB, sizeof(_page_t));
|
|
}
|
|
} else {
|
|
jq->num_pages = 1;
|
|
}
|
|
|
|
// post a message with job_handle to the msgQ that is serviced by a thread
|
|
msg.id = MSG_RUN_JOB;
|
|
msg.job_id = job_handle;
|
|
|
|
if (print_ifc && plugin && plugin->print_page &&
|
|
(msgQSend(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT, MSG_Q_FIFO) == OK)) {
|
|
errno = OK;
|
|
LOGD("wprintStartJob(): print job %ld queued (%s => %s)", job_handle,
|
|
mime_type, print_format);
|
|
} else {
|
|
if (print_ifc == NULL) {
|
|
errno = EAFNOSUPPORT;
|
|
} else if ((plugin == NULL) || (plugin->print_page == NULL)) {
|
|
errno = ELIBACC;
|
|
} else {
|
|
errno = EBADMSG;
|
|
}
|
|
|
|
LOGE("wprintStartJob(): ERROR plugin->start_job(%ld) : %s => %s", job_handle,
|
|
mime_type, print_format);
|
|
jq->job_state = JOB_STATE_ERROR;
|
|
_recycle_handle(job_handle);
|
|
job_handle = WPRINT_BAD_JOB_HANDLE;
|
|
}
|
|
}
|
|
_unlock();
|
|
return job_handle;
|
|
}
|
|
|
|
status_t wprintEndJob(wJob_t job_handle) {
|
|
_page_t page;
|
|
_job_queue_t *jq;
|
|
status_t result = ERROR;
|
|
|
|
_lock();
|
|
jq = _get_job_desc(job_handle);
|
|
|
|
if (jq) {
|
|
// if the job is done and is to be freed, do it
|
|
if ((jq->job_state == JOB_STATE_CANCELLED) || (jq->job_state == JOB_STATE_ERROR) ||
|
|
(jq->job_state == JOB_STATE_CORRUPTED) || (jq->job_state == JOB_STATE_COMPLETED)) {
|
|
result = OK;
|
|
if (jq->pageQ) {
|
|
while ((msgQNumMsgs(jq->pageQ) > 0)
|
|
&& (msgQReceive(jq->pageQ, (char *) &page, sizeof(page),
|
|
WAIT_FOREVER) == OK)) {
|
|
}
|
|
result |= msgQDelete(jq->pageQ);
|
|
jq->pageQ = NULL;
|
|
}
|
|
|
|
if (jq->saveQ) {
|
|
while ((msgQNumMsgs(jq->saveQ) > 0)
|
|
&& (msgQReceive(jq->saveQ, (char *) &page, sizeof(page),
|
|
WAIT_FOREVER) == OK)) {
|
|
}
|
|
result |= msgQDelete(jq->saveQ);
|
|
jq->saveQ = NULL;
|
|
}
|
|
_recycle_handle(job_handle);
|
|
} else {
|
|
LOGE("job %ld cannot be ended from state %d", job_handle, jq->job_state);
|
|
}
|
|
} else {
|
|
LOGE("ERROR: wprintEndJob(%ld), job not found", job_handle);
|
|
}
|
|
|
|
_unlock();
|
|
return result;
|
|
}
|
|
|
|
status_t wprintPage(wJob_t job_handle, int page_num, const char *filename, bool last_page,
|
|
bool pdf_page, unsigned int top_margin, unsigned int left_margin, unsigned int right_margin,
|
|
unsigned int bottom_margin) {
|
|
_job_queue_t *jq;
|
|
_page_t page;
|
|
status_t result = ERROR;
|
|
struct stat stat_buf;
|
|
|
|
_lock();
|
|
jq = _get_job_desc(job_handle);
|
|
|
|
// use empty string to indicate EOJ for an empty job
|
|
if (!filename) {
|
|
filename = "";
|
|
last_page = true;
|
|
} else if (OK == stat(filename, &stat_buf)) {
|
|
if (!S_ISREG(stat_buf.st_mode) || (stat_buf.st_size == 0)) {
|
|
_unlock();
|
|
return result;
|
|
}
|
|
} else {
|
|
_unlock();
|
|
return result;
|
|
}
|
|
|
|
// must be setup as a multi-page job, page_num must be valid, and filename must fit
|
|
if (jq && jq->is_dir && !(jq->last_page_seen) && (((strlen(filename) < MAX_PATHNAME_LENGTH)) ||
|
|
(jq && (strcmp(filename, "") == 0) && last_page))) {
|
|
memset(&page, 0, sizeof(page));
|
|
page.page_num = page_num;
|
|
page.corrupted = false;
|
|
page.pdf_page = pdf_page;
|
|
page.last_page = last_page;
|
|
page.top_margin = top_margin;
|
|
page.left_margin = left_margin;
|
|
page.right_margin = right_margin;
|
|
page.bottom_margin = bottom_margin;
|
|
|
|
if ((strlen(filename) == 0) || strchr(filename, '/')) {
|
|
// assume empty or complete pathname and use it as it is
|
|
strncpy(page.filename, filename, MAX_PATHNAME_LENGTH);
|
|
} else {
|
|
// generate a complete pathname
|
|
snprintf(page.filename, MAX_PATHNAME_LENGTH, "%s/%s", jq->pathname, filename);
|
|
}
|
|
|
|
if (last_page) {
|
|
jq->last_page_seen = true;
|
|
}
|
|
|
|
result = msgQSend(jq->pageQ, (char *) &page, sizeof(page), NO_WAIT, MSG_Q_FIFO);
|
|
}
|
|
|
|
if (result == OK) {
|
|
LOGD("wprintPage(%ld, %d, %s, %d)", job_handle, page_num, filename, last_page);
|
|
if (!(last_page && (strcmp(filename, "") == 0))) {
|
|
jq->num_pages++;
|
|
}
|
|
} else {
|
|
LOGE("wprintPage(%ld, %d, %s, %d)", job_handle, page_num, filename, last_page);
|
|
}
|
|
|
|
_unlock();
|
|
return result;
|
|
}
|
|
|
|
status_t wprintCancelJob(wJob_t job_handle) {
|
|
_job_queue_t *jq;
|
|
status_t result;
|
|
|
|
_lock();
|
|
|
|
jq = _get_job_desc(job_handle);
|
|
|
|
if (jq) {
|
|
LOGI("received cancel request");
|
|
// send a dummy page in case we're waiting on the msgQ page receive
|
|
if ((jq->job_state == JOB_STATE_RUNNING) || (jq->job_state == JOB_STATE_BLOCKED)) {
|
|
bool enableTimeout = true;
|
|
jq->cancel_ok = true;
|
|
jq->job_params.cancelled = true;
|
|
wprintPage(job_handle, jq->num_pages + 1, NULL, true, false, 0, 0, 0, 0);
|
|
if (jq->status_ifc) {
|
|
// are we blocked waiting for the job to start
|
|
if ((jq->job_state != JOB_STATE_BLOCKED) || (jq->job_params.page_num != 0)) {
|
|
errno = OK;
|
|
jq->cancel_ok = ((jq->status_ifc->cancel)(jq->status_ifc,
|
|
jq->job_params.job_originating_user_name) == 0);
|
|
if ((jq->cancel_ok == true) && (errno != OK)) {
|
|
enableTimeout = false;
|
|
}
|
|
}
|
|
}
|
|
if (!jq->cancel_ok) {
|
|
LOGE("CANCEL did not go through or is not supported for this device");
|
|
enableTimeout = true;
|
|
}
|
|
if (enableTimeout && (jq->print_ifc != NULL) &&
|
|
(jq->print_ifc->enable_timeout != NULL)) {
|
|
jq->print_ifc->enable_timeout(jq->print_ifc, 1);
|
|
}
|
|
|
|
errno = (jq->cancel_ok ? OK : ENOTSUP);
|
|
jq->job_state = JOB_STATE_CANCEL_REQUEST;
|
|
result = OK;
|
|
} else if ((jq->job_state == JOB_STATE_CANCEL_REQUEST) ||
|
|
(jq->job_state == JOB_STATE_CANCELLED)) {
|
|
result = OK;
|
|
errno = (jq->cancel_ok ? OK : ENOTSUP);
|
|
} else if (jq->job_state == JOB_STATE_QUEUED) {
|
|
jq->job_params.cancelled = true;
|
|
jq->job_state = JOB_STATE_CANCELLED;
|
|
|
|
if (jq->cb_fn) {
|
|
wprint_job_callback_params_t cb_param;
|
|
cb_param.state = JOB_DONE;
|
|
cb_param.blocked_reasons = BLOCKED_REASONS_CANCELLED;
|
|
cb_param.job_done_result = CANCELLED;
|
|
cb_param.certificate = jq->certificate;
|
|
cb_param.certificate_len = jq->certificate_len;
|
|
|
|
jq->cb_fn(job_handle, (void *) &cb_param);
|
|
}
|
|
|
|
errno = OK;
|
|
result = OK;
|
|
} else {
|
|
LOGE("job in other state");
|
|
result = ERROR;
|
|
errno = EBADRQC;
|
|
}
|
|
} else {
|
|
LOGE("could not find job");
|
|
result = ERROR;
|
|
errno = EBADR;
|
|
}
|
|
|
|
_unlock();
|
|
|
|
return result;
|
|
}
|
|
|
|
status_t wprintExit(void) {
|
|
_msg_t msg;
|
|
|
|
if (_msgQ) {
|
|
// toss the remaining messages in the msgQ
|
|
while ((msgQNumMsgs(_msgQ) > 0) &&
|
|
(OK == msgQReceive(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT))) {}
|
|
|
|
// send a quit message
|
|
msg.id = MSG_QUIT;
|
|
msgQSend(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT, MSG_Q_FIFO);
|
|
|
|
// stop the job thread
|
|
_stop_thread();
|
|
|
|
// empty out the semaphore
|
|
while (sem_trywait(&_job_end_wait_sem) == OK);
|
|
while (sem_trywait(&_job_start_wait_sem) == OK);
|
|
|
|
// receive any messages just in case
|
|
while ((msgQNumMsgs(_msgQ) > 0)
|
|
&& (OK == msgQReceive(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT))) {}
|
|
|
|
// delete the msgQ
|
|
msgQDelete(_msgQ);
|
|
_msgQ = NULL;
|
|
|
|
sem_destroy(&_job_end_wait_sem);
|
|
sem_destroy(&_job_start_wait_sem);
|
|
pthread_mutex_destroy(&_q_lock);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
void wprintSetSourceInfo(const char *appName, const char *appVersion, const char *osName) {
|
|
if (appName) {
|
|
strncpy(g_appName, appName, (sizeof(g_appName) - 1));
|
|
}
|
|
|
|
if (appVersion) {
|
|
strncpy(g_appVersion, appVersion, (sizeof(g_appVersion) - 1));
|
|
}
|
|
|
|
if (osName) {
|
|
strncpy(g_osName, osName, (sizeof(g_osName) - 1));
|
|
}
|
|
|
|
LOGI("App Name: '%s', Version: '%s', OS: '%s'", g_appName, g_appVersion, g_osName);
|
|
}
|