Kinokero Tutorial Part 2: Cloudprinting
Using Google Cloudprint Proxy Connector in Ruby
Kinokero is a ruby gem which provides a complete Google CloudPrint Proxy Connector functionality. That means that it can turn any OS-connected printer into a cloudprint accessible printer, as long as the internet connection is maintained.
This post is a continuation of Part 1 Getting Started, so please check that out for prerequisites and expected environments.
A basis for building a proxy connector
Within the gem source itself, there are two places that demonstrate using kinokero: the console and the testing setup helper. The console is located at: console/twiga.rb, and the testing helper is located at: test/test/test_kinokero.rb. Because testing is kind of specific and different, using the console as a template would be better.Console
During development, I needed a convenient setup and testing structure to individually trigger GCP primitives and see traces of the request and result. I'm calling that the "console" and it has no function inherent to the gem, other than a convenient setup and debugging apparatus. Rather than having it vanish, I have made it part of the gem superstructure and it can be run independently, as though it were an application, for calling and testing the gem.The console has a simple means of persistence (a seed yaml file) for any printers which are registered. The seed yaml requires, at the least for initial startup, a section of data for the (required) test printer.
In this tutorial, I will try to point out the important aspects to keep when building a program to use the kinokero proxy connector.
Structural
I use both rvm (ruby version manager) together with bundler to manage my various project environments. So you'll need a Gemfile with at least the following line:gem 'kinokero'
then you'll need to run the bundler:
$ bundle install
Configuration and initialization
Next, think about any configuration and initialization you'll need. The console does it in the following way, which should go at the head of your program:main module invokes configuration
#!/usr/bin/env ruby # ************************************************************************* # ***** app configured, initialized, and ruby-prepped here ************* # ************************************************************************* # make it so that our lib is at the head of the load/require path array $:.unshift( File.expand_path('../lib', __FILE__) ) # kick off module configurations load File.expand_path('../config/application_configuration.rb', __FILE__) # ************************************************************************* require 'yaml' require 'erb' require 'active_support/core_ext/hash'application_configuration.rb hold any basic gem invocation stuff
And config/application_configuration.rb basically is the following (of course you'll need to change to your own namespace conventions):
# *************************************************************************
# ******* general configuration for console appliance ***************
# *************************************************************************
module Twiga
# *************************************************************************
require 'rubygems'
require "kinokero"
require 'json'
# *************************************************************************
# ****** mimic the way RAILS sets up required gems **********************
# Set up gems listed in the Gemfile.
# *************************************************************************
#
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
# *************************************************************************
# ########################################################################
end # module Twiga
Retrieve Persistence
Your main module will need to retrieve any existing printer information from persistence. The console uses config/gcp_seed.yml for its persistence (it needs to be in writable medium). The following routines are your basic read/write for the persistence. Also included is build_device_list which will return a kinokero-style gcp_control_hash from the seed data.# *************************************************************************
# get managed printer data
# *************************************************************************
GCP_SEED_FILE = "../config/gcp_seed.yml"
# #########################################################################
# ########## working with seed data #####################################
# #########################################################################
# -------------------------------------------------------------------------
def load_gcp_seed()
if @seed_data.nil?
@seed_data = YAML.load(
ERB.new(
File.read(
File.expand_path(GCP_SEED_FILE , __FILE__ )
)
).result
)
end
end
# ------------------------------------------------------------------------
def write_gcp_seed()
unless @seed_data.nil?
File.open(
File.expand_path(GCP_SEED_FILE , __FILE__ ),
'w'
) { |f|
YAML.dump(@seed_data, f)
}
end # unless no seed data yet
end
# ------------------------------------------------------------------------
def build_device_list()
load_gcp_seed() # load the seed data
# prep to build up a hash of gcp_control hashes
gcp_control_hash = {}
@seed_data.each_key do |item|
# strip item into hash with keys
gcp_control_hash[ item ] = @seed_data[ item ].symbolize_keys
end # convert each seed to a device object
return gcp_control_hash
end # convert each seed to a device object
# ------------------------------------------------------------------------
Command line options
If you wish to have some command line switches to control kinokero, the following function will return an options hash from the command line switches.# #######################################################################
# ########## command line options handling ############################
# #######################################################################
# -----------------------------------------------------------------------
def parse_options()
options = {}
ARGV.each_index do |index|
case $*[index]
when '-m' then options[:auto_connect] = false
when '-v' then options[:verbose] = true
when '-q' then options[:verbose] = false
when '-t' then options[:log_truncate] = true
when '-r' then options[:log_response] = false
else
::Twiga.say_warn "unknown option: #{arg}"
end # case
$*.delete_at(index) # remove from command line
end # do each cmd line arg
return Kinokero::Cloudprint::DEFAULT_OPTIONS.merge(options)
end
# ------------------------------------------------------------------------
Invocation & instantiating a Proxy object
The console just jumps into the following code as part of the main module. Note that build_device_list automatically loads and parses the seed yml file, if it hasn't already been loaded. As kinokero processes this device list, it will automatically setup and activate any logical cloudprint printer which is designated as active and it will establish an on-line connection with GCPS unless the :auto_connect option is false.# start up the GCP proxy @proxy = Kinokero::Proxy.new( build_device_list(), parse_options )
# remember the device list for easy access
@my_devices = @proxy.my_devices # not necessary to extract locally
Register & remove a printer
These are the most basic two actions which a Proxy is expected to perform. To update persistence, the console has several helper methods: update_gcp_seed, add_gcp_seed_request, and write_gcp_seed. These can be found in the console and are not included here, as they are not germane to the main usage of kinokero.# ------------------------------------------------------------------------
def do_register( item )
new_request = build_gcp_request( item )
response = @proxy.do_register( new_request ) do |gcp_control|
update_gcp_seed(gcp_control, gcp_control[:item] ) do |seed|
add_gcp_seed_request( seed, new_request )
end # seed additions
end # do persist new printer information
unless response[:success]
puts "printer registration failed: #{response[:message]}"
end
end
# ------------------------------------------------------------------------
def do_delete( item )
item = validate_item( item )
@proxy.do_delete( item )
@seed_data[item]['is_active'] = false
write_gcp_seed()
end
# ------------------------------------------------------------------------
# if item hasn't yet been defined in seed data, create one out of
# thin air by using test as a template
# ------------------------------------------------------------------------
def build_gcp_request( item )
use_item = validate_item( item )
return {
item: item,
printer_id: 0, # will be cue to create new record
gcp_printer_name: "gcp_#{item}_printer",
capability_ppd: @seed_data[use_item]['capability_ppd'],
capability_cdd: @seed_data[use_item]['capability_cdd'],
cups_alias: @seed_data[use_item]['cups_alias'],
gcp_uuid: @seed_data[use_item]['gcp_uuid'],
gcp_manufacturer: @seed_data[use_item]['gcp_manufacturer'],
gcp_model: @seed_data[use_item]['gcp_model'],
gcp_setup_url: @seed_data[use_item]['gcp_setup_url'],
gcp_support_url: @seed_data[use_item]['gcp_support_url'],
gcp_update_url: @seed_data[use_item]['gcp_update_url'],
gcp_firmware: @seed_data[use_item]['gcp_firmware'],
}
end
# ------------------------------------------------------------------------
def validate_item( item )
return ( @seed_data.has_key?(item) ? item : 'test' )
end
# ------------------------------------------------------------------------