Project

General

Profile

Bug #11306 » cups.c

cups gem C code for Cups#device_uri_for - dsaronin (David Anderson), 06/25/2015 07:42 AM

 
#include <ruby_cups.h>

cups_dest_t *dests, *dest;
VALUE rubyCups, printJobs;

// Need to abstract this out of cups.c
VALUE ipp_state_to_symbol(int state)
{
VALUE jstate;

switch (state) {
case IPP_JOB_PENDING :
jstate = ID2SYM(rb_intern("pending"));
break;
case IPP_JOB_HELD :
jstate = ID2SYM(rb_intern("held"));
break;
case IPP_JOB_PROCESSING :
jstate = ID2SYM(rb_intern("processing"));
break;
case IPP_JOB_STOPPED :
jstate = ID2SYM(rb_intern("stopped"));
break;
case IPP_JOB_CANCELED :
jstate = ID2SYM(rb_intern("cancelled"));
break;
case IPP_JOB_ABORTED :
jstate = ID2SYM(rb_intern("aborted"));
break;
case IPP_JOB_COMPLETED :
jstate = ID2SYM(rb_intern("completed"));
break;
default:
jstate = ID2SYM(rb_intern("unknown"));
}
return jstate;
}

int printer_exists(VALUE printer){
// First call Cups#show_destinations
VALUE dest_list = rb_funcall(rubyCups, rb_intern("show_destinations"), 0);
// Then check the printer arg is included in the returned array...
return rb_ary_includes(dest_list, printer) ? 1 : 0;
}

/*
* call-seq:
* PrintJob.new(filename, printer=nil)
*
* Initializes a new PrintJob object. If no target printer/class is specified, the default is chosen.
* Note the specified file does not have to exist until print is called.
*/
static VALUE job_init(int argc, VALUE* argv, VALUE self)
{
VALUE filename, printer, job_options;

rb_scan_args(argc, argv, "12", &filename, &printer, &job_options);

rb_iv_set(self, "@filename", filename);
rb_iv_set(self, "@url_path", rb_str_new2(cupsServer()));

if (NIL_P(job_options)) {
rb_iv_set(self, "@job_options", rb_hash_new());
} else {
rb_iv_set(self, "@job_options", job_options);
}

if (NIL_P(printer)) {

// Fall back to default printer
VALUE def_p = rb_funcall(rubyCups, rb_intern("default_printer"), 0);

if (def_p == Qfalse) {
rb_raise(rb_eRuntimeError, "There is no default printer!");
} else {
rb_iv_set(self, "@printer", def_p);
}

} else {
if (printer_exists(printer)) {
rb_iv_set(self, "@printer", printer);
} else {
rb_raise(rb_eRuntimeError, "The printer or destination doesn't exist!");
}
}
return self;
}

/*
* Note: rb_hash_keys is defined in 1.8.6, but not in 1.8.7 ubuntu shared lib
* This is so that I can get a list of keys to convert to options
*/
static int
cups_keys_i(key, value, ary)
VALUE key, value, ary;
{
if (key == Qundef) return ST_CONTINUE;
rb_ary_push(ary, key);
return ST_CONTINUE;
}

