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 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ twiga_console
@@ -0,0 +1 @@
1
+ ruby-2.0.0-p247
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ruby "2.0.0"
4
+
5
+ gem 'kinokero', :git => "git://github.com/dsaronin/kinokero.git"
6
+ gem 'cups'
@@ -0,0 +1,63 @@
1
+ GIT
2
+ remote: git://github.com/dsaronin/kinokero.git
3
+ revision: b5c815573cdf04d8923d0c53d9134893a5bd7495
4
+ specs:
5
+ kinokero (0.0.5)
6
+ activesupport
7
+ beefcake
8
+ faraday
9
+ faraday-cookie_jar
10
+ faraday_middleware
11
+ json
12
+ logger
13
+ simple_oauth
14
+ typhoeus
15
+ xmpp4r
16
+
17
+ GEM
18
+ remote: https://rubygems.org/
19
+ specs:
20
+ activesupport (4.1.4)
21
+ i18n (~> 0.6, >= 0.6.9)
22
+ json (~> 1.7, >= 1.7.7)
23
+ minitest (~> 5.1)
24
+ thread_safe (~> 0.1)
25
+ tzinfo (~> 1.1)
26
+ beefcake (0.5.0)
27
+ cups (0.1.9)
28
+ domain_name (0.5.19)
29
+ unf (>= 0.0.5, < 1.0.0)
30
+ ethon (0.7.1)
31
+ ffi (>= 1.3.0)
32
+ faraday (0.9.0)
33
+ multipart-post (>= 1.2, < 3)
34
+ faraday-cookie_jar (0.0.6)
35
+ faraday (>= 0.7.4)
36
+ http-cookie (~> 1.0.0)
37
+ faraday_middleware (0.9.1)
38
+ faraday (>= 0.7.4, < 0.10)
39
+ ffi (1.9.3)
40
+ http-cookie (1.0.2)
41
+ domain_name (~> 0.5)
42
+ i18n (0.6.11)
43
+ json (1.8.1)
44
+ logger (1.2.8)
45
+ minitest (5.4.0)
46
+ multipart-post (2.0.0)
47
+ simple_oauth (0.2.0)
48
+ thread_safe (0.3.4)
49
+ typhoeus (0.6.9)
50
+ ethon (>= 0.7.1)
51
+ tzinfo (1.2.1)
52
+ thread_safe (~> 0.1)
53
+ unf (0.1.4)
54
+ unf_ext
55
+ unf_ext (0.0.6)
56
+ xmpp4r (0.5.6)
57
+
58
+ PLATFORMS
59
+ ruby
60
+
61
+ DEPENDENCIES
62
+ cups
63
+ kinokero!
@@ -0,0 +1,34 @@
1
+ kinokero
2
+ ========
3
+
4
+ faraday-based http client for interacting with Google CloudPrint &amp; other
5
+
6
+ # Kinokero
7
+
8
+ TODO: Write a gem description
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'kinokero'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install kinokero
23
+
24
+ ## Usage
25
+
26
+ TODO: Write usage instructions here
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it
31
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
32
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
33
+ 4. Push to the branch (`git push origin my-new-feature`)
34
+ 5. Create new Pull Request
@@ -0,0 +1,39 @@
1
+ # ****************************************************************************
2
+ # ******* general configuration for console appliance ********************
3
+ # ****************************************************************************
4
+
5
+ # ****************************************************************************
6
+
7
+ # ****************************************************************************
8
+ # Twiga module common methods invoked here
9
+ load File.expand_path('../../lib/appliance_common.rb', __FILE__)
10
+
11
+ # #########################################################################
12
+
13
+ module Twiga
14
+
15
+ # #########################################################################
16
+ # ****************************************************************************
17
+ require 'rubygems'
18
+ require "kinokero"
19
+ require 'json'
20
+
21
+ # ****************************************************************************
22
+ # ****** mimic the way RAILS sets up required gems *************************
23
+ # Set up gems listed in the Gemfile.
24
+ # ****************************************************************************
25
+ # puts File.expand_path('../../Gemfile', __FILE__)
26
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
27
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
28
+
29
+ # ****************************************************************************
30
+ # ****************************************************************************
31
+ # ****************************************************************************
32
+
33
+ # -----------------------------------------------------------------------------
34
+
35
+ # ****************************************************************************
36
+ # ****************************************************************************
37
+ # ****************************************************************************
38
+ # #########################################################################
39
+ end # module Twiga
@@ -0,0 +1,73 @@
1
+ ---
2
+ test:
3
+ printer_id: 0
4
+ item: test
5
+ cups_alias: laserjet_1102w
6
+ is_active: true
7
+ virgin_access: false
8
+ gcp_xmpp_jid: 345e29425516857a8c70581f58a180c7@cloudprint.googleusercontent.com
9
+ gcp_confirmation_url: https://www.google.com/cloudprint/regconfirmpage?printername=gcp_test_printer&email=dsanderson77@gmail.com&dpi=300&pagesize=215900,279400
10
+ gcp_owner_email: dsanderson77@gmail.com
11
+ gcp_printer_name: gcp_test_printer
12
+ gcp_printerid: d0510370-f36b-356f-1a82-f93c0756a5d9
13
+ gcp_access_token: ya29.dQBJZ59OXnON3x4AAADdeIzdkmsmnD_wwnboRXHXFJWrRmnaASlV8VoJurmBig
14
+ gcp_refresh_token: 1/MPXAGbW_0AkF5eSFtc96PesWp68lFnOz-HUSUd5aU_w
15
+ gcp_token_type: Bearer
16
+ gcp_token_expiry_time: 2014-09-03 17:22:54.101172269 -07:00
17
+ capability_ppd: /etc/cups/ppd/laserjet_1102w.ppd
18
+ capability_cdd: /etc/cups/cdd/laserjet_1102w.cdd
19
+ gcp_uuid: VND3R11877
20
+ gcp_manufacturer: Hewlett-Packard
21
+ gcp_model: LaserJet P1102w
22
+ gcp_setup_url: http://www8.hp.com/us/en/campaigns/wireless-printing-center/printer-setup-help.html
23
+ gcp_support_url: http://h10025.www1.hp.com/ewfrf/wc/product?product=4110396&lc=en&cc=us&dlc=en&lang=en&cc=us
24
+ gcp_update_url: http://h10025.www1.hp.com/ewfrf/wc/product?product=4110396&lc=en&cc=us&dlc=en&lang=en&cc=us
25
+ gcp_firmware: '20130703'
26
+ wild:
27
+ gcp_xmpp_jid: 9bd529179dfa665930799067d4016e9e@cloudprint.googleusercontent.com
28
+ gcp_confirmation_url: https://www.google.com/cloudprint/regconfirmpage?printername=gcp_wild_printer&email=conjugalis@gmail.com&dpi=300&pagesize=215900,279400
29
+ gcp_owner_email: conjugalis@gmail.com
30
+ gcp_printer_name: gcp_wild_printer
31
+ gcp_printerid: 94f42964-2269-fdeb-99af-4ed1bd3f07fe
32
+ gcp_access_token: ya29.hABKAR8kZeUq4vbgUgxV_xv3oXY-cc-FRkZvTvrf5H3fGuklzY6h8s29
33
+ gcp_refresh_token: 1/JDe8Y-rjbxRDzswmLEDuh8CJdnQ40Q10QhRyNWhyHn0
34
+ gcp_token_type: Bearer
35
+ gcp_token_expiry_time: 2014-09-17 19:11:40.886171387 -07:00
36
+ is_active: false
37
+ virgin_access: true
38
+ printer_id: 0
39
+ item: wild
40
+ cups_alias: laserjet_1102w
41
+ capability_ppd: /etc/cups/ppd/laserjet_1102w.ppd
42
+ capability_cdd: /etc/cups/cdd/laserjet_1102w.cdd
43
+ gcp_uuid: VND3R11877
44
+ gcp_manufacturer: Hewlett-Packard
45
+ gcp_model: LaserJet P1102w
46
+ gcp_setup_url: http://www8.hp.com/us/en/campaigns/wireless-printing-center/printer-setup-help.html
47
+ gcp_support_url: http://h10025.www1.hp.com/ewfrf/wc/product?product=4110396&lc=en&cc=us&dlc=en&lang=en&cc=us
48
+ gcp_update_url: http://h10025.www1.hp.com/ewfrf/wc/product?product=4110396&lc=en&cc=us&dlc=en&lang=en&cc=us
49
+ gcp_firmware: '20130703'
50
+ lime:
51
+ gcp_xmpp_jid: 3e0163e6a4802ba77c29d73410aad929@cloudprint.googleusercontent.com
52
+ gcp_confirmation_url: https://www.google.com/cloudprint/regconfirmpage?printername=gcp_lime_printer&email=dsanderson77@gmail.com&dpi=300&pagesize=215900,279400
53
+ gcp_owner_email: dsanderson77@gmail.com
54
+ gcp_printer_name: gcp_lime_printer
55
+ gcp_printerid: 8231517c-716d-4b0a-f721-83fdbe52a05d
56
+ gcp_access_token: ya29.UgA-fXhCHp0ZiR0AAADbQqXfRA-ks9CK2POGZA8yYF2a1vhEQa4G4_QSltFK_g
57
+ gcp_refresh_token: 1/NjwUJyyrebUIXsvl1mvqDmKCr-VtZfYR3pPusRPkLH0
58
+ gcp_token_type: Bearer
59
+ gcp_token_expiry_time: 2014-07-30 10:27:22.592090444 -07:00
60
+ is_active: false
61
+ virgin_access: false
62
+ printer_id: 0
63
+ item: lime
64
+ cups_alias: laserjet_1102w
65
+ capability_ppd: /etc/cups/ppd/laserjet_1102w.ppd
66
+ capability_cdd: /etc/cups/cdd/laserjet_1102w.cdd
67
+ gcp_uuid: VND3R11877
68
+ gcp_manufacturer: Hewlett-Packard
69
+ gcp_model: LaserJet P1102w
70
+ gcp_setup_url: http://www8.hp.com/us/en/campaigns/wireless-printing-center/printer-setup-help.html
71
+ gcp_support_url: http://h10025.www1.hp.com/ewfrf/wc/product?product=4110396&lc=en&cc=us&dlc=en&lang=en&cc=us
72
+ gcp_update_url: http://h10025.www1.hp.com/ewfrf/wc/product?product=4110396&lc=en&cc=us&dlc=en&lang=en&cc=us
73
+ gcp_firmware: '20130703'
@@ -0,0 +1,108 @@
1
+ # OPTIONAL: Use this as a template for changing kinokero configuration
2
+ # put it in your config/initializers directory and rename to
3
+ # kinokero.rb
4
+ # values shown below are the defaults in kinokero
5
+ # if for some reason you wish to respecify these defaults, precede the
6
+ # default name with "Kinokero::", such as the example for my_proxy_id
7
+ #
8
+ Kinokero.setup do |config|
9
+
10
+ # ---------------------------------------------------------------------
11
+ # class Proxy required
12
+ # ---------------------------------------------------------------------
13
+
14
+ # two verbose settings are needed: one for Proxy instances and the
15
+ # other for any class-level decisions; the one below is class-level
16
+ config.verbose = false
17
+
18
+ # unique name for this running of the GCP connector client
19
+ # config.my_proxy_id = Kinokero::MY_PROXY_ID
20
+
21
+ # picks up your registered Google API client id from environment
22
+ # config.proxy_client_id = ENV["GCP_PROXY_CLIENT_ID"]
23
+
24
+ # picks up your registered Google API client secret from environment
25
+ # config.proxy_client_secret = ENV["GCP_PROXY_CLIENT_SECRET"]
26
+
27
+ # picks up a unique serial number for this proxy (future expansion)
28
+ # config.proxy_serial_nbr = ENV["GCP_PROXY_SERIAL_NBR"]
29
+
30
+ # ---------------------------------------------------------------------
31
+ # class Cloudprint required
32
+ # ---------------------------------------------------------------------
33
+
34
+ # how to encoade oauth files
35
+ # config.mimetype_oauth = MIMETYPE_OAUTH
36
+
37
+ # how to encode PPD files
38
+ # config.mimetype_ppd = MIMETYPE_PPD
39
+
40
+ # how to encode CDD files
41
+ # config.mimetype_cdd = MIMETYPE_CDD
42
+
43
+ # secs to sleep before register polling again
44
+ # config.polling_secs = POLLING_SECS
45
+
46
+ # number of characters to truncate response logs
47
+ # config.truncate_log = TRUNCATE_LOG
48
+
49
+ # cloudprint registration main URL
50
+ # config.followup_host = FOLLOWUP_HOST #
51
+
52
+ # GCP constant
53
+ # config.followup_uri = FOLLOWUP_URI #
54
+
55
+ # Google primary URI
56
+ # config.gaia_host = GAIA_HOST #
57
+
58
+ # user-based OAUTH2 access (future expansion)
59
+ # config.login_uri = LOGIN_URI #
60
+
61
+ # GCPS primary URL
62
+ # config.gcp_url = GCP_URL #
63
+
64
+ # GCPS service point
65
+ # config.gcp_service = GCP_SERVICE #
66
+
67
+ # full pathname of SSL certificates path for this machine
68
+ # config.ssl_ca_path = SSL_CERT_PATH
69
+
70
+
71
+ # GCP documentation constants
72
+ # config.authorization_scope = AUTHORIZATION_SCOPE #
73
+ # config.authorization_redirect_uri = AUTHORIZATION_REDIRECT_URI #
74
+
75
+ # Google OAUTH2 token handling endpint URL
76
+ # config.oauth2_token_endpoint = OAUTH2_TOKEN_ENDPOINT #
77
+
78
+
79
+ # ---------------------------------------------------------------------
80
+ # class Jingle required
81
+ # ---------------------------------------------------------------------
82
+
83
+ # Google jingle (XMPP) server domain
84
+ # config.xmpp_server = XMPP_SERVER #
85
+
86
+ # Jingle extension for google push stanzas
87
+ # config.ns_google_push = NS_GOOGLE_PUSH #
88
+
89
+ # Jingle cloudprint channel domain
90
+ # config.gcp_channel = GCP_CHANNEL #
91
+
92
+ # ---------------------------------------------------------------------
93
+ # cups testpage file path
94
+ # ---------------------------------------------------------------------
95
+
96
+ # pathname location of a printable testfile on local machine
97
+ # config.cups_testpage_file = CUPS_TESTPAGE_FILE
98
+
99
+ # ---------------------------------------------------------------------
100
+ # printer device/cups related
101
+ # ---------------------------------------------------------------------
102
+
103
+ # number of seconds to wait before polling printer status
104
+ # config.printer_poll_cycle = PRINTER_POLL_CYCLE
105
+
106
+
107
+
108
+ end # Kinokero setup
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ bundle exec "`pwd`/twiga.rb -m"
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ bundle exec irb -r "`pwd`/twiga.rb -m"
@@ -0,0 +1,73 @@
1
+ # ****************************************************************************
2
+ # ******* common methods for twiga appliance ********************
3
+ # ****************************************************************************
4
+ module Twiga
5
+ # **************************************************************************
6
+
7
+ @@env = nil
8
+
9
+ # -----------------------------------------------------------------------------
10
+ def self.say_info(msg)
11
+ print "\e[1;34m" + msg + "\e[0m"
12
+ end
13
+ # -----------------------------------------------------------------------------
14
+ def self.say_warn(msg)
15
+ print "\e[1;33m" + msg + "\e[0m"
16
+ end
17
+ # -----------------------------------------------------------------------------
18
+ def self.say_err(msg)
19
+ print "\e[1;31m" + msg + "\e[0m"
20
+ end
21
+ # -----------------------------------------------------------------------------
22
+
23
+ # -----------------------------------------------------------------------------
24
+ # env= -- sets the environment for appliance
25
+ # raises ArgumentError exception if invalid
26
+ # -----------------------------------------------------------------------------
27
+ def self.env=( new_env )
28
+
29
+ if %w(development production test staging).include?( new_env )
30
+ @@env = new_env # ok to set environment
31
+ else
32
+ raise ArgumentError, "Unrecognized environment: #{new_env}"
33
+ end # environment validation check
34
+
35
+ return @@env
36
+
37
+ end
38
+
39
+ # -----------------------------------------------------------------------------
40
+ # set_environment! -- initializes and sets environment variable
41
+ # tries to get from environment variable if missing
42
+ # else defaults to 'development'
43
+ # -----------------------------------------------------------------------------
44
+ def self.set_environment!
45
+ env= ( ENV[ 'TWIGA_ENV' ] || 'development' )
46
+ end
47
+
48
+ def self.env
49
+ @@env || set_environment!
50
+ end
51
+
52
+ def self.env_is_production?
53
+ @@env == 'production'
54
+ end
55
+
56
+ def self.env_is_test?
57
+ @@env == 'test'
58
+ end
59
+
60
+ def self.env_is_staging?
61
+ @@env == 'staging'
62
+ end
63
+
64
+ def self.env_is_development?
65
+ @@env == 'development'
66
+ end
67
+
68
+
69
+
70
+ # ****************************************************************************
71
+ # ****************************************************************************
72
+ # ****************************************************************************
73
+ end # module Twiga
@@ -0,0 +1,579 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # ****************************************************************************
4
+ # ***** app configured, initialized, and ruby-prepped here ****************
5
+ # ****************************************************************************
6
+
7
+ puts "\nTwiga configuration; running ruby #{ RUBY_VERSION }"
8
+
9
+ # make it so that our lib is at the head of the load/require path array
10
+ $:.unshift( File.expand_path('../lib', __FILE__) )
11
+ # kick off module configurations
12
+ load File.expand_path('../config/application_configuration.rb', __FILE__)
13
+
14
+ # ****************************************************************************
15
+
16
+ require 'yaml'
17
+ require 'erb'
18
+ require 'active_support/core_ext/hash'
19
+ require 'cups'
20
+ require 'thread'
21
+
22
+ # ****************************************************************************
23
+ # get temporary managed printer data for testing interfaces
24
+ # ****************************************************************************
25
+
26
+ GCP_SEED_FILE = "../config/gcp_seed.yml"
27
+
28
+ # #########################################################################
29
+ # ########## working with seed data #####################################
30
+ # #########################################################################
31
+
32
+ # -----------------------------------------------------------------------------
33
+
34
+ def load_gcp_seed()
35
+ if @seed_data.nil?
36
+ @seed_data = YAML.load(
37
+ ERB.new(
38
+ File.read(
39
+ File.expand_path(GCP_SEED_FILE , __FILE__ )
40
+ )
41
+ ).result
42
+ )
43
+
44
+ puts "seed_data: #{@seed_data.object_id}"
45
+
46
+ end
47
+ end
48
+
49
+ # -----------------------------------------------------------------------------
50
+
51
+ def write_gcp_seed()
52
+ unless @seed_data.nil?
53
+
54
+ ::Twiga.say_warn "updating seed data\n"
55
+
56
+ File.open(
57
+ File.expand_path(GCP_SEED_FILE , __FILE__ ),
58
+ 'w'
59
+ ) { |f|
60
+ YAML.dump(@seed_data, f)
61
+ }
62
+
63
+ end # unless no seed data yet
64
+ end
65
+
66
+ # -----------------------------------------------------------------------------
67
+
68
+ def get_gcp_control( item )
69
+ load_gcp_seed()
70
+
71
+ @seed_data[ item ].symbolize_keys
72
+ end
73
+
74
+ # -----------------------------------------------------------------------------
75
+
76
+ def update_gcp_seed(gcp_control, item, &block )
77
+
78
+ @seed_data[item] ||= {} # if first time
79
+
80
+ @seed_data[item]['gcp_xmpp_jid'] = gcp_control[:gcp_xmpp_jid]
81
+ @seed_data[item]['gcp_confirmation_url'] = gcp_control[:gcp_confirmation_url]
82
+ @seed_data[item]['gcp_owner_email'] = gcp_control[:gcp_owner_email]
83
+
84
+ @seed_data[item]['gcp_printer_name'] = gcp_control[:gcp_printer_name]
85
+ @seed_data[item]['gcp_printerid'] = gcp_control[:gcp_printerid]
86
+
87
+ @seed_data[item]['gcp_access_token'] = gcp_control[:gcp_access_token]
88
+ @seed_data[item]['gcp_refresh_token'] = gcp_control[:gcp_refresh_token]
89
+ @seed_data[item]['gcp_token_type'] = gcp_control[:gcp_token_type]
90
+ @seed_data[item]['gcp_token_expiry_time'] = gcp_control[:gcp_token_expiry_time]
91
+
92
+ @seed_data[item]['is_active'] = gcp_control[:is_active]
93
+ @seed_data[item]['virgin_access'] = gcp_control[:virgin_access]
94
+
95
+ yield( @seed_data[item] ) if block_given?
96
+
97
+ write_gcp_seed()
98
+
99
+ end
100
+
101
+ # -----------------------------------------------------------------------------
102
+
103
+
104
+ def add_gcp_seed_request( seed, new_request )
105
+
106
+ # add in some addtional initializing seed info from the request block
107
+ seed['printer_id'] = new_request[:printer_id]
108
+ seed['item'] = new_request[:item]
109
+ seed['cups_alias'] = new_request[:cups_alias]
110
+ seed['capability_ppd'] = new_request[:capability_ppd]
111
+ seed['capability_cdd'] = new_request[:capability_cdd]
112
+ seed['gcp_printer_name'] = new_request[:gcp_printer_name]
113
+
114
+ seed['gcp_uuid'] = new_request[:gcp_uuid]
115
+ seed['gcp_manufacturer'] = new_request[:gcp_manufacturer]
116
+ seed['gcp_model'] = new_request[:gcp_model]
117
+ seed['gcp_setup_url'] = new_request[:gcp_setup_url]
118
+ seed['gcp_support_url'] = new_request[:gcp_support_url]
119
+ seed['gcp_update_url'] = new_request[:gcp_update_url]
120
+ seed['gcp_firmware'] = new_request[:gcp_firmware]
121
+
122
+ return seed
123
+ end
124
+
125
+ # -----------------------------------------------------------------------------
126
+
127
+ def update_gcp_seed_tokens( item )
128
+
129
+ @seed_data[item] ||= {} # if first time
130
+
131
+ gcp = @proxy.my_devices[item].gcp_printer_control
132
+
133
+ @seed_data[item]['gcp_access_token'] = gcp[:gcp_access_token]
134
+ @seed_data[item]['gcp_token_expiry_time'] = gcp[:gcp_token_expiry_time]
135
+ @seed_data[item]['virgin_access'] = gcp[:virgin_access]
136
+
137
+ write_gcp_seed()
138
+ end
139
+
140
+ # -----------------------------------------------------------------------------
141
+
142
+ def build_device_list()
143
+
144
+ load_gcp_seed() # load the seed data
145
+
146
+ # prep to build up a hash of gcp_control hashes
147
+ gcp_control_hash = {}
148
+
149
+ @seed_data.each_key do |item|
150
+ gcp_control_hash[ item ] = @seed_data[ item ].symbolize_keys # strip item into hash with keys
151
+ end # convert each seed to a device object
152
+
153
+ return gcp_control_hash
154
+
155
+ end # convert each seed to a device object
156
+
157
+ # -----------------------------------------------------------------------------
158
+
159
+ # if item hasn't yet been defined in seed data, create one out of
160
+ # thin air by using test as a template
161
+ def build_gcp_request( item )
162
+
163
+ use_item = validate_item( item )
164
+
165
+ return {
166
+ item: item,
167
+ printer_id: 0, # will be cue to create new record
168
+ gcp_printer_name: "gcp_#{item}_printer",
169
+ capability_ppd: @seed_data[use_item]['capability_ppd'],
170
+ capability_cdd: @seed_data[use_item]['capability_cdd'],
171
+ cups_alias: @seed_data[use_item]['cups_alias'],
172
+ gcp_uuid: @seed_data[use_item]['gcp_uuid'],
173
+ gcp_manufacturer: @seed_data[use_item]['gcp_manufacturer'],
174
+ gcp_model: @seed_data[use_item]['gcp_model'],
175
+ gcp_setup_url: @seed_data[use_item]['gcp_setup_url'],
176
+ gcp_support_url: @seed_data[use_item]['gcp_support_url'],
177
+ gcp_update_url: @seed_data[use_item]['gcp_update_url'],
178
+ gcp_firmware: @seed_data[use_item]['gcp_firmware'],
179
+ }
180
+ end
181
+
182
+ # -----------------------------------------------------------------------------
183
+
184
+ def validate_item( item )
185
+ return ( @seed_data.has_key?(item) ? item : 'test' )
186
+ end
187
+
188
+ # -----------------------------------------------------------------------------
189
+
190
+ # #########################################################################
191
+ # ########## one-offs for do_work commands ##############################
192
+ # #########################################################################
193
+
194
+ # -----------------------------------------------------------------------------
195
+
196
+ def do_ready_state( item )
197
+ @proxy.do_ready_state( validate_item( item ) )
198
+ end
199
+
200
+
201
+ # -----------------------------------------------------------------------------
202
+
203
+ def do_list( item )
204
+ @proxy.do_list( validate_item( item ) )
205
+ end
206
+
207
+ # -----------------------------------------------------------------------------
208
+
209
+ def do_fetch( item )
210
+ jobs = @proxy.do_fetch_jobs( validate_item( item ) )
211
+ if jobs['success']
212
+ ::Twiga.say_warn "#{jobs['request']['params']['printerid'].first }: #{jobs['jobs'].size } print jobs"
213
+ else # had error
214
+ ::Twiga.say_err "#{jobs['request']['params']['printerid'].first }: #{jobs['message']}"
215
+ end
216
+ end
217
+
218
+
219
+ # -----------------------------------------------------------------------------
220
+
221
+ def do_register( item )
222
+ new_request = build_gcp_request( item )
223
+
224
+ response = @proxy.do_register( new_request ) do |gcp_control|
225
+
226
+ update_gcp_seed(gcp_control, gcp_control[:item] ) do |seed|
227
+ add_gcp_seed_request( seed, new_request )
228
+ end # seed additions
229
+
230
+ end # do persist new printer information
231
+
232
+ unless response[:success]
233
+ ::Twiga.say_err "printer registration failed: #{response[:message]}"
234
+ end
235
+
236
+ end
237
+
238
+ # -----------------------------------------------------------------------------
239
+
240
+ def do_delete( item )
241
+ item = validate_item( item )
242
+ @proxy.do_delete( item )
243
+ @seed_data[item]['is_active'] = false
244
+ write_gcp_seed()
245
+ end
246
+
247
+ # -----------------------------------------------------------------------------
248
+
249
+ def do_refresh( item )
250
+ item = validate_item( item )
251
+ @proxy.do_refresh( item )
252
+ update_gcp_seed_tokens( item )
253
+ end
254
+
255
+ # -----------------------------------------------------------------------------
256
+
257
+ def do_connect( item )
258
+ item = validate_item( item )
259
+ ::Twiga.say_info "Starting jingle connection to device: #{item}...\n"
260
+ @proxy.do_connect( item )
261
+ update_gcp_seed_tokens( item )
262
+ end
263
+
264
+ # -----------------------------------------------------------------------------
265
+
266
+ def show_seed()
267
+ ::Twiga.say_warn "seed_data: #{@seed_data.object_id}"
268
+ puts @seed_data.inspect
269
+ end
270
+
271
+ # -----------------------------------------------------------------------------
272
+
273
+ def show_gcp( item )
274
+ gcp = @proxy.my_devices[validate_item( item )].gcp_printer_control
275
+ ::Twiga.say_warn "cloudprint gcp: #{gcp.object_id}\n"
276
+ puts gcp.inspect
277
+
278
+ end
279
+
280
+ # -----------------------------------------------------------------------------
281
+
282
+ def show_devices()
283
+ ::Twiga.say_warn "device obj: #{@proxy.my_devices.object_id}\n"
284
+ puts @proxy.my_devices.inspect
285
+ end
286
+
287
+ # -----------------------------------------------------------------------------
288
+
289
+ def show_time( item )
290
+ item = validate_item( item )
291
+ extime = @proxy.my_devices[item].gcp_printer_control[:gcp_token_expiry_time]
292
+ ::Twiga.say_warn "expiry for #{item}: #{extime.class.name}\t#{extime.to_s(:db)}\n"
293
+
294
+ end
295
+
296
+ # -----------------------------------------------------------------------------
297
+
298
+ HELP_LIST = %w(list fetch register ready refresh delete devices connect save seed time exit quit gcp help)
299
+
300
+ def show_help()
301
+ ::Twiga.say_info "Twiga commands: " + HELP_LIST.join(', ')
302
+ end
303
+
304
+ # -----------------------------------------------------------------------------
305
+ # do_work -- mini command interpretor to assist debugging & development
306
+ # -----------------------------------------------------------------------------
307
+ def do_work()
308
+
309
+ show_help()
310
+ ::Twiga.say_info "\ntwiga> "
311
+
312
+ while (cmd = gets) do
313
+ cmd.strip!
314
+ tokens = cmd.split(/ /)
315
+ unless tokens.empty?
316
+
317
+ item = tokens[1] || 'test'
318
+
319
+ case tokens.first.downcase
320
+
321
+ when 'quit','exit' then break
322
+
323
+ when 'register' then do_register( item )
324
+ when 'refresh' then do_refresh( item )
325
+ when 'list' then do_list( item )
326
+ when 'fetch' then do_fetch( item )
327
+ when 'delete' then do_delete( item )
328
+
329
+ when 'save' then update_gcp_seed_tokens( item )
330
+ when 'seed' then show_seed()
331
+ when 'ready' then do_ready_state( item )
332
+ when 'time' then show_time( item )
333
+ when 'devices' then show_devices()
334
+
335
+ when 'connect' then do_connect( item )
336
+
337
+ when 'help' then show_help()
338
+ when 'cups' then do_cups_work()
339
+ when 'gcp' then show_gcp( item )
340
+
341
+ else
342
+ ::Twiga.say_err "? unknown command: #{cmd}"
343
+ end # case
344
+
345
+ end # unless no command
346
+
347
+ ::Twiga.say_info "\ntwiga> "
348
+ end
349
+
350
+ end
351
+
352
+ # -----------------------------------------------------------------------------
353
+
354
+ # #########################################################################
355
+ # ########## one-offs for do_cups commands ##############################
356
+ # #########################################################################
357
+
358
+ # -----------------------------------------------------------------------------
359
+
360
+ def show_printers()
361
+ list = Cups.show_destinations
362
+ puts list.inspect
363
+ end
364
+
365
+ def show_default()
366
+ printer = Cups.default_printer
367
+ puts printer.inspect
368
+ end
369
+
370
+ def show_jobs()
371
+ hash = Cups.all_jobs( Cups.default_printer )
372
+ last_job = hash.keys.sort.last
373
+ puts "#{last_job}: " + hash[last_job].inspect
374
+ end
375
+
376
+ def show_brief_options()
377
+ ::Twiga.say_warn snippet_brief_options(
378
+ Cups.options_for( Cups.default_printer )
379
+ )
380
+ end
381
+
382
+ def show_options()
383
+ show_brief_options()
384
+ puts snippet_full_options Cups.options_for( Cups.default_printer )
385
+ end
386
+
387
+ def state_to_s( state )
388
+ case ( state )
389
+ when '3' then 'idle'
390
+ when '4' then 'busy'
391
+ when '5' then 'stopped'
392
+ else
393
+ state
394
+ end # case
395
+ end
396
+
397
+ def snippet_full_options( state_hash )
398
+ <<RUBY20
399
+ name: #{ state_hash['printer-info'] }
400
+ make: #{ state_hash['printer-make-and-model'] }
401
+ ready: #{ state_hash['printer-is-accepting-jobs'] }
402
+ state: #{ state_to_s( state_hash['printer-state'] ) }
403
+ why: #{ state_hash['printer-state-reasons'] }
404
+ RUBY20
405
+ end
406
+
407
+ def snippet_brief_options( state_hash )
408
+
409
+ state_hash['printer-info'] + ': ' +
410
+ ( state_hash['printer-is-accepting-jobs'] ? '' : 'not ') +
411
+ 'ready - ' +
412
+ state_to_s( state_hash['printer-state'] ) +
413
+ ( state_hash['printer-state-reasons'] == 'none' ?
414
+ '' :
415
+ ': ' + state_hash['printer-state-reasons'] ) + "\n"
416
+
417
+ end
418
+
419
+ def print_testpage( printer=nil )
420
+ pj = setup_testpage( printer )
421
+ puts "pj initial state is: #{pj.state}"
422
+ pj.print
423
+ return pj
424
+ end
425
+
426
+ def setup_testpage( printer=nil )
427
+ return Cups::PrintJob.new( Kinokero.cups_testpage_file, printer)
428
+ end
429
+
430
+ def scan_state( printer=nil )
431
+ state = :queued
432
+ pj = print_testpage( printer )
433
+ while ( state == :processing || state == :held || state == :queued )
434
+ unless pj.state == state
435
+ state = pj.state
436
+ puts "state changed to: #{state}; #{pj.error_code} / #{pj.error_reason}"
437
+ end # state changed
438
+ sleep 0.200 # sleep for 200 ms
439
+ end # while
440
+
441
+ puts "final state: #{pj.state}"
442
+ puts "scan state finished: #{pj.completed?} / #{pj.failed?}"
443
+ puts "error info: #{pj.error_code} / #{pj.error_reason}"
444
+ end
445
+
446
+ def start_poll_thread()
447
+
448
+ poll_thread = Thread.new do
449
+
450
+ while true
451
+ show_brief_options()
452
+ sleep 2
453
+ end
454
+
455
+ end # polling thread
456
+
457
+ # force abort of everything if exception in thread
458
+ poll_thread.abort_on_exception = true
459
+
460
+ return poll_thread
461
+
462
+ end
463
+
464
+
465
+ # -----------------------------------------------------------------------------
466
+
467
+ CUPS_HELP_LIST = %w(printers default jobs options print scan exit quit help)
468
+
469
+ def show_cups_help()
470
+ ::Twiga.say_info "Twiga cups commands: " + CUPS_HELP_LIST.join(', ')
471
+ end
472
+
473
+
474
+ # -----------------------------------------------------------------------------
475
+ # -----------------------------------------------------------------------------
476
+
477
+ def do_cups_work()
478
+
479
+ # start_poll_thread()
480
+
481
+ show_help()
482
+ ::Twiga.say_info "\ncups>> "
483
+
484
+ while (cmd = gets) do
485
+ cmd.strip!
486
+ tokens = cmd.split(/ /)
487
+ unless tokens.empty?
488
+
489
+ item = tokens[1] || 'test'
490
+
491
+ case tokens.first.downcase
492
+
493
+ when 'quit','exit' then break
494
+
495
+ when 'printers' then show_printers()
496
+ when 'default' then show_default()
497
+ when 'jobs' then show_jobs()
498
+ when 'options' then show_options()
499
+
500
+ when 'print' then print_testpage()
501
+ when 'scan' then scan_state()
502
+
503
+ when 'help' then show_cups_help()
504
+
505
+ else
506
+ ::Twiga.say_err "? unknown command: #{cmd}"
507
+ end # case
508
+
509
+ end # unless no command
510
+
511
+ ::Twiga.say_info "\ncups>> "
512
+ end
513
+
514
+
515
+ end
516
+
517
+ # -----------------------------------------------------------------------------
518
+
519
+ # #########################################################################
520
+ # ########## command line options handling ##############################
521
+ # #########################################################################
522
+
523
+ # -----------------------------------------------------------------------------
524
+
525
+ def parse_options()
526
+
527
+ options = {}
528
+
529
+ ARGV.each_index do |index|
530
+ case $*[index]
531
+ when '-m' then options[:auto_connect] = false
532
+ when '-v' then options[:verbose] = true
533
+ when '-q' then options[:verbose] = false
534
+ when '-t' then options[:log_truncate] = true
535
+ when '-r' then options[:log_response] = false
536
+ else
537
+ ::Twiga.say_warn "unknown option: #{arg}"
538
+ end # case
539
+
540
+ $*.delete_at(index) # remove from command line
541
+
542
+ end # do each cmd line arg
543
+
544
+ return Kinokero::Cloudprint::DEFAULT_OPTIONS.merge(options)
545
+
546
+ end
547
+
548
+ # -----------------------------------------------------------------------------
549
+
550
+ # **************************************************************************** # ***** appliance initialization here *************************************
551
+ # ******** MAIN STARTING POINT HERE **************************************
552
+ # ****************************************************************************
553
+
554
+ ::Twiga.say_info "\nTwiga starting...\n"
555
+
556
+ # start up the GCP proxy
557
+ @proxy = Kinokero::Proxy.new( build_device_list(), parse_options )
558
+ @my_devices = @proxy.my_devices
559
+
560
+ unless $0 =~ /irb/ # are we in console mode?
561
+ # no, start appliance
562
+
563
+ # ****************************************************************************
564
+ # *** appliance primary work goes here ************************************
565
+ # ****************************************************************************
566
+
567
+ do_work() # primary twiga control area
568
+
569
+ # ****************************************************************************
570
+ # ********* app ends here ************************************************
571
+ # ****************************************************************************
572
+
573
+ ::Twiga.say_info "...ending\n\n"
574
+
575
+ exit 0
576
+
577
+ # -----------------------------------------------------------------------------
578
+ end # end unless console mode
579
+ # fall through to IRB