kinokero 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +44 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +49 -0
- data/Gemfile_mock +8 -0
- data/LICENSE +20 -0
- data/README.md +955 -0
- data/Rakefile +1 -0
- data/console/.ruby-gemset +1 -0
- data/console/.ruby-version +1 -0
- data/console/Gemfile +6 -0
- data/console/Gemfile.lock +63 -0
- data/console/README.md +34 -0
- data/console/config/application_configuration.rb +39 -0
- data/console/config/gcp_seed.yml +73 -0
- data/console/config/kinokero_initializer_template.rb +108 -0
- data/console/console +2 -0
- data/console/irb_console +2 -0
- data/console/lib/appliance_common.rb +73 -0
- data/console/twiga.rb +579 -0
- data/hp-check.log +244 -0
- data/kinokero.gemspec +35 -0
- data/lib/kinokero.rb +183 -0
- data/lib/kinokero/blank.rb +105 -0
- data/lib/kinokero/cloudprint.rb +1159 -0
- data/lib/kinokero/device.rb +6 -0
- data/lib/kinokero/jingle.rb +176 -0
- data/lib/kinokero/log.rb +157 -0
- data/lib/kinokero/printer.rb +313 -0
- data/lib/kinokero/proxy.rb +341 -0
- data/lib/kinokero/ruby_extensions.rb +21 -0
- data/lib/kinokero/sasl_xoauth2.rb +164 -0
- data/lib/kinokero/version.rb +3 -0
- data/lib/proto/cloud_device_description.proto +18 -0
- data/lib/proto/cloud_device_state.proto +30 -0
- data/lib/proto/cloud_device_state_type.proto +20 -0
- data/lib/proto/cloud_device_ui_state.proto +42 -0
- data/lib/proto/cloud_device_ui_state_severity.proto +12 -0
- data/lib/proto/cloud_job_ticket.proto +18 -0
- data/lib/proto/collate.proto +4 -0
- data/lib/proto/collate_ticket_item.proto +5 -0
- data/lib/proto/color.proto +42 -0
- data/lib/proto/color_ticket_item.proto +12 -0
- data/lib/proto/copies.proto +6 -0
- data/lib/proto/copies_ticket_item.proto +5 -0
- data/lib/proto/cover.proto +31 -0
- data/lib/proto/cover_state.proto +25 -0
- data/lib/proto/device_action_cause.proto +19 -0
- data/lib/proto/dpi.proto +27 -0
- data/lib/proto/dpi_ticket_item.proto +13 -0
- data/lib/proto/duplex.proto +15 -0
- data/lib/proto/duplex_ticket_item.proto +8 -0
- data/lib/proto/fit_to_page.proto +20 -0
- data/lib/proto/fit_to_page_ticket_item.proto +8 -0
- data/lib/proto/input_tray_state.proto +31 -0
- data/lib/proto/input_tray_unit.proto +35 -0
- data/lib/proto/job_state.proto +143 -0
- data/lib/proto/local_settings.proto +36 -0
- data/lib/proto/localized_string.proto +119 -0
- data/lib/proto/margins.proto +33 -0
- data/lib/proto/margins_ticket_item.proto +14 -0
- data/lib/proto/marker.proto +62 -0
- data/lib/proto/marker_state.proto +31 -0
- data/lib/proto/media_path.proto +6 -0
- data/lib/proto/media_path_state.proto +25 -0
- data/lib/proto/media_size.proto +216 -0
- data/lib/proto/media_size_ticket_item.proto +17 -0
- data/lib/proto/output_bin_state.proto +31 -0
- data/lib/proto/output_bin_unit.proto +32 -0
- data/lib/proto/page_orientation.proto +16 -0
- data/lib/proto/page_orientation_ticket_item.proto +9 -0
- data/lib/proto/page_range.proto +15 -0
- data/lib/proto/page_range_ticket_item.proto +7 -0
- data/lib/proto/print_job_state.proto +18 -0
- data/lib/proto/print_job_state_diff.proto +13 -0
- data/lib/proto/print_job_ui_state.proto +24 -0
- data/lib/proto/print_ticket_section.proto +30 -0
- data/lib/proto/printer_description_section.proto +107 -0
- data/lib/proto/printer_state_section.proto +49 -0
- data/lib/proto/printer_ui_state_section.proto +39 -0
- data/lib/proto/printing_speed.proto +35 -0
- data/lib/proto/pwg_raster_config.proto +176 -0
- data/lib/proto/range_capability.proto +14 -0
- data/lib/proto/reverse_order.proto +5 -0
- data/lib/proto/reverse_order_ticket_item.proto +5 -0
- data/lib/proto/scanner_description_section.proto +5 -0
- data/lib/proto/scanner_state_section.proto +25 -0
- data/lib/proto/select_capability.proto +31 -0
- data/lib/proto/supported_content_type.proto +12 -0
- data/lib/proto/typed_value_capability.proto +15 -0
- data/lib/proto/vendor_capability.proto +40 -0
- data/lib/proto/vendor_state.proto +26 -0
- data/lib/proto/vendor_ticket_item.proto +9 -0
- data/lib/proto_lib/cloud_device_state.pb.rb +21 -0
- data/lib/proto_lib/cloud_device_state_type.pb.rb +16 -0
- data/lib/proto_lib/cloud_device_ui_state.pb.rb +22 -0
- data/lib/proto_lib/cloud_device_ui_state_severity.pb.rb +17 -0
- data/lib/proto_lib/collate.pb.rb +11 -0
- data/lib/proto_lib/color.pb.rb +31 -0
- data/lib/proto_lib/copies.pb.rb +12 -0
- data/lib/proto_lib/cover.pb.rb +21 -0
- data/lib/proto_lib/cover_state.pb.rb +27 -0
- data/lib/proto_lib/device_action_cause.pb.rb +18 -0
- data/lib/proto_lib/dpi.pb.rb +27 -0
- data/lib/proto_lib/duplex.pb.rb +26 -0
- data/lib/proto_lib/fit_to_page.pb.rb +28 -0
- data/lib/proto_lib/input_tray_state.pb.rb +30 -0
- data/lib/proto_lib/input_tray_unit.pb.rb +25 -0
- data/lib/proto_lib/job_state.pb.rb +99 -0
- data/lib/proto_lib/localized_string.pb.rb +118 -0
- data/lib/proto_lib/margins.pb.rb +30 -0
- data/lib/proto_lib/marker.pb.rb +45 -0
- data/lib/proto_lib/marker_state.pb.rb +30 -0
- data/lib/proto_lib/media_path.pb.rb +11 -0
- data/lib/proto_lib/media_path_state.pb.rb +27 -0
- data/lib/proto_lib/media_size.pb.rb +198 -0
- data/lib/proto_lib/output_bin_state.pb.rb +30 -0
- data/lib/proto_lib/output_bin_unit.pb.rb +22 -0
- data/lib/proto_lib/page_orientation.pb.rb +26 -0
- data/lib/proto_lib/page_range.pb.rb +20 -0
- data/lib/proto_lib/print_job_state_diff.pb.rb +12 -0
- data/lib/proto_lib/printer_description_section.pb.rb +30 -0
- data/lib/proto_lib/printer_state_section.pb.rb +23 -0
- data/lib/proto_lib/printer_ui_state_section.pb.rb +28 -0
- data/lib/proto_lib/printing_speed.pb.rb +21 -0
- data/lib/proto_lib/pwg_raster_config.pb.rb +103 -0
- data/lib/proto_lib/range_capability.pb.rb +19 -0
- data/lib/proto_lib/reverse_order.pb.rb +11 -0
- data/lib/proto_lib/scanner_description_section.pb.rb +10 -0
- data/lib/proto_lib/scanner_state_section.pb.rb +17 -0
- data/lib/proto_lib/select_capability.pb.rb +22 -0
- data/lib/proto_lib/supported_content_type.pb.rb +13 -0
- data/lib/proto_lib/typed_value_capability.pb.rb +19 -0
- data/lib/proto_lib/vendor_capability.pb.rb +23 -0
- data/lib/proto_lib/vendor_state.pb.rb +27 -0
- data/test/.ruby-gemset +1 -0
- data/test/.ruby-version +1 -0
- data/test/Gemfile +68 -0
- data/test/Gemfile.lock +269 -0
- data/test/README.md +2 -0
- data/test/Rakefile +6 -0
- data/test/app/assets/javascripts/application.js +16 -0
- data/test/app/assets/stylesheets/application.css +13 -0
- data/test/app/controllers/application_controller.rb +13 -0
- data/test/app/controllers/home_controller.rb +10 -0
- data/test/app/helpers/application_helper.rb +2 -0
- data/test/app/views/home/index.html.erb +2 -0
- data/test/app/views/home/show.html.erb +2 -0
- data/test/app/views/layouts/application.html.erb +14 -0
- data/test/bin/bundle +3 -0
- data/test/bin/rails +4 -0
- data/test/bin/rake +4 -0
- data/test/config/application.rb +29 -0
- data/test/config/boot.rb +4 -0
- data/test/config/database.yml +25 -0
- data/test/config/environment.rb +5 -0
- data/test/config/environments/development.rb +48 -0
- data/test/config/environments/production.rb +95 -0
- data/test/config/environments/test.rb +42 -0
- data/test/config/initializers/backtrace_silencers.rb +7 -0
- data/test/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/config/initializers/inflections.rb +16 -0
- data/test/config/initializers/mime_types.rb +5 -0
- data/test/config/initializers/secret_token.rb +12 -0
- data/test/config/initializers/session_store.rb +3 -0
- data/test/config/initializers/wrap_parameters.rb +14 -0
- data/test/config/locales/en.yml +23 -0
- data/test/config/routes.rb +65 -0
- data/test/db/development.sqlite3 +0 -0
- data/test/db/migrate/20111012050200_add_sessions_table.rb +12 -0
- data/test/db/schema.rb +26 -0
- data/test/db/seeds.rb +7 -0
- data/test/db/test.sqlite3 +0 -0
- data/test/log/development.log +0 -0
- data/test/log/test.log +0 -0
- data/test/test/controllers/home_controller_test.rb +133 -0
- data/test/test/ctlr_test_helper.rb +7 -0
- data/test/test/fixtures/gcp_seed.yml +51 -0
- data/test/test/models/cloudprint_test.rb +186 -0
- data/test/test/models/jingle_test.rb +44 -0
- data/test/test/models/printer_test.rb +99 -0
- data/test/test/models/proxy_test.rb +102 -0
- data/test/test/test_helper.rb +31 -0
- data/test/test/test_kinokero.rb +234 -0
- metadata +462 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Object
|
4
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
5
|
+
# For example, '', ' ', +nil+, [], and {} are all blank.
|
6
|
+
#
|
7
|
+
# This simplifies:
|
8
|
+
#
|
9
|
+
# if address.nil? || address.empty?
|
10
|
+
#
|
11
|
+
# ...to:
|
12
|
+
#
|
13
|
+
# if address.blank?
|
14
|
+
def blank?
|
15
|
+
respond_to?(:empty?) ? empty? : !self
|
16
|
+
end
|
17
|
+
|
18
|
+
# An object is present if it's not <tt>blank?</tt>.
|
19
|
+
def present?
|
20
|
+
!blank?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns object if it's <tt>present?</tt> otherwise returns +nil+.
|
24
|
+
# <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
|
25
|
+
#
|
26
|
+
# This is handy for any representation of objects where blank is the same
|
27
|
+
# as not present at all. For example, this simplifies a common check for
|
28
|
+
# HTTP POST/query parameters:
|
29
|
+
#
|
30
|
+
# state = params[:state] if params[:state].present?
|
31
|
+
# country = params[:country] if params[:country].present?
|
32
|
+
# region = state || country || 'US'
|
33
|
+
#
|
34
|
+
# ...becomes:
|
35
|
+
#
|
36
|
+
# region = params[:state].presence || params[:country].presence || 'US'
|
37
|
+
def presence
|
38
|
+
self if present?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class NilClass
|
43
|
+
# +nil+ is blank:
|
44
|
+
#
|
45
|
+
# nil.blank? # => true
|
46
|
+
def blank?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class FalseClass
|
52
|
+
# +false+ is blank:
|
53
|
+
#
|
54
|
+
# false.blank? # => true
|
55
|
+
def blank?
|
56
|
+
true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class TrueClass
|
61
|
+
# +true+ is not blank:
|
62
|
+
#
|
63
|
+
# true.blank? # => false
|
64
|
+
def blank?
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Array
|
70
|
+
# An array is blank if it's empty:
|
71
|
+
#
|
72
|
+
# [].blank? # => true
|
73
|
+
# [1,2,3].blank? # => false
|
74
|
+
alias_method :blank?, :empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
class Hash
|
78
|
+
# A hash is blank if it's empty:
|
79
|
+
#
|
80
|
+
# {}.blank? # => true
|
81
|
+
# { key: 'value' }.blank? # => false
|
82
|
+
alias_method :blank?, :empty?
|
83
|
+
end
|
84
|
+
|
85
|
+
class String
|
86
|
+
# A string is blank if it's empty or contains whitespaces only:
|
87
|
+
#
|
88
|
+
# ''.blank? # => true
|
89
|
+
# ' '.blank? # => true
|
90
|
+
# ' '.blank? # => true
|
91
|
+
# ' something here '.blank? # => false
|
92
|
+
def blank?
|
93
|
+
self !~ /[^[:space:]]/
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Numeric #:nodoc:
|
98
|
+
# No number is blank:
|
99
|
+
#
|
100
|
+
# 1.blank? # => false
|
101
|
+
# 0.blank? # => false
|
102
|
+
def blank?
|
103
|
+
false
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,1159 @@
|
|
1
|
+
|
2
|
+
module Kinokero
|
3
|
+
|
4
|
+
# #########################################################################
|
5
|
+
|
6
|
+
#
|
7
|
+
# handles all interactions with Google Cloud Print server
|
8
|
+
# but not the jingle-XMPP related connections
|
9
|
+
#
|
10
|
+
# == Options
|
11
|
+
#
|
12
|
+
# * +:url+ - GCP URL (as formed in constants above)
|
13
|
+
# * +:oauth_token+ - supplied OAUTH from GCP
|
14
|
+
# * +:ssl_ca_path+ - local SSL certificates path
|
15
|
+
# * +:verbose+ - true if verbose logging
|
16
|
+
# * +:log_truncate+ - true if truncate long responses from the log
|
17
|
+
# * +:log_response+ - true if log responses from GCP
|
18
|
+
# * +:client_redirect_uri+ - redirect URL for the same
|
19
|
+
#
|
20
|
+
class Cloudprint
|
21
|
+
|
22
|
+
require 'thread'
|
23
|
+
|
24
|
+
extend Forwardable
|
25
|
+
|
26
|
+
# #########################################################################
|
27
|
+
|
28
|
+
# GCP API actions
|
29
|
+
GCP_CONTROL = '/control'
|
30
|
+
GCP_DELETE = '/delete'
|
31
|
+
GCP_FETCH = '/fetch'
|
32
|
+
GCP_LIST = '/list'
|
33
|
+
GCP_REGISTER = '/register'
|
34
|
+
GCP_UPDATE = '/update'
|
35
|
+
|
36
|
+
# GCP ERROR CODES
|
37
|
+
GCP_ERR_XSRF_FAIL = 9 # "XSRF token validation failed."
|
38
|
+
GCP_ERR_NOT_REG_YET = 502 # "Token not registered yet."
|
39
|
+
GCP_ERR_NO_GET_AUTH = 505 # "Unable to get the authorization code."
|
40
|
+
GCP_ERR_EXPIRED = 506 # "Token not registered yet."
|
41
|
+
|
42
|
+
# HTTP RESPONSE CODES
|
43
|
+
HTTP_RESPONSE_OK = 200
|
44
|
+
HTTP_RESPONSE_BAD_REQUEST = 400
|
45
|
+
HTTP_RESPONSE_UNAUTHORIZED = 401
|
46
|
+
HTTP_RESPONSE_FORBIDDEN = 403
|
47
|
+
HTTP_RESPONSE_NOT_FOUND = 404
|
48
|
+
|
49
|
+
# GCP Job States
|
50
|
+
GCP_JOBSTATES = %w(DRAFT HELD QUEUED IN_PROGRESS STOPPED DONE ABORTED)
|
51
|
+
GCP_JOBSTATE_DRAFT = 0 # Job is being created and is not ready for processing yet.;
|
52
|
+
GCP_JOBSTATE_HELD = 1 # Submitted and ready, but should not be processed yet.;
|
53
|
+
GCP_JOBSTATE_QUEUED = 2 # Ready for processing.;
|
54
|
+
GCP_JOBSTATE_IN_PROGRESS = 3 # Currently being processed.
|
55
|
+
GCP_JOBSTATE_STOPPED = 4 # Was in progress, but stopped due to error or user intervention.;
|
56
|
+
GCP_JOBSTATE_DONE = 5 # Processed successfully.;
|
57
|
+
GCP_JOBSTATE_ABORTED = 6 # Aborted due to error or by user action (cancelled).;
|
58
|
+
|
59
|
+
# GCP User action causes
|
60
|
+
GCP_USER_ACTIONS = %(CANCELLED PAUSED OTHER)
|
61
|
+
GCP_USER_ACTION_CANCELLED = 0 # User has cancelled the job
|
62
|
+
GCP_USER_ACTION_PAUSED = 1 # User has paused the job
|
63
|
+
GCP_USER_ACTION_OTHER = 100 # User has performed some other action
|
64
|
+
|
65
|
+
# GCP connection states
|
66
|
+
GCP_CONNECTION_STATE_READY = 2 # "ONLINE"
|
67
|
+
GCP_CONNECTION_STATE_NOT_READY = 3 # "OFFLINE"
|
68
|
+
|
69
|
+
|
70
|
+
# #########################################################################
|
71
|
+
# #########################################################################
|
72
|
+
|
73
|
+
# default options and configurations for cloudprinting
|
74
|
+
DEFAULT_OPTIONS = {
|
75
|
+
:verbose => false, # log everything?
|
76
|
+
:auto_connect => true, # automatically connect active devices?
|
77
|
+
:log_truncate => false, # truncate long responses?
|
78
|
+
:log_response => false # log the responses?
|
79
|
+
}
|
80
|
+
|
81
|
+
# #########################################################################
|
82
|
+
|
83
|
+
# will be used to determine if user options valid
|
84
|
+
# if (in future) any default options were to be off-limits,
|
85
|
+
# then a specific sets of keys will have to be enumerated below
|
86
|
+
VALID_CLOUDPRINT_OPTIONS = DEFAULT_OPTIONS.keys
|
87
|
+
|
88
|
+
# #########################################################################
|
89
|
+
|
90
|
+
@@connection = nil # class-wide client http Faraday connection
|
91
|
+
|
92
|
+
# #########################################################################
|
93
|
+
|
94
|
+
attr_reader :connection, :gcp_control, :jingle
|
95
|
+
|
96
|
+
# #########################################################################
|
97
|
+
|
98
|
+
# instantiate new CloudPrint object
|
99
|
+
#
|
100
|
+
# * *Args* :
|
101
|
+
# - +gcp_control+ - nil or hash of persistent GCP attributes for managed printer
|
102
|
+
# - +options+ - hash of optional settings (see above)
|
103
|
+
# * *Returns* :
|
104
|
+
# - CloudPrint object
|
105
|
+
# * *Raises* :
|
106
|
+
# -
|
107
|
+
#
|
108
|
+
def initialize( gcp_control, options )
|
109
|
+
|
110
|
+
@options = validate_cloudprint_options( DEFAULT_OPTIONS.merge(options) )
|
111
|
+
@gcp_control = validate_gcp_control( gcp_control )
|
112
|
+
verbose = @options[:verbose]
|
113
|
+
|
114
|
+
Cloudprint.make_client_connection( verbose ) # set up faraday connection
|
115
|
+
|
116
|
+
# set up a reason why jingle not started
|
117
|
+
gcp_control[:message] = "device inactive at initialization" unless gcp_control[:is_active]
|
118
|
+
|
119
|
+
if gcp_control[:is_active] &&
|
120
|
+
printer_still_active?() # verify that this printer is still active
|
121
|
+
|
122
|
+
@jingle = Kinokero::Jingle.new( self, gcp_control, verbose )
|
123
|
+
end # if active printer
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# ------------------------------------------------------------------------------
|
129
|
+
|
130
|
+
# returns the client-to-host faraday connection
|
131
|
+
#
|
132
|
+
# * *Args* :
|
133
|
+
# -
|
134
|
+
# * *Returns* :
|
135
|
+
# - Faraday connection object
|
136
|
+
# * *Raises* :
|
137
|
+
# -
|
138
|
+
#
|
139
|
+
def self.client_connection( )
|
140
|
+
# creates connection if first time; assumes verbose
|
141
|
+
@@connection ||= make_client_connection( true )
|
142
|
+
end
|
143
|
+
|
144
|
+
# sets up the client-to-host faraday connection
|
145
|
+
#
|
146
|
+
# * *Args* :
|
147
|
+
# - verbose: true if verbose debugging log output
|
148
|
+
# * *Returns* :
|
149
|
+
# - Faraday connection object
|
150
|
+
# * *Raises* :
|
151
|
+
# -
|
152
|
+
# * *Note* :
|
153
|
+
# - GCP returns responses as content-type: "text/plain",
|
154
|
+
# so we want faraday to parse all responses from JSON to HASH
|
155
|
+
# regardless of content-type
|
156
|
+
#
|
157
|
+
def self.make_client_connection( verbose )
|
158
|
+
|
159
|
+
@@connection = Faraday.new(
|
160
|
+
::Kinokero.gcp_url,
|
161
|
+
:ssl => { :ca_path => ::Kinokero.ssl_ca_path }
|
162
|
+
) do |faraday|
|
163
|
+
# faraday.request :retry
|
164
|
+
# faraday.request :oauth2, {
|
165
|
+
# :token => @gcp_control[ :gcp_access_token ]
|
166
|
+
# }
|
167
|
+
|
168
|
+
faraday.use :cookie_jar # cookiejar handling
|
169
|
+
faraday.request :multipart # multipart files
|
170
|
+
faraday.request :url_encoded # form-encode POST params
|
171
|
+
faraday.response :json, { :content_type => [ /\bjson$/, /\bplain$/, /\btext$/ ] }
|
172
|
+
faraday.response(:logger) if verbose # log requests to STDOUT
|
173
|
+
# faraday.adapter :typhoeus # make requests with typhoeus
|
174
|
+
faraday.adapter Faraday.default_adapter # useful for debugging
|
175
|
+
end # do faraday setup
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
# ------------------------------------------------------------------------------
|
180
|
+
|
181
|
+
# handles the anonymous printer registration protocol
|
182
|
+
#
|
183
|
+
# * *Args* :
|
184
|
+
# - +params+ - hash with parameters:
|
185
|
+
# - +:id+ -
|
186
|
+
# - +:printer_name+ -
|
187
|
+
# - +:status+ - (of printer: string)
|
188
|
+
# - +:capability_ppd+ - (filename)
|
189
|
+
# - +:default_ppd+ - (filename)
|
190
|
+
# - +block+ - asynchronously will receive oauth2 info if user submits token
|
191
|
+
# * *Returns* :
|
192
|
+
# - success/failure via response hash
|
193
|
+
# - +:success+ - true or false
|
194
|
+
# - +:swalapala_printer_id+ - any internal record id for the printer
|
195
|
+
# - +:gcp_printer_name+ - string of printer name
|
196
|
+
# - +:gcp_printer_id+ - gcp printer id for use in requests
|
197
|
+
# - +:gcp_invite_page_url+ - gcp invite page url (see docs)
|
198
|
+
# - +:gcp_easy_reg_url+ - gcp one-click url (see docs for complete_invite_url)
|
199
|
+
# - +:gcp_auto_invite_url+ - gcp automated_invite_url (see docs)
|
200
|
+
# - +:gcp_claim_token_url+ - gcp invite url (see docs)
|
201
|
+
# - +:gcp_printer_reg_token+ - gcp registration_token for claiming printer
|
202
|
+
# - +:gcp_reg_token_duration+ - gcp token_duration in seconds
|
203
|
+
# * *Raises* :
|
204
|
+
# -
|
205
|
+
#
|
206
|
+
# == Anonymous registration protocol
|
207
|
+
#
|
208
|
+
# Anonymous registration requires registering without any login credentials,
|
209
|
+
# and then taking some of the returning tokens to complete the registration.
|
210
|
+
#
|
211
|
+
# Here are the steps required:
|
212
|
+
# * Access registration URL using HTTPS without authentication tokens
|
213
|
+
# * Get token back from Cloud Print Service
|
214
|
+
# * Use the token to claim the printer (with authentication tokens)
|
215
|
+
# * Send query to polling URL;
|
216
|
+
# * receive an authentication_code, jabber_url
|
217
|
+
# * Send authentication_code together with our client_id, etc to oauth2
|
218
|
+
# * receive access_token, refresh_token
|
219
|
+
#
|
220
|
+
# == anonymous registration calls will return:
|
221
|
+
#
|
222
|
+
# registration_token: a human readable string the user will need to claim printer ownership
|
223
|
+
# token_duration: the lifetime of the registration_token, in seconds (the whole registration has to finish within this time frame)
|
224
|
+
# invite_url: the url that a user will need to visit to claim ownership of the printer
|
225
|
+
# complete_invite_url: same thing of invite_url but already containing the registration_token, so that the user doesn't have to insert it manually
|
226
|
+
# invite_page_url: the url of a printable page containing the user's registration_token and url. (The page can be retrieved by the printer in PDF or PWG-raster format based on the HTTP header of the request, as for getting print jobs. At the moment the page size is letter and the resolution for the raster format is 300dpi. In the near future the page will have the page size and resolution based on the printer defaults.)
|
227
|
+
# polling_url: the url that the printer will need to poll for the OAuth2 authorization_code
|
228
|
+
#
|
229
|
+
# ------------------------------------------------------------------------------
|
230
|
+
# Display to user following information to claim the user's printer.
|
231
|
+
#
|
232
|
+
# 'Go claim your printer at this url:'
|
233
|
+
# 'http://www.google.com/cloudprint/claimprinter.html'
|
234
|
+
# 'Use token: response['registration_token']
|
235
|
+
# ------------------------------------------------------------------------------
|
236
|
+
#
|
237
|
+
def self.register_anonymous_printer(params,&block)
|
238
|
+
|
239
|
+
# step 1: issue /register to GCP server
|
240
|
+
reg_response = gcp_anonymous_register(params).body
|
241
|
+
|
242
|
+
if (status = reg_response[ 'success' ]) # success; continues
|
243
|
+
|
244
|
+
poll_thread = Thread.new do
|
245
|
+
# DEPRECATED: pid = fork do
|
246
|
+
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
247
|
+
# step 3: poll GCP asynchronously as a separate process
|
248
|
+
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
249
|
+
poll_response = gcp_anonymous_poll(reg_response).body
|
250
|
+
|
251
|
+
if poll_response[ 'success' ] # successful polling registration
|
252
|
+
|
253
|
+
# step 4, obtain OAuth2 authorization tokens
|
254
|
+
oauth_response = gcp_get_oauth2_tokens(
|
255
|
+
poll_response[ 'authorization_code' ]
|
256
|
+
).body
|
257
|
+
|
258
|
+
# oauth_response['error'].nil?
|
259
|
+
# oauth_response['error'].to_s
|
260
|
+
|
261
|
+
# create the control hash
|
262
|
+
gcp_control = {
|
263
|
+
printer_id: params[:printer_id],
|
264
|
+
gcp_printer_name: reg_response['printers'][0]['name'],
|
265
|
+
|
266
|
+
gcp_xmpp_jid: poll_response['xmpp_jid'],
|
267
|
+
gcp_printerid: reg_response['printers'][0]['id'],
|
268
|
+
gcp_owner_email: poll_response['user_email'],
|
269
|
+
|
270
|
+
gcp_confirmation_url: poll_response['confirmation_page_url'],
|
271
|
+
|
272
|
+
gcp_access_token: oauth_response['access_token'],
|
273
|
+
gcp_refresh_token: oauth_response['refresh_token'],
|
274
|
+
gcp_token_type: oauth_response['token_type'],
|
275
|
+
|
276
|
+
gcp_token_expiry_time: Time.now + oauth_response['expires_in'].to_i,
|
277
|
+
|
278
|
+
capability_ppd: params[:capability_ppd],
|
279
|
+
capability_cdd: params[:capability_cdd],
|
280
|
+
cups_alias: params[:cups_alias],
|
281
|
+
item: params[:item],
|
282
|
+
virgin_access: true, # boolean for dealing with jingle access token quirk
|
283
|
+
is_active: true,
|
284
|
+
|
285
|
+
gcp_uuid: params[:gcp_uuid],
|
286
|
+
gcp_manufacturer: params[:gcp_manufacturer],
|
287
|
+
gcp_model: params[:gcp_model],
|
288
|
+
gcp_setup_url: params[:gcp_setup_url],
|
289
|
+
gcp_support_url: params[:gcp_support_url],
|
290
|
+
gcp_update_url: params[:gcp_update_url],
|
291
|
+
gcp_firmware: params[:gcp_firmware],
|
292
|
+
|
293
|
+
}
|
294
|
+
|
295
|
+
# let calling module save the response for us
|
296
|
+
yield( gcp_control ) # persistence
|
297
|
+
|
298
|
+
end # if polling succeeded
|
299
|
+
|
300
|
+
# DEPRECATED: exit
|
301
|
+
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
302
|
+
|
303
|
+
end # fork block
|
304
|
+
|
305
|
+
# force abort of everything if exception in thread
|
306
|
+
poll_thread.abort_on_exception = true
|
307
|
+
|
308
|
+
# DEPRECATED: Process.detach(pid) # we are not interested in the exit
|
309
|
+
# code of this child and it should become independent
|
310
|
+
|
311
|
+
end # if successful response
|
312
|
+
|
313
|
+
# continue on asynchronously with whatever
|
314
|
+
# step 2: tell user where to claim printer
|
315
|
+
response = {
|
316
|
+
success: status,
|
317
|
+
message: reg_response['message'],
|
318
|
+
swalapala_printer_id: params[:printer_id],
|
319
|
+
cups_alias: params[:cups_alias]
|
320
|
+
}
|
321
|
+
|
322
|
+
if status
|
323
|
+
|
324
|
+
response[:gcp_printer_name] = reg_response['printers'][0]['name']
|
325
|
+
response[:gcp_printer_id] = reg_response['printers'][0]['id']
|
326
|
+
response[:gcp_invite_page_url] = reg_response['invite_page_url']
|
327
|
+
response[:gcp_easy_reg_url] = reg_response['complete_invite_url']
|
328
|
+
response[:gcp_auto_invite_url] = reg_response['automated_invite_url']
|
329
|
+
response[:gcp_claim_token_url] = reg_response['invite_url']
|
330
|
+
response[:gcp_printer_reg_token] = reg_response['registration_token']
|
331
|
+
response[:gcp_reg_token_duration] = reg_response['token_duration']
|
332
|
+
|
333
|
+
end # successful response
|
334
|
+
|
335
|
+
return response
|
336
|
+
end
|
337
|
+
|
338
|
+
# ------------------------------------------------------------------------------
|
339
|
+
|
340
|
+
# gcp_anonymous_register - /register gcp2.0 for anon printer; returns response hash
|
341
|
+
# args:
|
342
|
+
# params - hash with parameters:
|
343
|
+
# :id, :printer_name, :capability_ppd, :default_ppd, :status
|
344
|
+
#
|
345
|
+
# * *Args* :
|
346
|
+
# - ++ -
|
347
|
+
# - ++ -
|
348
|
+
# * *Returns* :
|
349
|
+
# -
|
350
|
+
# * *Raises* :
|
351
|
+
# -
|
352
|
+
#
|
353
|
+
def self.gcp_anonymous_register(params)
|
354
|
+
|
355
|
+
sem_state = CloudDeviceState.new(
|
356
|
+
version: "1.0",
|
357
|
+
printer: PrinterStateSection.new(
|
358
|
+
state: "IDLE"
|
359
|
+
)
|
360
|
+
)
|
361
|
+
|
362
|
+
reg_response = Cloudprint.client_connection.post ::Kinokero.gcp_service + GCP_REGISTER do |req|
|
363
|
+
req.headers['X-CloudPrint-Proxy'] = ::Kinokero.my_proxy_id
|
364
|
+
req.body = {
|
365
|
+
:name => params[:gcp_printer_name],
|
366
|
+
:proxy => ::Kinokero.my_proxy_id,
|
367
|
+
|
368
|
+
gcp_version: '2.0',
|
369
|
+
use_cdd: 'true',
|
370
|
+
uuid: params[:gcp_uuid],
|
371
|
+
manufacturer: params[:gcp_manufacturer],
|
372
|
+
model: params[:gcp_model],
|
373
|
+
setup_url: params[:gcp_setup_url],
|
374
|
+
support_url: params[:gcp_support_url],
|
375
|
+
update_url: params[:gcp_update_url],
|
376
|
+
firmware: params[:gcp_firmware],
|
377
|
+
|
378
|
+
semantic_state: sem_state.to_json,
|
379
|
+
|
380
|
+
:capabilities => Faraday::UploadIO.new(
|
381
|
+
params[:capability_cdd],
|
382
|
+
::Kinokero.mimetype_cdd
|
383
|
+
),
|
384
|
+
|
385
|
+
}
|
386
|
+
|
387
|
+
Kinokero::Log.log_request( 'get anon-reg', req )
|
388
|
+
|
389
|
+
end # request do
|
390
|
+
|
391
|
+
Kinokero::Log.log_response( 'anon-reg', reg_response )
|
392
|
+
|
393
|
+
return reg_response
|
394
|
+
|
395
|
+
end
|
396
|
+
|
397
|
+
# ------------------------------------------------------------------------------
|
398
|
+
|
399
|
+
# gcp_anonymous_register - /register gcp1.0 for anon printer; returns response hash
|
400
|
+
# DEPRECATED: older version
|
401
|
+
#
|
402
|
+
def self.gcp_anonymous_register_1_0(params)
|
403
|
+
|
404
|
+
reg_response = Cloudprint.client_connection.post ::Kinokero.gcp_service + GCP_REGISTER do |req|
|
405
|
+
req.headers['X-CloudPrint-Proxy'] = ::Kinokero.my_proxy_id
|
406
|
+
req.body = {
|
407
|
+
:name => params[:gcp_printer_name],
|
408
|
+
:proxy => ::Kinokero.my_proxy_id,
|
409
|
+
:description => params[:gcp_printer_name],
|
410
|
+
:default_display_name => params[:gcp_printer_name],
|
411
|
+
:status => params[:status],
|
412
|
+
:capabilities => Faraday::UploadIO.new(
|
413
|
+
params[:capability_ppd],
|
414
|
+
::Kinokero.mimetype_ppd
|
415
|
+
),
|
416
|
+
}
|
417
|
+
|
418
|
+
Kinokero::Log.log_request( 'get anon-reg', req )
|
419
|
+
|
420
|
+
end # request do
|
421
|
+
|
422
|
+
Kinokero::Log.log_response( 'anon-reg', reg_response )
|
423
|
+
|
424
|
+
return reg_response
|
425
|
+
|
426
|
+
end
|
427
|
+
|
428
|
+
# req.headers['Authorization'] = "GoogleLogin auth=" + AuthAccessToken
|
429
|
+
# ------------------------------------------------------------------------------
|
430
|
+
# From GCP documentation:
|
431
|
+
# If the user has successfully claimed the token then the poll_response hash is:
|
432
|
+
# success: is true
|
433
|
+
# authorization_code: the OAuth2 authorization_code to be used to get OAuth2
|
434
|
+
# refresh_token and access_token. See details at gcp_get_oauth2_tokens
|
435
|
+
# xmpp_jid: this is the jabber id or email address that needs to be used with
|
436
|
+
# Google Talk to subscribe for print notifications.
|
437
|
+
# This needs to be retained in the printer memory forever.
|
438
|
+
# user_email: the email address of the user that claimed the
|
439
|
+
# registration_token at the previous step
|
440
|
+
# confirmation_page_url: the url of a printable page that confirms to the user
|
441
|
+
# that the printer has been registered to him/herself.
|
442
|
+
# The same notes relative to retrieving the invite_page_url above apply here too.
|
443
|
+
# ------------------------------------------------------------------------------
|
444
|
+
# gcp_anonymous_poll - polls GCP server to see if user has claimed token
|
445
|
+
# returns polling response hash
|
446
|
+
# args:
|
447
|
+
# response - gcp response hash
|
448
|
+
# ------------------------------------------------------------------------------
|
449
|
+
#
|
450
|
+
# * *Args* :
|
451
|
+
# - ++ -
|
452
|
+
# - ++ -
|
453
|
+
# * *Returns* :
|
454
|
+
# -
|
455
|
+
# * *Raises* :
|
456
|
+
# -
|
457
|
+
#
|
458
|
+
def self.gcp_anonymous_poll(anon_response)
|
459
|
+
|
460
|
+
poll_url = anon_response['polling_url'] + Kinokero.proxy_client_id
|
461
|
+
printer_id = anon_response['printers'][0]['id']
|
462
|
+
|
463
|
+
# countdown timer for polling loop
|
464
|
+
0.step( anon_response['token_duration'].to_i, ::Kinokero.polling_secs ) do |i|
|
465
|
+
|
466
|
+
sleep ::Kinokero.polling_secs # sleep here until next poll
|
467
|
+
|
468
|
+
# poll GCP to see if printer claimed yet?
|
469
|
+
poll_response = gcp_poll_request( poll_url )
|
470
|
+
|
471
|
+
# user claimed printer success ?
|
472
|
+
# if reg_id == printer_id ?????????
|
473
|
+
return poll_response if
|
474
|
+
poll_response.body[ 'success' ] ||
|
475
|
+
poll_response.body["errorCode"] != GCP_ERR_NOT_REG_YET
|
476
|
+
|
477
|
+
#else, continue to poll
|
478
|
+
|
479
|
+
end # sleep/polling loop
|
480
|
+
|
481
|
+
# log failure
|
482
|
+
Kinokero::Log.debug( 'anon-poll' ) { "polling timed out" } if @options[:verbose]
|
483
|
+
|
484
|
+
return { 'success' => false, 'message' => "polling timed out" } # return failure
|
485
|
+
|
486
|
+
end
|
487
|
+
|
488
|
+
# ------------------------------------------------------------------------------
|
489
|
+
|
490
|
+
# gcp_poll_request -- returns response hash after trying a polling POST
|
491
|
+
#
|
492
|
+
# * *Args* :
|
493
|
+
# - ++ -
|
494
|
+
# - ++ -
|
495
|
+
# * *Returns* :
|
496
|
+
# -
|
497
|
+
# * *Raises* :
|
498
|
+
# -
|
499
|
+
#
|
500
|
+
def self.gcp_poll_request( poll_url )
|
501
|
+
|
502
|
+
poll_response = Cloudprint.client_connection.post( poll_url ) do |req| # connection poll request
|
503
|
+
req.headers['X-CloudPrint-Proxy'] = ::Kinokero.my_proxy_id
|
504
|
+
end # post poll response request
|
505
|
+
|
506
|
+
Kinokero::Log.log_response( 'anon-poll', poll_response )
|
507
|
+
|
508
|
+
return poll_response
|
509
|
+
|
510
|
+
end
|
511
|
+
|
512
|
+
# ------------------------------------------------------------------------------
|
513
|
+
|
514
|
+
# * *Args* :
|
515
|
+
# - +auth_code+ -
|
516
|
+
# - ++ -
|
517
|
+
# * *Returns* :
|
518
|
+
# - oauth_response hash
|
519
|
+
# * *Raises* :
|
520
|
+
# -
|
521
|
+
#
|
522
|
+
# From GCP documentation:
|
523
|
+
# the printer must use the authorization_code to obtain OAuth2 Auth tokens,
|
524
|
+
# themselves used to authenticate subsequent API calls to Google Cloud Print.
|
525
|
+
#
|
526
|
+
# There are two types of tokens involved:
|
527
|
+
#
|
528
|
+
# * The refresh_token should be retained in printer memory forever.
|
529
|
+
# It can then be used to retrieve a temporary access_token.
|
530
|
+
# * The access_token needs to be refreshed every hour,
|
531
|
+
# and is used as authentication credentials in subsequent API calls.
|
532
|
+
#
|
533
|
+
# The printer can initially retrieve both tokens together by POSTing
|
534
|
+
# the authorization_code to the OAuth2 token endpoint at
|
535
|
+
# https://accounts.google.com/o/oauth2/token,
|
536
|
+
#
|
537
|
+
# along with the following parameters:
|
538
|
+
# * client_id (the same that you appended to polling_url when fetching
|
539
|
+
# the authorization_code)
|
540
|
+
# * redirect_uri (set it to 'oob')
|
541
|
+
# * client_secret (obtained along with client_id as part of your
|
542
|
+
# * client credentials)
|
543
|
+
# * grant_type="authorization_code"
|
544
|
+
# * scope=https://www.googleapis.com/auth/cloudprint
|
545
|
+
# (scope identifies the Google service being accessed, in this case GCP)
|
546
|
+
# If this request succeeds, a refresh token and short-lived access token
|
547
|
+
# will be returned via JSON. You can then use the access token to make
|
548
|
+
# API calls by attaching the following Authorization HTTP header to each of
|
549
|
+
# your API calls: Authorization: OAuth YOUR_ACCESS_TOKEN.
|
550
|
+
# You can retrieve additional access tokens once the first expires
|
551
|
+
# (after an hour) by using the token endpoint with your refresh token,
|
552
|
+
# client credentials, and the parameter grant_type=refresh_token.
|
553
|
+
#
|
554
|
+
def self.gcp_get_oauth2_tokens( auth_code )
|
555
|
+
|
556
|
+
oauth_response = Cloudprint.client_connection.post( ::Kinokero.oauth2_token_endpoint ) do |req|
|
557
|
+
req.body = {
|
558
|
+
:client_id => Kinokero.proxy_client_id,
|
559
|
+
:client_secret => Kinokero.proxy_client_secret,
|
560
|
+
:redirect_uri => ::Kinokero.authorization_redirect_uri,
|
561
|
+
:code => auth_code,
|
562
|
+
:grant_type => "authorization_code",
|
563
|
+
:scope => ::Kinokero.authorization_scope,
|
564
|
+
}
|
565
|
+
|
566
|
+
Kinokero::Log.log_request( 'get oauth2 code', req )
|
567
|
+
|
568
|
+
end # request do
|
569
|
+
|
570
|
+
Kinokero::Log.log_response( 'oauth2 code', oauth_response )
|
571
|
+
|
572
|
+
if oauth_response.status == HTTP_RESPONSE_OK
|
573
|
+
|
574
|
+
oauth_response.body['success'] = true
|
575
|
+
|
576
|
+
else # failed to fetch token
|
577
|
+
|
578
|
+
oauth_response.body['success'] = false
|
579
|
+
Kinokero::Log.error( 'oauth2 token fetch fail' ) { "**********************************" }
|
580
|
+
|
581
|
+
end # if..then..else success
|
582
|
+
|
583
|
+
return oauth_response
|
584
|
+
|
585
|
+
end
|
586
|
+
|
587
|
+
# #########################################################################
|
588
|
+
# #########################################################################
|
589
|
+
# instance methods
|
590
|
+
# #########################################################################
|
591
|
+
# #########################################################################
|
592
|
+
|
593
|
+
# ------------------------------------------------------------------------------
|
594
|
+
|
595
|
+
def gtalk_start_connection(&block)
|
596
|
+
|
597
|
+
if @jingle.nil?
|
598
|
+
|
599
|
+
Kinokero::Log.error( "jingle not started yet; #{@gcp_control[:message]}" )
|
600
|
+
|
601
|
+
else
|
602
|
+
|
603
|
+
@jingle.gtalk_start_connection do |printerid|
|
604
|
+
yield( printerid )
|
605
|
+
end # closure for doing print stuff
|
606
|
+
|
607
|
+
end
|
608
|
+
|
609
|
+
end
|
610
|
+
|
611
|
+
# ------------------------------------------------------------------------------
|
612
|
+
|
613
|
+
# gcp_get_job_file -- returns the job file to be printed
|
614
|
+
#
|
615
|
+
# * *Args* :
|
616
|
+
# - +file_url+ - url to get the file for printing
|
617
|
+
# * *Returns* :
|
618
|
+
# - nil if failed to get file; else file itself
|
619
|
+
# * *Raises* :
|
620
|
+
# -
|
621
|
+
#
|
622
|
+
def gcp_get_job_file( file_url )
|
623
|
+
|
624
|
+
file_response = Cloudprint.client_connection.get( file_url ) do |req| # connection get job file request
|
625
|
+
req.headers['X-CloudPrint-Proxy'] = ::Kinokero.my_proxy_id
|
626
|
+
req.headers['Authorization'] = gcp_form_auth_token()
|
627
|
+
|
628
|
+
log_request( 'get job file', req )
|
629
|
+
end # post poll response request
|
630
|
+
|
631
|
+
# check the RESPONSE_HEADER for SUCCESS
|
632
|
+
return ( file_response.env.status == HTTP_RESPONSE_OK ?
|
633
|
+
file_response.env.body :
|
634
|
+
nil
|
635
|
+
)
|
636
|
+
|
637
|
+
end
|
638
|
+
|
639
|
+
|
640
|
+
# ------------------------------------------------------------------------------
|
641
|
+
|
642
|
+
# refresh an expired gcp auth token
|
643
|
+
#
|
644
|
+
# * *Args* :
|
645
|
+
# -
|
646
|
+
# * *Returns* :
|
647
|
+
# - oauth_response hash showing succcess/fail
|
648
|
+
# * *Raises* :
|
649
|
+
# -
|
650
|
+
#
|
651
|
+
def gcp_refresh_tokens( )
|
652
|
+
|
653
|
+
oauth_response = Cloudprint.client_connection.post( ::Kinokero.oauth2_token_endpoint ) do |req|
|
654
|
+
req.body = {
|
655
|
+
:client_id => Kinokero.proxy_client_id,
|
656
|
+
:client_secret => Kinokero.proxy_client_secret,
|
657
|
+
:refresh_token => @gcp_control[:gcp_refresh_token],
|
658
|
+
:grant_type => "refresh_token"
|
659
|
+
}
|
660
|
+
|
661
|
+
log_request( 'get refresh token', req )
|
662
|
+
|
663
|
+
end # request do
|
664
|
+
|
665
|
+
if oauth_response.status == HTTP_RESPONSE_OK
|
666
|
+
|
667
|
+
@gcp_control[:gcp_access_token] = oauth_response.body['access_token']
|
668
|
+
@gcp_control[:gcp_token_expiry_time] =
|
669
|
+
Time.now + oauth_response.body['expires_in'].to_i
|
670
|
+
@gcp_control[:virgin_access] = false
|
671
|
+
oauth_response.body['success'] = true
|
672
|
+
|
673
|
+
else # failed to refresh token
|
674
|
+
|
675
|
+
oauth_response.body['success'] = false
|
676
|
+
Kinokero::Log.error( 'refresh fail' ) { "**********************************" }
|
677
|
+
|
678
|
+
end # if..then..else success
|
679
|
+
|
680
|
+
log_response( 'refresh token', oauth_response )
|
681
|
+
|
682
|
+
return oauth_response.body
|
683
|
+
|
684
|
+
end
|
685
|
+
|
686
|
+
# ------------------------------------------------------------------------------
|
687
|
+
|
688
|
+
# gets a list of jobs queued for a printer
|
689
|
+
#
|
690
|
+
# * *Args* :
|
691
|
+
# - +printerid+ - gcp printer_id for the printer
|
692
|
+
# * *Returns* :
|
693
|
+
# - fetch hash including queue
|
694
|
+
# * *Raises* :
|
695
|
+
# -
|
696
|
+
#
|
697
|
+
def gcp_get_printer_fetch( printerid )
|
698
|
+
|
699
|
+
fetch_response = Cloudprint.client_connection.post( ::Kinokero.gcp_service + GCP_FETCH ) do |req|
|
700
|
+
req.headers['Authorization'] = gcp_form_auth_token()
|
701
|
+
req.body = {
|
702
|
+
:printerid => printerid
|
703
|
+
}
|
704
|
+
|
705
|
+
log_request( 'fetch queue', req )
|
706
|
+
|
707
|
+
end # request do
|
708
|
+
log_response( 'fetch queue', fetch_response )
|
709
|
+
|
710
|
+
return fetch_response.body
|
711
|
+
|
712
|
+
end
|
713
|
+
|
714
|
+
# ------------------------------------------------------------------------------
|
715
|
+
|
716
|
+
def gcp_delete_printer( skip_gcp=nil )
|
717
|
+
|
718
|
+
unless skip_gcp
|
719
|
+
|
720
|
+
remove_response = Cloudprint.client_connection.post( ::Kinokero.gcp_service + GCP_DELETE ) do |req|
|
721
|
+
req.headers['Authorization'] = gcp_form_auth_token()
|
722
|
+
req.body = {
|
723
|
+
:printerid => @gcp_control[:gcp_printerid]
|
724
|
+
}
|
725
|
+
|
726
|
+
log_request( 'remove printer', req )
|
727
|
+
|
728
|
+
end # request do
|
729
|
+
log_response( 'remove printer', remove_response )
|
730
|
+
|
731
|
+
end # skip issuing gcp command
|
732
|
+
|
733
|
+
if skip_gcp || remove_response[ 'success' ]
|
734
|
+
|
735
|
+
# unsubscribe & close jingle connection
|
736
|
+
@jingle.gtalk_close_connection() unless @jingle.nil?
|
737
|
+
@gcp_control[:is_active] = false
|
738
|
+
@jingle = nil # make available to garbage collect
|
739
|
+
|
740
|
+
end
|
741
|
+
|
742
|
+
return skip_gcp || remove_response.body
|
743
|
+
|
744
|
+
end
|
745
|
+
|
746
|
+
# ------------------------------------------------------------------------------
|
747
|
+
|
748
|
+
# report status for a print job
|
749
|
+
#
|
750
|
+
# * *Args* :
|
751
|
+
# - +jobid+ - gcp job_id
|
752
|
+
# - +status+ - GCP_JOBSTATUS_ type
|
753
|
+
# - +nbr_pages+ - number of pages printed
|
754
|
+
# * *Returns* :
|
755
|
+
# -
|
756
|
+
# * *Raises* :
|
757
|
+
# -
|
758
|
+
#
|
759
|
+
def gcp_job_status( jobid, status, nbr_pages )
|
760
|
+
|
761
|
+
state_diff = PrintJobStateDiff.new(
|
762
|
+
state: JobState.new( type: status ),
|
763
|
+
pages_printed: nbr_pages
|
764
|
+
)
|
765
|
+
|
766
|
+
return generic_job_status( jobid, state_diff )
|
767
|
+
|
768
|
+
end
|
769
|
+
|
770
|
+
# ------------------------------------------------------------------------------
|
771
|
+
|
772
|
+
# report abort status for a print job
|
773
|
+
#
|
774
|
+
# * *Args* :
|
775
|
+
# - +jobid+ - gcp job_id
|
776
|
+
# - +status+ - GCP_USER_ACTION status
|
777
|
+
# - +nbr_pages+ - number of pages printed
|
778
|
+
# * *Returns* :
|
779
|
+
# -
|
780
|
+
# * *Raises* :
|
781
|
+
# -
|
782
|
+
#
|
783
|
+
def gcp_job_status_abort( jobid, status, nbr_pages )
|
784
|
+
|
785
|
+
state_diff = PrintJobStateDiff.new(
|
786
|
+
state: JobState.new(
|
787
|
+
type: GCP_JOBSTATE_ABORTED,
|
788
|
+
device_action_cause: DeviceActionCause.new(
|
789
|
+
error_code: "DOWNLOAD_FAILURE"
|
790
|
+
)
|
791
|
+
),
|
792
|
+
pages_printed: nbr_pages
|
793
|
+
)
|
794
|
+
|
795
|
+
return generic_job_status( jobid, state_diff )
|
796
|
+
|
797
|
+
end
|
798
|
+
|
799
|
+
# ------------------------------------------------------------------------------
|
800
|
+
|
801
|
+
# version: "1.0",
|
802
|
+
# printer: PrinterStateSection.new(
|
803
|
+
# state: "STOPPED",
|
804
|
+
# marker_state: MarkerState.new(
|
805
|
+
# item: [
|
806
|
+
# MarkerState::Item.new(
|
807
|
+
# vendor_id: "black",
|
808
|
+
# state: 'EXHAUSTED',
|
809
|
+
# level_percent: 0
|
810
|
+
# ),
|
811
|
+
# MarkerState::Item.new(
|
812
|
+
# vendor_id: "color",
|
813
|
+
# state: 'OK',
|
814
|
+
# level_percent: 88,
|
815
|
+
# level_pages: 100
|
816
|
+
# )
|
817
|
+
# ]
|
818
|
+
# )
|
819
|
+
# )
|
820
|
+
|
821
|
+
def gcp_ready_state_changed( ready_state, state, reason )
|
822
|
+
|
823
|
+
# TODO: screen out unexpected states
|
824
|
+
#
|
825
|
+
# state_diff will only show what has changed from previous CDS
|
826
|
+
state_diff = CloudDeviceState.new(
|
827
|
+
printer: PrinterStateSection.new(
|
828
|
+
state: state.to_s.upcase,
|
829
|
+
)
|
830
|
+
)
|
831
|
+
|
832
|
+
status_response = Cloudprint.client_connection.post( ::Kinokero.gcp_service + GCP_UPDATE ) do |req|
|
833
|
+
req.headers['Authorization'] = gcp_form_auth_token()
|
834
|
+
req.body = {
|
835
|
+
printerid: @gcp_control[:gcp_printerid],
|
836
|
+
semantic_state_diff: state_diff.to_json
|
837
|
+
}
|
838
|
+
|
839
|
+
log_request( 'device update', req )
|
840
|
+
|
841
|
+
end # request do
|
842
|
+
|
843
|
+
log_response( 'device update', status_response )
|
844
|
+
|
845
|
+
return status_response.body
|
846
|
+
|
847
|
+
|
848
|
+
end
|
849
|
+
|
850
|
+
# ------------------------------------------------------------------------------
|
851
|
+
|
852
|
+
# checks GCP server to see if printer still active
|
853
|
+
#
|
854
|
+
# * *Args* :
|
855
|
+
# -
|
856
|
+
# * *Returns* :
|
857
|
+
# - true if still active; false if not
|
858
|
+
# * *Raises* :
|
859
|
+
# -
|
860
|
+
# * *side effects* :
|
861
|
+
# - changes :is_active status; sets :message for reason
|
862
|
+
#
|
863
|
+
def printer_still_active?()
|
864
|
+
list_result = gcp_get_printer_list
|
865
|
+
|
866
|
+
is_active = false # assume failure
|
867
|
+
|
868
|
+
if list_result["success"]
|
869
|
+
|
870
|
+
if list_result["printers"].empty?
|
871
|
+
|
872
|
+
@gcp_control[:message] = "proxy printer list empty"
|
873
|
+
|
874
|
+
# try to find a matching printer in the list
|
875
|
+
elsif list_result["printers"].any? { |p| p["id"] == @gcp_control[:gcp_printerid] }
|
876
|
+
|
877
|
+
is_active = true # success here!
|
878
|
+
|
879
|
+
else # failed to find matching printer in list
|
880
|
+
|
881
|
+
@gcp_control[:message] = "matching printer not found in proxy printer list"
|
882
|
+
|
883
|
+
end # if..then..else check proxy printer list
|
884
|
+
|
885
|
+
else # failed to get list result
|
886
|
+
|
887
|
+
@gcp_control[:message] = list_result["message"] || "couldn't obtain proxy printer list"
|
888
|
+
|
889
|
+
end # able/not get list results
|
890
|
+
|
891
|
+
return (@gcp_control[:is_active] = is_active)
|
892
|
+
|
893
|
+
end
|
894
|
+
|
895
|
+
# ------------------------------------------------------------------------------
|
896
|
+
|
897
|
+
# gcp protocol to get the list of registered printers for the proxy
|
898
|
+
#
|
899
|
+
# * *Args* :
|
900
|
+
# -
|
901
|
+
# * *Returns* :
|
902
|
+
# -
|
903
|
+
# * *Raises* :
|
904
|
+
# -
|
905
|
+
#
|
906
|
+
def gcp_get_printer_list( )
|
907
|
+
|
908
|
+
list_response = Cloudprint.client_connection.post( ::Kinokero.gcp_service + GCP_LIST ) do |req|
|
909
|
+
req.headers['Authorization'] = gcp_form_auth_token()
|
910
|
+
req.body = {
|
911
|
+
:proxy => ::Kinokero.my_proxy_id
|
912
|
+
}
|
913
|
+
|
914
|
+
log_request( 'get printer list', req )
|
915
|
+
|
916
|
+
end # request do
|
917
|
+
log_response( 'get printer list', list_response )
|
918
|
+
|
919
|
+
return list_response.body
|
920
|
+
|
921
|
+
end
|
922
|
+
|
923
|
+
# ------------------------------------------------------------------------------
|
924
|
+
|
925
|
+
# forms a fresh TOKEN_TYPE + AUTH_TOKEN string
|
926
|
+
#
|
927
|
+
# * *Args* :
|
928
|
+
# -
|
929
|
+
# * *Returns* :
|
930
|
+
# - string for current auth type & token
|
931
|
+
# * *Raises* :
|
932
|
+
# -
|
933
|
+
#
|
934
|
+
def gcp_form_auth_token()
|
935
|
+
return '' if @gcp_control.nil?
|
936
|
+
gcp_refresh_tokens if Time.now >= @gcp_control[:gcp_token_expiry_time]
|
937
|
+
return "#{ @gcp_control[:gcp_token_type] } #{ @gcp_control[:gcp_access_token] }"
|
938
|
+
end
|
939
|
+
|
940
|
+
# ------------------------------------------------------------------------------
|
941
|
+
|
942
|
+
# forms a fresh AUTH_TOKEN string
|
943
|
+
#
|
944
|
+
# * *Args* :
|
945
|
+
# -
|
946
|
+
# * *Returns* :
|
947
|
+
# - string for current auth token
|
948
|
+
# * *Raises* :
|
949
|
+
# -
|
950
|
+
#
|
951
|
+
def gcp_form_jingle_auth_token()
|
952
|
+
return '' if @gcp_control.nil?
|
953
|
+
|
954
|
+
# jingle quirk seems to be unable to use the initial, long access token
|
955
|
+
# which was returned by the oauth2 call (longer length)
|
956
|
+
# but it jingle readily handles the refreshed access token (shorter length)
|
957
|
+
if @gcp_control[:virgin_access] ||
|
958
|
+
Time.now >= @gcp_control[:gcp_token_expiry_time]
|
959
|
+
gcp_refresh_tokens
|
960
|
+
end
|
961
|
+
|
962
|
+
return @gcp_control[:gcp_access_token]
|
963
|
+
end
|
964
|
+
|
965
|
+
|
966
|
+
# ------------------------------------------------------------------------------
|
967
|
+
|
968
|
+
#
|
969
|
+
# log_request -- will log the farady request params if verbose setting
|
970
|
+
#
|
971
|
+
# * *Args* :
|
972
|
+
# - +msg+ - string to identify position in protocol sequence
|
973
|
+
# - +req+ - gcp request hash
|
974
|
+
# * *Returns* :
|
975
|
+
# -
|
976
|
+
# * *Raises* :
|
977
|
+
# -
|
978
|
+
#
|
979
|
+
def log_request( msg, req )
|
980
|
+
Kinokero::Log.log_request( msg, req, @options[:verbose] )
|
981
|
+
end
|
982
|
+
|
983
|
+
# ------------------------------------------------------------------------------
|
984
|
+
|
985
|
+
# log the GCP response
|
986
|
+
#
|
987
|
+
# * *Args* :
|
988
|
+
# - +msg+ - string to identify position in protocol sequence
|
989
|
+
# - +response+ - gcp response hash
|
990
|
+
# * *Returns* :
|
991
|
+
# -
|
992
|
+
# * *Raises* :
|
993
|
+
# -
|
994
|
+
#
|
995
|
+
def log_response( msg, response )
|
996
|
+
Kinokero::Log.log_response(
|
997
|
+
msg,
|
998
|
+
response,
|
999
|
+
@options[:verbose] && @options[:log_response]
|
1000
|
+
)
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
# ------------------------------------------------------------------------------
|
1004
|
+
private # because untested
|
1005
|
+
# ------------------------------------------------------------------------------
|
1006
|
+
# simple auth token requester;
|
1007
|
+
# won't work for accounts that require two-step
|
1008
|
+
#
|
1009
|
+
# * *Args* :
|
1010
|
+
# - +email+ - proxy owner's email
|
1011
|
+
# - +password+ - proxy owner's password
|
1012
|
+
# * *Returns* :
|
1013
|
+
# - gcp response hash
|
1014
|
+
# * *Raises* :
|
1015
|
+
# -
|
1016
|
+
# * *NOTE* :
|
1017
|
+
# - currently this is untested!
|
1018
|
+
#
|
1019
|
+
def gcp_get_auth_tokens(email, password)
|
1020
|
+
|
1021
|
+
auth_response = Cloudprint.client_connection.post( ::Kinokero.login_url ) do |req|
|
1022
|
+
req.body = {
|
1023
|
+
:accountType => 'GOOGLE',
|
1024
|
+
:Email => email,
|
1025
|
+
:Passwd => password,
|
1026
|
+
:service => ::Kinokero.gcp_service,
|
1027
|
+
:source => ::Kinokero.my_proxy_id
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
log_request( 'get auth tokens', req )
|
1031
|
+
|
1032
|
+
end # request do
|
1033
|
+
|
1034
|
+
return auth_response.body
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
# ------------------------------------------------------------------------------
|
1038
|
+
|
1039
|
+
# ------------------------------------------------------------------------------
|
1040
|
+
|
1041
|
+
# #########################################################################
|
1042
|
+
|
1043
|
+
protected
|
1044
|
+
|
1045
|
+
# #########################################################################
|
1046
|
+
|
1047
|
+
# ------------------------------------------------------------------------------
|
1048
|
+
|
1049
|
+
# converts a status enum to GCP code word
|
1050
|
+
#
|
1051
|
+
# * *Args* :
|
1052
|
+
# - +status+ - enum value for status
|
1053
|
+
# * *Returns* :
|
1054
|
+
# - string of the GCP code
|
1055
|
+
# * *Raises* :
|
1056
|
+
# -
|
1057
|
+
#
|
1058
|
+
def status_to_code(status)
|
1059
|
+
return GCP_JOBSTATES[ status ]
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
# ------------------------------------------------------------------------------
|
1063
|
+
|
1064
|
+
# converts an abort status enum to GCP code word
|
1065
|
+
#
|
1066
|
+
# * *Args* :
|
1067
|
+
# - +status+ - enum value for abort status
|
1068
|
+
# * *Returns* :
|
1069
|
+
# - string of the GCP user action code
|
1070
|
+
# * *Raises* :
|
1071
|
+
# -
|
1072
|
+
#
|
1073
|
+
def abort_status_to_code(status)
|
1074
|
+
status = 2 if status == GCP_USER_ACTION_OTHER
|
1075
|
+
return GCP_USER_ACTIONS[ status ]
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
|
1079
|
+
|
1080
|
+
# ------------------------------------------------------------------------------
|
1081
|
+
|
1082
|
+
# ------------------------------------------------------------------------------
|
1083
|
+
|
1084
|
+
# generic job status reporting
|
1085
|
+
#
|
1086
|
+
# * *Args* :
|
1087
|
+
# - +jobid+ - job id string
|
1088
|
+
# - +state_diff+ - proto_buf JobStateDIff object
|
1089
|
+
# * *Returns* :
|
1090
|
+
# -
|
1091
|
+
# * *Raises* :
|
1092
|
+
# -
|
1093
|
+
#
|
1094
|
+
def generic_job_status( jobid, state_diff )
|
1095
|
+
|
1096
|
+
status_response = Cloudprint.client_connection.post( ::Kinokero.gcp_service + GCP_CONTROL ) do |req|
|
1097
|
+
req.headers['Authorization'] = gcp_form_auth_token()
|
1098
|
+
req.body = {
|
1099
|
+
:jobid => jobid,
|
1100
|
+
:semantic_state_diff => state_diff.to_json
|
1101
|
+
}
|
1102
|
+
|
1103
|
+
log_request( 'status control', req )
|
1104
|
+
|
1105
|
+
end # request do
|
1106
|
+
log_response( 'status control', status_response )
|
1107
|
+
|
1108
|
+
return status_response.body
|
1109
|
+
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
# ------------------------------------------------------------------------------
|
1113
|
+
|
1114
|
+
# validates user's options
|
1115
|
+
#
|
1116
|
+
# * *Args* :
|
1117
|
+
# - +options+ - described in constants
|
1118
|
+
# * *Returns* :
|
1119
|
+
# - options hash itself
|
1120
|
+
# * *Raises* :
|
1121
|
+
# - ArgumentError if invalid option present
|
1122
|
+
#
|
1123
|
+
def validate_cloudprint_options(options)
|
1124
|
+
|
1125
|
+
# init stuff goes here; options validations;
|
1126
|
+
options.assert_valid_keys(VALID_CLOUDPRINT_OPTIONS)
|
1127
|
+
|
1128
|
+
# future options checking using following pattern
|
1129
|
+
# unless (options[:any_key].nil?
|
1130
|
+
# raise ArgumentError,":any_key must exist"
|
1131
|
+
# end
|
1132
|
+
|
1133
|
+
return options
|
1134
|
+
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
# ------------------------------------------------------------------------------
|
1138
|
+
|
1139
|
+
# validate the gcp control options and set object attribute
|
1140
|
+
#
|
1141
|
+
# TBD: validate the options
|
1142
|
+
#
|
1143
|
+
# * *Args* :
|
1144
|
+
# - +gcp_control+ - options for setting attribute
|
1145
|
+
# * *Returns* :
|
1146
|
+
# - the gcp_control hash
|
1147
|
+
# * *Raises* :
|
1148
|
+
# -
|
1149
|
+
#
|
1150
|
+
def validate_gcp_control( gcp_control )
|
1151
|
+
return gcp_control
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
|
1155
|
+
# #########################################################################
|
1156
|
+
end # class Cloudprint
|
1157
|
+
|
1158
|
+
# #########################################################################
|
1159
|
+
end # module Kinokero
|