/*
* call-seq:
* print_job.print -> Fixnum
*
* Submit a print job to the selected printer or class. Returns true on success.
*/
static VALUE cups_print(VALUE self)
{
int job_id;
VALUE file = rb_iv_get(self, "@filename");
VALUE rname = rb_iv_get(self, "@title");
VALUE printer = rb_iv_get(self, "@printer");
VALUE url_path = rb_iv_get(self, "@url_path");

char *fname = RSTRING_PTR(file); // Filename
char *title = T_STRING == TYPE(rname) ? RSTRING_PTR(rname) : "rCups";
char *target = RSTRING_PTR(printer); // Target printer string
const char *url = RSTRING_PTR(url_path); // Server URL address
int port = 631; // Default CUPS port

VALUE job_options = rb_iv_get(self, "@job_options");

// Create an array of the keys from the job_options hash
VALUE job_options_keys = rb_ary_new();
rb_hash_foreach(job_options, cups_keys_i, job_options_keys);

VALUE iter;
int num_options = 0;
cups_option_t *options = NULL;

// foreach option in the job options array
while (! NIL_P(iter = rb_ary_pop(job_options_keys))) {

VALUE value = rb_hash_aref(job_options, iter);

// assert the key and value are strings
if (NIL_P(rb_check_string_type(iter)) || NIL_P(rb_check_string_type(value))) {
cupsFreeOptions(num_options, options);
rb_raise(rb_eTypeError, "job options is not string => string hash");
return Qfalse;
}

// convert to char pointers and add to cups optoins
char * iter_str = rb_string_value_ptr(&iter);
char * value_str = rb_string_value_ptr(&value);
cupsAddOption(iter_str, value_str, num_options++, &options);
}

if(NIL_P(url)) {
url = cupsServer();
}

int encryption = (http_encryption_t)cupsEncryption();
http_t *http = httpConnect2(url, port, NULL, AF_UNSPEC, (http_encryption_t) encryption, 1, 30000, NULL);

job_id = cupsPrintFile2(http, target, fname, title, num_options, options); // Do it. "rCups" should be the filename/path
//
// cupsFreeOptions(num_options, options);

rb_iv_set(self, "@job_id", INT2NUM(job_id));
return Qtrue;
}

/*
* call-seq:
* Cups.show_destinations -> Array
*
* Show all destinations on the default server
*/
static VALUE cups_show_dests(VALUE self)
{
VALUE dest_list;
int i;
int num_dests = cupsGetDests(&dests); // Size of dest_list array
dest_list = rb_ary_new2(num_dests);

for (i = num_dests, dest = dests; i > 0; i --, dest ++) {
VALUE destination = rb_str_new2(dest->name);
rb_ary_push(dest_list, destination); // Add this testination name to dest_list string
}

cupsFreeDests(num_dests, dests);
return dest_list;
}

/*
* call-seq:
* Cups.default_printer -> String or nil
*
* Get default printer or class. Returns a string or false if there is no default
*/
static VALUE cups_get_default(VALUE self)
{
const char *default_printer;
default_printer = cupsGetDefault();

if (default_printer != NULL) {
VALUE def_p = rb_str_new2(default_printer);

return def_p;
} else {
return Qnil;
}
}

/*
* call-seq:
* print_job.cancel -> true or false
*
* Cancel the current job. Returns true if successful, false otherwise.
*/
static VALUE cups_cancel(VALUE self)
{
VALUE printer, job_id;
printer = rb_iv_get(self, "@printer");
job_id = rb_iv_get(self, "@job_id");

if (NIL_P(job_id)) {
return Qfalse; // If @job_id is nil
} else { // Otherwise attempt to cancel
int job = NUM2INT(job_id);
char *target = RSTRING_PTR(printer); // Target printer string
int cancellation;
cancellation = cupsCancelJob(target, job);
return Qtrue;
}
}

/*
* call-seq:
* print_job.failed? -> true or false
*
* Did this job fail?
*/
static VALUE cups_job_failed(VALUE self)
{
VALUE job_id = rb_iv_get(self, "@job_id");

if (NIL_P(job_id) || !NUM2INT(job_id) == 0) {
return Qfalse;
} else {
return Qtrue;
}
}

/*
* call-seq:
* print_job.error_reason -> String
*
* Get the last human-readable error string
*/
static VALUE cups_get_error_reason(VALUE self)
{
VALUE job_id = rb_iv_get(self, "@job_id");

if (NIL_P(job_id) || !NUM2INT(job_id) == 0) {
return Qnil;
} else {
VALUE error_exp = rb_str_new2(cupsLastErrorString());
return error_exp;
}
}

/*
* call-seq:
* print_job.error_code -> Fixnum
*
* Get the last IPP error code.
*/
static VALUE cups_get_error_code(VALUE self)
{
VALUE job_id = rb_iv_get(self, "@job_id");

if (NIL_P(job_id) || !NUM2INT(job_id) == 0) {
return Qnil;
} else {
VALUE ipp_error_code = INT2NUM(cupsLastError());
return ipp_error_code;
}
}

