/*
* 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);
}
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;
int loop = 1;
printer_state_dyn_t printer_state;
do {
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;
switch (status) {
case PRINT_STATUS_IDLE:
printer_state.printer_status = PRINT_STATUS_IDLE;
jq->blocked_reasons = 0;
loop = 0;
break;
case PRINT_STATUS_UNKNOWN:
if (printer_state.printer_reasons[0] == PRINT_STATUS_UNKNOWN) {
LOGE("PRINTER STATUS UNKNOWN - Ln 747 libwprint.c");
// no status available, break out and hope for the best
printer_state.printer_status = PRINT_STATUS_IDLE;
loop = 0;
break;
}
case PRINT_STATUS_SVC_REQUEST:
if ((printer_state.printer_reasons[0] == PRINT_STATUS_UNABLE_TO_CONNECT)
|| (printer_state.printer_reasons[0] == PRINT_STATUS_OFFLINE)) {
if (_is_certificate_allowed(jq)) {
LOGD("_job_thread: Received an Unable to Connect message");
jq->blocked_reasons = BLOCKED_REASON_UNABLE_TO_CONNECT;
} else {
LOGD("_job_thread: Bad certificate");
jq->blocked_reasons = BLOCKED_REASON_BAD_CERTIFICATE;
}
loop = 0;
break;
}
default:
if (printer_state.printer_status & PRINTER_IDLE_BIT) {
LOGD("printer blocked but appears to be in an idle state. "
"Allowing job to proceed");
printer_state.printer_status = PRINT_STATUS_IDLE;
loop = 0;
break;
} else if (retry >= MAX_IDLE_WAIT) {
jq->blocked_reasons |= BLOCKED_REASONS_PRINTER_BUSY;
loop = 0;
} else if (!jq->job_params.cancelled) {
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++;
}
break;
}
if (jq->job_params.cancelled) {
loop = 0;
}
} while (loop);
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;
}
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);
}