21 October 2014

Kinokero (cloudprint) tutorial part 2

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

# ------------------------------------------------------------------------

Putting it all together

All the above sections are part of the main module in the console, console/twiga.rb.  So you can put these together in any manner that makes sense to your purpose.  Most of the methods shown above are merely helper functions to prep the required kinokero data structures.