/*
* call-seq:
* print_job.state -> String
*
* Get human-readable state of current job.
*/
static VALUE cups_get_job_state(VALUE self)
{
VALUE job_id = rb_iv_get(self, "@job_id");
VALUE printer = rb_iv_get(self, "@printer");
VALUE jstate;

int num_jobs;
cups_job_t *jobs;
ipp_jstate_t job_state = IPP_JOB_PENDING;
int i;
char *printer_arg = RSTRING_PTR(printer);

if (NIL_P(job_id)) {
return Qnil;
} else {
num_jobs = cupsGetJobs(&jobs, printer_arg, 1, -1); // Get jobs

for (i = 0; i < num_jobs; i ++) {
if (jobs[i].id == NUM2INT(job_id)) {
job_state = jobs[i].state;
break;
}
}

// Free job array
cupsFreeJobs(num_jobs, jobs);

jstate = ipp_state_to_symbol(job_state);
return jstate;
}
}

/*
* call-seq:
* print_job.completed? -> true or false
*
* Has the job completed?
*/
static VALUE cups_job_completed(VALUE self)
{
VALUE job_id = rb_iv_get(self, "@job_id");
VALUE printer = rb_iv_get(self, "@printer");
VALUE jstate;

int num_jobs;
cups_job_t *jobs;
ipp_jstate_t job_state = IPP_JOB_PENDING;
int i;
char *printer_arg = RSTRING_PTR(printer);

if (NIL_P(job_id)) {
return Qfalse;
}

num_jobs = cupsGetJobs(&jobs, printer_arg, 1, -1); // Get jobs

// find our job
for (i = 0; i < num_jobs; i ++) {
if (jobs[i].id == NUM2INT(job_id)) {
job_state = jobs[i].state;
break;
}
}

// Free job array
cupsFreeJobs(num_jobs, jobs);

if (job_state == IPP_JOB_COMPLETED) {
return Qtrue;
} else {
return Qfalse;
}

}

/*
* call-seq:
* Cups.all_jobs(printer) -> Hash
*
* Get all jobs from default CUPS server. Takes a single printer/class string argument.
* Returned hash keys are CUPS job ids, and the values are hashes of job info
* with keys:
*
* [:title, :submitted_by, :size, :format, :state]
*/


static VALUE cups_get_jobs(VALUE self, VALUE printer)
{
// Don't have to lift a finger unless the printer exists.
if (!printer_exists(printer)){
rb_raise(rb_eRuntimeError, "The printer or destination doesn't exist!");
}

VALUE job_list, job_info_hash, jid, jtitle, juser, jsize, jformat, jstate;
int job_id;
int num_jobs;
cups_job_t *jobs;
ipp_jstate_t state;
int i;
char *printer_arg = RSTRING_PTR(printer);

num_jobs = cupsGetJobs(&jobs, printer_arg, 1, -1); // Get jobs
job_list = rb_hash_new();

for (i = 0; i < num_jobs; i ++) { // Construct a hash of individual job info
job_info_hash = rb_hash_new();
jid = INT2NUM(jobs[i].id);
jtitle = rb_str_new2(jobs[i].title);
juser = rb_str_new2(jobs[i].user);
jsize = INT2NUM(jobs[i].size);
jformat = rb_str_new2(jobs[i].format);
jstate = ipp_state_to_symbol(jobs[i].state);

rb_hash_aset(job_info_hash, ID2SYM(rb_intern("title")), jtitle);
rb_hash_aset(job_info_hash, ID2SYM(rb_intern("submitted_by")), juser);
rb_hash_aset(job_info_hash, ID2SYM(rb_intern("size")), jsize);
rb_hash_aset(job_info_hash, ID2SYM(rb_intern("format")), jformat);
rb_hash_aset(job_info_hash, ID2SYM(rb_intern("state")), jstate);

rb_hash_aset(job_list, jid, job_info_hash); // And push it all into job_list hash
}

// Free job array
cupsFreeJobs(num_jobs, jobs);
return job_list;
}

