kinokero 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +44 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +49 -0
  7. data/Gemfile_mock +8 -0
  8. data/LICENSE +20 -0
  9. data/README.md +955 -0
  10. data/Rakefile +1 -0
  11. data/console/.ruby-gemset +1 -0
  12. data/console/.ruby-version +1 -0
  13. data/console/Gemfile +6 -0
  14. data/console/Gemfile.lock +63 -0
  15. data/console/README.md +34 -0
  16. data/console/config/application_configuration.rb +39 -0
  17. data/console/config/gcp_seed.yml +73 -0
  18. data/console/config/kinokero_initializer_template.rb +108 -0
  19. data/console/console +2 -0
  20. data/console/irb_console +2 -0
  21. data/console/lib/appliance_common.rb +73 -0
  22. data/console/twiga.rb +579 -0
  23. data/hp-check.log +244 -0
  24. data/kinokero.gemspec +35 -0
  25. data/lib/kinokero.rb +183 -0
  26. data/lib/kinokero/blank.rb +105 -0
  27. data/lib/kinokero/cloudprint.rb +1159 -0
  28. data/lib/kinokero/device.rb +6 -0
  29. data/lib/kinokero/jingle.rb +176 -0
  30. data/lib/kinokero/log.rb +157 -0
  31. data/lib/kinokero/printer.rb +313 -0
  32. data/lib/kinokero/proxy.rb +341 -0
  33. data/lib/kinokero/ruby_extensions.rb +21 -0
  34. data/lib/kinokero/sasl_xoauth2.rb +164 -0
  35. data/lib/kinokero/version.rb +3 -0
  36. data/lib/proto/cloud_device_description.proto +18 -0
  37. data/lib/proto/cloud_device_state.proto +30 -0
  38. data/lib/proto/cloud_device_state_type.proto +20 -0
  39. data/lib/proto/cloud_device_ui_state.proto +42 -0
  40. data/lib/proto/cloud_device_ui_state_severity.proto +12 -0
  41. data/lib/proto/cloud_job_ticket.proto +18 -0
  42. data/lib/proto/collate.proto +4 -0
  43. data/lib/proto/collate_ticket_item.proto +5 -0
  44. data/lib/proto/color.proto +42 -0
  45. data/lib/proto/color_ticket_item.proto +12 -0
  46. data/lib/proto/copies.proto +6 -0
  47. data/lib/proto/copies_ticket_item.proto +5 -0
  48. data/lib/proto/cover.proto +31 -0
  49. data/lib/proto/cover_state.proto +25 -0
  50. data/lib/proto/device_action_cause.proto +19 -0
  51. data/lib/proto/dpi.proto +27 -0
  52. data/lib/proto/dpi_ticket_item.proto +13 -0
  53. data/lib/proto/duplex.proto +15 -0
  54. data/lib/proto/duplex_ticket_item.proto +8 -0
  55. data/lib/proto/fit_to_page.proto +20 -0
  56. data/lib/proto/fit_to_page_ticket_item.proto +8 -0
  57. data/lib/proto/input_tray_state.proto +31 -0
  58. data/lib/proto/input_tray_unit.proto +35 -0
  59. data/lib/proto/job_state.proto +143 -0
  60. data/lib/proto/local_settings.proto +36 -0
  61. data/lib/proto/localized_string.proto +119 -0
  62. data/lib/proto/margins.proto +33 -0
  63. data/lib/proto/margins_ticket_item.proto +14 -0
  64. data/lib/proto/marker.proto +62 -0
  65. data/lib/proto/marker_state.proto +31 -0
  66. data/lib/proto/media_path.proto +6 -0
  67. data/lib/proto/media_path_state.proto +25 -0
  68. data/lib/proto/media_size.proto +216 -0
  69. data/lib/proto/media_size_ticket_item.proto +17 -0
  70. data/lib/proto/output_bin_state.proto +31 -0
  71. data/lib/proto/output_bin_unit.proto +32 -0
  72. data/lib/proto/page_orientation.proto +16 -0
  73. data/lib/proto/page_orientation_ticket_item.proto +9 -0
  74. data/lib/proto/page_range.proto +15 -0
  75. data/lib/proto/page_range_ticket_item.proto +7 -0
  76. data/lib/proto/print_job_state.proto +18 -0
  77. data/lib/proto/print_job_state_diff.proto +13 -0
  78. data/lib/proto/print_job_ui_state.proto +24 -0
  79. data/lib/proto/print_ticket_section.proto +30 -0
  80. data/lib/proto/printer_description_section.proto +107 -0
  81. data/lib/proto/printer_state_section.proto +49 -0
  82. data/lib/proto/printer_ui_state_section.proto +39 -0
  83. data/lib/proto/printing_speed.proto +35 -0
  84. data/lib/proto/pwg_raster_config.proto +176 -0
  85. data/lib/proto/range_capability.proto +14 -0
  86. data/lib/proto/reverse_order.proto +5 -0
  87. data/lib/proto/reverse_order_ticket_item.proto +5 -0
  88. data/lib/proto/scanner_description_section.proto +5 -0
  89. data/lib/proto/scanner_state_section.proto +25 -0
  90. data/lib/proto/select_capability.proto +31 -0
  91. data/lib/proto/supported_content_type.proto +12 -0
  92. data/lib/proto/typed_value_capability.proto +15 -0
  93. data/lib/proto/vendor_capability.proto +40 -0
  94. data/lib/proto/vendor_state.proto +26 -0
  95. data/lib/proto/vendor_ticket_item.proto +9 -0
  96. data/lib/proto_lib/cloud_device_state.pb.rb +21 -0
  97. data/lib/proto_lib/cloud_device_state_type.pb.rb +16 -0
  98. data/lib/proto_lib/cloud_device_ui_state.pb.rb +22 -0
  99. data/lib/proto_lib/cloud_device_ui_state_severity.pb.rb +17 -0
  100. data/lib/proto_lib/collate.pb.rb +11 -0
  101. data/lib/proto_lib/color.pb.rb +31 -0
  102. data/lib/proto_lib/copies.pb.rb +12 -0
  103. data/lib/proto_lib/cover.pb.rb +21 -0
  104. data/lib/proto_lib/cover_state.pb.rb +27 -0
  105. data/lib/proto_lib/device_action_cause.pb.rb +18 -0
  106. data/lib/proto_lib/dpi.pb.rb +27 -0
  107. data/lib/proto_lib/duplex.pb.rb +26 -0
  108. data/lib/proto_lib/fit_to_page.pb.rb +28 -0
  109. data/lib/proto_lib/input_tray_state.pb.rb +30 -0
  110. data/lib/proto_lib/input_tray_unit.pb.rb +25 -0
  111. data/lib/proto_lib/job_state.pb.rb +99 -0
  112. data/lib/proto_lib/localized_string.pb.rb +118 -0
  113. data/lib/proto_lib/margins.pb.rb +30 -0
  114. data/lib/proto_lib/marker.pb.rb +45 -0
  115. data/lib/proto_lib/marker_state.pb.rb +30 -0
  116. data/lib/proto_lib/media_path.pb.rb +11 -0
  117. data/lib/proto_lib/media_path_state.pb.rb +27 -0
  118. data/lib/proto_lib/media_size.pb.rb +198 -0
  119. data/lib/proto_lib/output_bin_state.pb.rb +30 -0
  120. data/lib/proto_lib/output_bin_unit.pb.rb +22 -0
  121. data/lib/proto_lib/page_orientation.pb.rb +26 -0
  122. data/lib/proto_lib/page_range.pb.rb +20 -0
  123. data/lib/proto_lib/print_job_state_diff.pb.rb +12 -0
  124. data/lib/proto_lib/printer_description_section.pb.rb +30 -0
  125. data/lib/proto_lib/printer_state_section.pb.rb +23 -0
  126. data/lib/proto_lib/printer_ui_state_section.pb.rb +28 -0
  127. data/lib/proto_lib/printing_speed.pb.rb +21 -0
  128. data/lib/proto_lib/pwg_raster_config.pb.rb +103 -0
  129. data/lib/proto_lib/range_capability.pb.rb +19 -0
  130. data/lib/proto_lib/reverse_order.pb.rb +11 -0
  131. data/lib/proto_lib/scanner_description_section.pb.rb +10 -0
  132. data/lib/proto_lib/scanner_state_section.pb.rb +17 -0
  133. data/lib/proto_lib/select_capability.pb.rb +22 -0
  134. data/lib/proto_lib/supported_content_type.pb.rb +13 -0
  135. data/lib/proto_lib/typed_value_capability.pb.rb +19 -0
  136. data/lib/proto_lib/vendor_capability.pb.rb +23 -0
  137. data/lib/proto_lib/vendor_state.pb.rb +27 -0
  138. data/test/.ruby-gemset +1 -0
  139. data/test/.ruby-version +1 -0
  140. data/test/Gemfile +68 -0
  141. data/test/Gemfile.lock +269 -0
  142. data/test/README.md +2 -0
  143. data/test/Rakefile +6 -0
  144. data/test/app/assets/javascripts/application.js +16 -0
  145. data/test/app/assets/stylesheets/application.css +13 -0
  146. data/test/app/controllers/application_controller.rb +13 -0
  147. data/test/app/controllers/home_controller.rb +10 -0
  148. data/test/app/helpers/application_helper.rb +2 -0
  149. data/test/app/views/home/index.html.erb +2 -0
  150. data/test/app/views/home/show.html.erb +2 -0
  151. data/test/app/views/layouts/application.html.erb +14 -0
  152. data/test/bin/bundle +3 -0
  153. data/test/bin/rails +4 -0
  154. data/test/bin/rake +4 -0
  155. data/test/config/application.rb +29 -0
  156. data/test/config/boot.rb +4 -0
  157. data/test/config/database.yml +25 -0
  158. data/test/config/environment.rb +5 -0
  159. data/test/config/environments/development.rb +48 -0
  160. data/test/config/environments/production.rb +95 -0
  161. data/test/config/environments/test.rb +42 -0
  162. data/test/config/initializers/backtrace_silencers.rb +7 -0
  163. data/test/config/initializers/filter_parameter_logging.rb +4 -0
  164. data/test/config/initializers/inflections.rb +16 -0
  165. data/test/config/initializers/mime_types.rb +5 -0
  166. data/test/config/initializers/secret_token.rb +12 -0
  167. data/test/config/initializers/session_store.rb +3 -0
  168. data/test/config/initializers/wrap_parameters.rb +14 -0
  169. data/test/config/locales/en.yml +23 -0
  170. data/test/config/routes.rb +65 -0
  171. data/test/db/development.sqlite3 +0 -0
  172. data/test/db/migrate/20111012050200_add_sessions_table.rb +12 -0
  173. data/test/db/schema.rb +26 -0
  174. data/test/db/seeds.rb +7 -0
  175. data/test/db/test.sqlite3 +0 -0
  176. data/test/log/development.log +0 -0
  177. data/test/log/test.log +0 -0
  178. data/test/test/controllers/home_controller_test.rb +133 -0
  179. data/test/test/ctlr_test_helper.rb +7 -0
  180. data/test/test/fixtures/gcp_seed.yml +51 -0
  181. data/test/test/models/cloudprint_test.rb +186 -0
  182. data/test/test/models/jingle_test.rb +44 -0
  183. data/test/test/models/printer_test.rb +99 -0
  184. data/test/test/models/proxy_test.rb +102 -0
  185. data/test/test/test_helper.rb +31 -0
  186. data/test/test/test_kinokero.rb +234 -0
  187. 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