/*
* call-seq:
* Cups.cancel_print(cups_id, printer_name) -> true or false
*
* Cancel the print job. Returns true if successful, false otherwise.
*/
static VALUE cups_cancel_print(int argc, VALUE* argv, VALUE self)
{
VALUE printer, job_id;
rb_scan_args(argc, argv, "20", &job_id, &printer);

if (NIL_P(job_id)) {
return Qfalse; // If @job_id is nil
} else { // Otherwise attempt to cancel
int job = NUM2INT(job_id);
char *target = RSTRING_PTR(printer); // Target printer string
int cancellation;
cancellation = cupsCancelJob(target, job);

return Qtrue;
}
}

/*
* call-seq:
* Cups.device_uri_for(printer_name) -> String
*
* Return uri for requested printer.
*/
static VALUE cups_get_device_uri(VALUE self, VALUE printer)
{
if (!printer_exists(printer))
{
rb_raise(rb_eRuntimeError, "The printer or destination doesn't exist!");
}

VALUE options_list;
http_t *http;
ipp_t *request;
ipp_t *response;
ipp_attribute_t *attr;
char uri[1024];
char *location;
char *name = RSTRING_PTR(printer);

request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", name);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);

if ((response = cupsDoRequest(http, request, "/")) != NULL)
{
if((attr = ippFindAttribute(response, "device-uri", IPP_TAG_URI)) != NULL)
{
return rb_str_new2(attr->values[0].string.text);
}
ippDelete(response);
}
return Qtrue;
}

/*
* call-seq:
* Cups.options_for(name) -> Hash or nil
*
* Get all options from CUPS server with name. Returns a hash with key/value pairs
* based on server options, or nil if no server with name.
*/
static VALUE cups_get_options(VALUE self, VALUE printer)
{
// Don't have to lift a finger unless the printer exists.
if (!printer_exists(printer)){
rb_raise(rb_eRuntimeError, "The printer or destination doesn't exist!");
}

VALUE options_list;
int i;
char *printer_arg = RSTRING_PTR(printer);

options_list = rb_hash_new();

cups_dest_t *dests;
int num_dests = cupsGetDests(&dests);
cups_dest_t *dest = cupsGetDest(printer_arg, NULL, num_dests, dests);

if (dest == NULL) {
cupsFreeDests(num_dests, dests);
return Qnil;
} else {
for(i =0; i< dest->num_options; i++) {
rb_hash_aset(options_list, rb_str_new2(dest->options[i].name), rb_str_new2(dest->options[i].value));
}
cupsFreeDests(num_dests, dests);
return options_list;
}

}

/*
*/

void Init_cups() {
rubyCups = rb_define_module("Cups");
printJobs = rb_define_class_under(rubyCups, "PrintJob", rb_cObject);

// Cups::PrintJob Attributes
rb_define_attr(printJobs, "printer", 1, 0);
rb_define_attr(printJobs, "filename", 1, 0);
rb_define_attr(printJobs, "url_path", 1, 0);
rb_define_attr(printJobs, "job_id", 1, 0);
rb_define_attr(printJobs, "job_options", 1, 0);
rb_define_attr(printJobs, "title", 1, 1);

// Cups::PrintJob Methods
rb_define_method(printJobs, "initialize", job_init, -1);
rb_define_method(printJobs, "print", cups_print, 0);
rb_define_method(printJobs, "cancel", cups_cancel, 0);
rb_define_method(printJobs, "state", cups_get_job_state, 0);
rb_define_method(printJobs, "completed?", cups_job_completed, 0);
rb_define_method(printJobs, "failed?", cups_job_failed, 0);
rb_define_method(printJobs, "error_reason", cups_get_error_reason, 0);
rb_define_method(printJobs, "error_code", cups_get_error_code, 0);

// Cups Module Methods
rb_define_singleton_method(rubyCups, "show_destinations", cups_show_dests, 0);
rb_define_singleton_method(rubyCups, "default_printer", cups_get_default, 0);
rb_define_singleton_method(rubyCups, "all_jobs", cups_get_jobs, 1);
rb_define_singleton_method(rubyCups, "cancel_print", cups_cancel_print, -1);
rb_define_singleton_method(rubyCups, "options_for", cups_get_options, 1);
rb_define_singleton_method(rubyCups, "device_uri_for", cups_get_device_uri, 1);
rb_define_singleton_method(rubyCups, "get_connection_for", cups_get_device_uri, 1);
}
(3-3/3)