umlaut 3.0.0alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. data/LICENSE +7 -0
  2. data/README.md +49 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/images/error.gif +0 -0
  5. data/app/assets/images/export_bg_bot.gif +0 -0
  6. data/app/assets/images/export_bg_mid.gif +0 -0
  7. data/app/assets/images/export_bg_top.gif +0 -0
  8. data/app/assets/images/famfamfam/book_open.png +0 -0
  9. data/app/assets/images/famfamfam/cross.png +0 -0
  10. data/app/assets/images/famfamfam/page_sound.gif +0 -0
  11. data/app/assets/images/famfamfam/page_text.gif +0 -0
  12. data/app/assets/images/famfamfam/page_up.gif +0 -0
  13. data/app/assets/images/famfamfam/page_white.png +0 -0
  14. data/app/assets/images/famfamfam/readme.html +1495 -0
  15. data/app/assets/images/famfamfam/tiny_cross.png +0 -0
  16. data/app/assets/images/frame_remove.gif +0 -0
  17. data/app/assets/images/ico_go.gif +0 -0
  18. data/app/assets/images/jhu_findit.gif +0 -0
  19. data/app/assets/images/list_closed.png +0 -0
  20. data/app/assets/images/list_open.png +0 -0
  21. data/app/assets/images/more_info.gif +0 -0
  22. data/app/assets/images/rails.png +0 -0
  23. data/app/assets/images/request.gif +0 -0
  24. data/app/assets/images/spinner.gif +0 -0
  25. data/app/assets/javascripts/umlaut/ajax_windows.js +35 -0
  26. data/app/assets/javascripts/umlaut/ensure_window_size.js.erb +34 -0
  27. data/app/assets/javascripts/umlaut/expand_contract_toggle.js +25 -0
  28. data/app/assets/javascripts/umlaut/search_autocomplete.js +46 -0
  29. data/app/assets/javascripts/umlaut/simple_visible_toggle.js +8 -0
  30. data/app/assets/javascripts/umlaut/update_html.js +152 -0
  31. data/app/assets/javascripts/umlaut.js +17 -0
  32. data/app/assets/stylesheets/umlaut.css +857 -0
  33. data/app/controllers/application_controller.rb +14 -0
  34. data/app/controllers/export_email_controller.rb +123 -0
  35. data/app/controllers/js_helper_controller.rb +10 -0
  36. data/app/controllers/link_router_controller.rb +87 -0
  37. data/app/controllers/open_search_controller.rb +9 -0
  38. data/app/controllers/resolve_controller.rb +288 -0
  39. data/app/controllers/resource_controller.rb +83 -0
  40. data/app/controllers/search_controller.rb +328 -0
  41. data/app/controllers/search_methods/sfx3.rb +148 -0
  42. data/app/controllers/search_methods/sfx4.rb +257 -0
  43. data/app/controllers/search_methods/sfx_api.rb +47 -0
  44. data/app/controllers/store_controller.rb +64 -0
  45. data/app/controllers/umlaut/controller_behavior.rb +20 -0
  46. data/app/controllers/umlaut/controller_logic.rb +96 -0
  47. data/app/controllers/umlaut/error_handling.rb +48 -0
  48. data/app/controllers/umlaut_controller.rb +112 -0
  49. data/app/helpers/application_helper.rb +4 -0
  50. data/app/helpers/emailer_helper.rb +43 -0
  51. data/app/helpers/export_email_helper.rb +34 -0
  52. data/app/helpers/open_search_helper.rb +7 -0
  53. data/app/helpers/resolve_helper.rb +225 -0
  54. data/app/helpers/search_helper.rb +50 -0
  55. data/app/helpers/umlaut/footer_helper.rb +64 -0
  56. data/app/helpers/umlaut/helper.rb +62 -0
  57. data/app/helpers/umlaut/html_head_helper.rb +37 -0
  58. data/app/helpers/umlaut/url_generation.rb +77 -0
  59. data/app/mailers/emailer.rb +48 -0
  60. data/app/models/clickthrough.rb +2 -0
  61. data/app/models/collection.rb +259 -0
  62. data/app/models/crossref_lookup.rb +2 -0
  63. data/app/models/dispatched_service.rb +58 -0
  64. data/app/models/permalink.rb +29 -0
  65. data/app/models/referent.rb +473 -0
  66. data/app/models/referent_value.rb +14 -0
  67. data/app/models/request.rb +449 -0
  68. data/app/models/service_response.rb +179 -0
  69. data/app/models/service_store.rb +59 -0
  70. data/app/models/service_type_value.rb +58 -0
  71. data/app/models/service_wave.rb +150 -0
  72. data/app/models/sfx_db/az_additional_title.rb +11 -0
  73. data/app/models/sfx_db/az_letter_group.rb +11 -0
  74. data/app/models/sfx_db/az_title.rb +38 -0
  75. data/app/models/sfx_db/az_title_v2.rb +34 -0
  76. data/app/models/sfx_db/isbn.rb +12 -0
  77. data/app/models/sfx_db/issn.rb +12 -0
  78. data/app/models/sfx_db/object.rb +35 -0
  79. data/app/models/sfx_db/object_portfolio.rb +6 -0
  80. data/app/models/sfx_db/publisher.rb +10 -0
  81. data/app/models/sfx_db/sfx_db_base.rb +54 -0
  82. data/app/models/sfx_db/target.rb +9 -0
  83. data/app/models/sfx_db/target_service.rb +10 -0
  84. data/app/models/sfx_db/title.rb +10 -0
  85. data/app/models/sfx_db.rb +10 -0
  86. data/app/models/sfx_url.rb +35 -0
  87. data/app/views/emailer/citation.text.erb +28 -0
  88. data/app/views/emailer/short_citation.text.erb +8 -0
  89. data/app/views/export_email/_email.html.erb +25 -0
  90. data/app/views/export_email/_send_email.html.erb +3 -0
  91. data/app/views/export_email/_send_txt.html.erb +3 -0
  92. data/app/views/export_email/_txt.html.erb +62 -0
  93. data/app/views/export_email/email.html.erb +3 -0
  94. data/app/views/export_email/send_email.html.erb +1 -0
  95. data/app/views/export_email/send_txt.html.erb +1 -0
  96. data/app/views/export_email/txt.html.erb +3 -0
  97. data/app/views/js_helper/loader.erb.js +13 -0
  98. data/app/views/layouts/umlaut.html.erb +52 -0
  99. data/app/views/open_search/index.html.erb +9 -0
  100. data/app/views/resolve/_api_in_progress.xml.erb +21 -0
  101. data/app/views/resolve/_background_progress.html.erb +51 -0
  102. data/app/views/resolve/_background_updater.html.erb +38 -0
  103. data/app/views/resolve/_citation.html.erb +87 -0
  104. data/app/views/resolve/_coins.html.erb +1 -0
  105. data/app/views/resolve/_compact_citation.html.erb +33 -0
  106. data/app/views/resolve/_cover_image.html.erb +35 -0
  107. data/app/views/resolve/_fulltext.html.erb +55 -0
  108. data/app/views/resolve/_help.html.erb +17 -0
  109. data/app/views/resolve/_holding.html.erb +91 -0
  110. data/app/views/resolve/_related_items.html.erb +35 -0
  111. data/app/views/resolve/_search_inside.html.erb +62 -0
  112. data/app/views/resolve/_section_display.html.erb +49 -0
  113. data/app/views/resolve/_service_errors.html.erb +29 -0
  114. data/app/views/resolve/_standard_response_item.html.erb +89 -0
  115. data/app/views/resolve/api.xml.builder +72 -0
  116. data/app/views/resolve/background_status.html.erb +26 -0
  117. data/app/views/resolve/index.html.erb +73 -0
  118. data/app/views/resolve/partial_html_sections.xml.erb +30 -0
  119. data/app/views/search/_a_to_z.html.erb +6 -0
  120. data/app/views/search/_citation.html.erb +94 -0
  121. data/app/views/search/_pager.html.erb +60 -0
  122. data/app/views/search/books.html.erb +103 -0
  123. data/app/views/search/journal_search.html.erb +90 -0
  124. data/app/views/search/journals.html.erb +167 -0
  125. data/app/views/search/opensearch_description.rxml +10 -0
  126. data/app/views/testing/index.html.erb +1 -0
  127. data/app/views/umlaut/README +5 -0
  128. data/app/views/umlaut/error.html.erb +45 -0
  129. data/db/migrate/01_umlaut_init.rb +113 -0
  130. data/db/orig_fixed_data/service_type_values.yml +120 -0
  131. data/db/seeds.rb +7 -0
  132. data/lib/CronTab.rb +192 -0
  133. data/lib/aws_product_sign.rb +146 -0
  134. data/lib/exlibris/aleph/patron.rb +64 -0
  135. data/lib/exlibris/aleph/record.rb +54 -0
  136. data/lib/exlibris/aleph/rest_api.rb +29 -0
  137. data/lib/exlibris/primo/holding.rb +192 -0
  138. data/lib/exlibris/primo/rsrc.rb +17 -0
  139. data/lib/exlibris/primo/searcher.rb +276 -0
  140. data/lib/exlibris/primo/source/aleph.rb +46 -0
  141. data/lib/exlibris/primo/source/distribution/nyu_aleph.rb +323 -0
  142. data/lib/exlibris/primo/toc.rb +17 -0
  143. data/lib/exlibris/primo_ws.rb +140 -0
  144. data/lib/generators/templates/umlaut_services.yml +237 -0
  145. data/lib/generators/umlaut/asset_hooks_generator.rb +44 -0
  146. data/lib/generators/umlaut/install_generator.rb +110 -0
  147. data/lib/hip3/bib.rb +291 -0
  148. data/lib/hip3/bib_searcher.rb +302 -0
  149. data/lib/hip3/custom_field_lookup.rb +44 -0
  150. data/lib/hip3/holding.rb +50 -0
  151. data/lib/hip3/item.rb +65 -0
  152. data/lib/hip3/receipt.rb +7 -0
  153. data/lib/hip3/serial_copy.rb +82 -0
  154. data/lib/holding.rb +32 -0
  155. data/lib/marc_helper.rb +254 -0
  156. data/lib/metadata_helper.rb +312 -0
  157. data/lib/opensearch_feed.rb +398 -0
  158. data/lib/opensearch_query.rb +98 -0
  159. data/lib/referent_filter.rb +16 -0
  160. data/lib/referent_filters/dissertation_catch.rb +45 -0
  161. data/lib/section_renderer.rb +503 -0
  162. data/lib/service.rb +336 -0
  163. data/lib/service_adaptors/ajax_export.rb +37 -0
  164. data/lib/service_adaptors/amazon.rb +412 -0
  165. data/lib/service_adaptors/blacklight.rb +327 -0
  166. data/lib/service_adaptors/book_finder.rb +40 -0
  167. data/lib/service_adaptors/bx.rb +51 -0
  168. data/lib/service_adaptors/cover_thing.rb +73 -0
  169. data/lib/service_adaptors/elsevier_cover.rb +57 -0
  170. data/lib/service_adaptors/email_export.rb +10 -0
  171. data/lib/service_adaptors/ezproxy.rb +171 -0
  172. data/lib/service_adaptors/google_book_search.rb +442 -0
  173. data/lib/service_adaptors/gpo.rb +124 -0
  174. data/lib/service_adaptors/hathi_trust.rb +308 -0
  175. data/lib/service_adaptors/hip3_service.rb +150 -0
  176. data/lib/service_adaptors/hip_holding_search.rb +237 -0
  177. data/lib/service_adaptors/internet_archive.rb +488 -0
  178. data/lib/service_adaptors/isbn_db.rb +86 -0
  179. data/lib/service_adaptors/isi.rb +258 -0
  180. data/lib/service_adaptors/jcr.rb +146 -0
  181. data/lib/service_adaptors/opac.rb +351 -0
  182. data/lib/service_adaptors/open_library.rb +316 -0
  183. data/lib/service_adaptors/open_library_cover.rb +73 -0
  184. data/lib/service_adaptors/primo_service.rb +392 -0
  185. data/lib/service_adaptors/primo_source.rb +78 -0
  186. data/lib/service_adaptors/pubmed.rb +133 -0
  187. data/lib/service_adaptors/request_to_fixture.rb +68 -0
  188. data/lib/service_adaptors/scopus.rb +295 -0
  189. data/lib/service_adaptors/sfx-new.rb +557 -0
  190. data/lib/service_adaptors/sfx.rb +566 -0
  191. data/lib/service_adaptors/sfx_backchannel_record.rb +69 -0
  192. data/lib/service_adaptors/txt_holding_export.rb +32 -0
  193. data/lib/service_adaptors/ulrichs_cover.rb +57 -0
  194. data/lib/service_adaptors/ulrichs_link.rb +47 -0
  195. data/lib/service_adaptors/worldcat.rb +116 -0
  196. data/lib/service_adaptors/worldcat_identities.rb +591 -0
  197. data/lib/tasks/umlaut.rake +134 -0
  198. data/lib/umlaut/default_configuration.rb +5 -0
  199. data/lib/umlaut/routes.rb +136 -0
  200. data/lib/umlaut/version.rb +3 -0
  201. data/lib/umlaut.rb +37 -0
  202. data/lib/umlaut_configurable.rb +343 -0
  203. data/lib/umlaut_http.rb +100 -0
  204. data/lib/xml_schema_helper.rb +109 -0
  205. data/test/dummy/Rakefile +7 -0
  206. data/test/dummy/app/assets/javascripts/application.js +13 -0
  207. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  208. data/test/dummy/app/controllers/application_controller.rb +3 -0
  209. data/test/dummy/app/controllers/umlaut_controller.rb +112 -0
  210. data/test/dummy/app/helpers/application_helper.rb +2 -0
  211. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  212. data/test/dummy/config/application.rb +45 -0
  213. data/test/dummy/config/boot.rb +10 -0
  214. data/test/dummy/config/database-jhu.yml +44 -0
  215. data/test/dummy/config/database.yml +25 -0
  216. data/test/dummy/config/environment.rb +5 -0
  217. data/test/dummy/config/environments/development.rb +34 -0
  218. data/test/dummy/config/environments/production.rb +60 -0
  219. data/test/dummy/config/environments/test.rb +39 -0
  220. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  221. data/test/dummy/config/initializers/inflections.rb +10 -0
  222. data/test/dummy/config/initializers/mime_types.rb +5 -0
  223. data/test/dummy/config/initializers/secret_token.rb +7 -0
  224. data/test/dummy/config/initializers/session_store.rb +8 -0
  225. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  226. data/test/dummy/config/locales/en.yml +5 -0
  227. data/test/dummy/config/routes.rb +61 -0
  228. data/test/dummy/config/umlaut_services.yml +237 -0
  229. data/test/dummy/config.ru +4 -0
  230. data/test/dummy/db/migrate/20111228211210_umlaut_init.rb +113 -0
  231. data/test/dummy/db/schema.rb +124 -0
  232. data/test/dummy/log/development.log +12981 -0
  233. data/test/dummy/log/production.log +0 -0
  234. data/test/dummy/public/404.html +26 -0
  235. data/test/dummy/public/422.html +26 -0
  236. data/test/dummy/public/500.html +26 -0
  237. data/test/dummy/public/favicon.ico +0 -0
  238. data/test/dummy/script/rails +6 -0
  239. data/test/dummy/tmp/cache/assets/C5F/340/sprockets%2F99692920160b7a279b86a80415b79db7 +0 -0
  240. data/test/dummy/tmp/cache/assets/C70/4D0/sprockets%2F034ad2036e623081bd352800786dfe80 +0 -0
  241. data/test/dummy/tmp/cache/assets/C73/920/sprockets%2Fd371318f22900492fd180f17c5e2a504 +9268 -0
  242. data/test/dummy/tmp/cache/assets/C80/980/sprockets%2Fc94807409c1523d43e18d25f35d93c41 +0 -0
  243. data/test/dummy/tmp/cache/assets/C8F/780/sprockets%2Fe47e28558116fb5f8038754e60d1961d +11769 -0
  244. data/test/dummy/tmp/cache/assets/CAA/EB0/sprockets%2F1d179210e8b76f1ea63c802688a015e4 +9271 -0
  245. data/test/dummy/tmp/cache/assets/CBB/9C0/sprockets%2F706f28923fb754cad04b9107c89986a1 +0 -0
  246. data/test/dummy/tmp/cache/assets/CBF/B60/sprockets%2F08ca89671549936265dcb673bf02e36f +0 -0
  247. data/test/dummy/tmp/cache/assets/CC9/9F0/sprockets%2F306166316e2cafd13c15e62b51a2339d +0 -0
  248. data/test/dummy/tmp/cache/assets/CF6/F20/sprockets%2F5b2ffa1103079dfd555197838f87a99f +0 -0
  249. data/test/dummy/tmp/cache/assets/CF7/2B0/sprockets%2F25a7c73655bd3598173b39d9f98bcd46 +862 -0
  250. data/test/dummy/tmp/cache/assets/CFE/080/sprockets%2F37fe9f4255baddbd549a659914929398 +0 -0
  251. data/test/dummy/tmp/cache/assets/D22/060/sprockets%2F9aec77b768e91a802d284271c58e2f7e +21357 -0
  252. data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  253. data/test/dummy/tmp/cache/assets/D33/6D0/sprockets%2F500129c57f1146e556ec3aacd6cd38c1 +0 -0
  254. data/test/dummy/tmp/cache/assets/D33/FD0/sprockets%2F2ba0b4e6334a77b923e5f770381bb2bf +0 -0
  255. data/test/dummy/tmp/cache/assets/D42/C20/sprockets%2Fbcf14e437b1582bf93b77670acf8e090 +21353 -0
  256. data/test/dummy/tmp/cache/assets/D50/A30/sprockets%2F7d8b294ac433db5d056538f8cf7c66b9 +0 -0
  257. data/test/dummy/tmp/cache/assets/D54/ED0/sprockets%2F71c9fa01091d432b131da3bb73faf3d4 +872 -0
  258. data/test/dummy/tmp/cache/assets/D65/590/sprockets%2Fc1bb92fc3406a126b7dd302edc96d629 +0 -0
  259. data/test/dummy/tmp/cache/assets/D71/6B0/sprockets%2Fde558b71b494cf09b1bf055c8dff0353 +0 -0
  260. data/test/dummy/tmp/cache/assets/D72/610/sprockets%2Fa8c708eeb30ef93de34d755d4f45d023 +859 -0
  261. data/test/dummy/tmp/cache/assets/D76/AD0/sprockets%2Fe2158cde93188cf5ab6457bc6d6602ec +0 -0
  262. data/test/dummy/tmp/cache/assets/D7A/E40/sprockets%2F9622ffcc499a57627cd1bb18fe31b8e4 +11772 -0
  263. data/test/dummy/tmp/cache/assets/D84/210/sprockets%2Fabd0103ccec2b428ac62c94e4c40b384 +0 -0
  264. data/test/dummy/tmp/cache/assets/D9B/770/sprockets%2F8aacf02eb7dbb0949704b28f27b87e0b +0 -0
  265. data/test/dummy/tmp/cache/assets/DA6/A80/sprockets%2F92e26d8e58d5bcc8b8f6c25d1b05b9c1 +0 -0
  266. data/test/dummy/tmp/cache/assets/DE8/790/sprockets%2Fd1333bde2b9aafcc712d11dd09ab35d8 +0 -0
  267. data/test/dummy/tmp/cache/assets/DF7/F30/sprockets%2F7bc16c4109b17fabe29f8ddbbf732d1c +374 -0
  268. data/test/dummy/tmp/cache/assets/E03/570/sprockets%2F493bdc0ac14cd4f57fdfe4253f992bde +0 -0
  269. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  270. data/test/dummy/tmp/cache/assets/E0B/4B0/sprockets%2F7988df51a61c81ce6ede4a2d4c8cce4f +377 -0
  271. data/test/dummy/tmp/cache/assets/E5F/960/sprockets%2Fdc007b6cad5c7ef08e33ec28cfff0ef6 +0 -0
  272. data/test/fixtures/dispatched_services.yml +5 -0
  273. data/test/fixtures/permalinks.yml +5 -0
  274. data/test/fixtures/referent_values.yml +1734 -0
  275. data/test/fixtures/referents.yml +156 -0
  276. data/test/fixtures/requests.yml +284 -0
  277. data/test/fixtures/service_responses.yml +5 -0
  278. data/test/fixtures/sfx_urls.yml +4 -0
  279. data/test/performance/browsing_test.rb +9 -0
  280. data/test/test_helper.rb +10 -0
  281. data/test/umlaut_test.rb +7 -0
  282. data/test/unit/aleph_patron_test.rb +39 -0
  283. data/test/unit/aleph_record_benchmarks.rb +28 -0
  284. data/test/unit/aleph_record_test.rb +30 -0
  285. data/test/unit/aws_product_sign_test.rb +93 -0
  286. data/test/unit/collection_test.rb +76 -0
  287. data/test/unit/google_book_search_test.rb +101 -0
  288. data/test/unit/primo_searcher_test.rb +403 -0
  289. data/test/unit/primo_service_test.rb +939 -0
  290. data/test/unit/primo_ws_test.rb +131 -0
  291. data/test/unit/service_response_test.rb +9 -0
  292. data/test/unit/service_test.rb +33 -0
  293. metadata +580 -0
data/db/seeds.rb ADDED
@@ -0,0 +1,7 @@
1
+ # This file should contain all the record creation needed to seed the database with its default values.
2
+ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3
+ #
4
+ # Examples:
5
+ #
6
+ # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
7
+ # Mayor.create(:name => 'Daley', :city => cities.first)
data/lib/CronTab.rb ADDED
@@ -0,0 +1,192 @@
1
+ # Extracted from dpklib, Ruby library released under same license as Ruby.
2
+
3
+
4
+ class CronTab
5
+ attr_accessor :min, :hour, :mday, :mon, :wday, :command
6
+
7
+ WDAY = %w(sun mon tue wed thu fri sat)
8
+ FormatError = Class.new(StandardError)
9
+
10
+ def initialize(str)
11
+ super()
12
+
13
+ self.min, self.hour, self.mday, self.mon, self.wday =
14
+ CronTab.parse_timedate(str)
15
+
16
+ self.command = str.scan( /(?:\S+\s+){5}(.*)/ ).shift
17
+ end
18
+
19
+ def ===(rhs)
20
+ judge_date = proc {
21
+ b = true
22
+ b = b && (mday === rhs.mday)
23
+ b = b && (mon === rhs.mon)
24
+ b = b && (wday === rhs.wday)
25
+ }
26
+ judge_hour = proc {
27
+ b = true
28
+ b = b && (min === rhs.min)
29
+ b = b && (hour === rhs.hour)
30
+ }
31
+
32
+ case rhs
33
+ when Time
34
+ judge_hour.call && judge_date.call
35
+ when Dpklib::Hour
36
+ judge_hour.call
37
+ when Date
38
+ judge_date.call
39
+ else
40
+ super
41
+ end
42
+ end
43
+ alias include? ===;
44
+
45
+ class NextSeeker
46
+ attr_accessor :scalar, :field, :lower_seeker
47
+
48
+ def initialize(s, f, l)
49
+ self.scalar = s
50
+ self.field = f
51
+ self.lower_seeker = l
52
+ end
53
+
54
+ def succ
55
+ if lower_seeker.nil? || lower_seeker.succ then
56
+ self.scalar = field.nextof(scalar)
57
+ scalar
58
+ else
59
+ lower_seeker.recursive_zero
60
+ self.scalar += 1
61
+ succ
62
+ end
63
+ end
64
+
65
+ def recursive_zero
66
+ self.scalar = 0
67
+ lower_seeker && lower_seeker.recursive_zero
68
+ end
69
+ end #/NextSeeker
70
+
71
+ class YearField
72
+ def nextof(nowyear)
73
+ nowyear
74
+ end
75
+ end #/YearField
76
+
77
+ def nexttime(nowtime = Time.now)
78
+ nowmin = nowtime.min + 1
79
+
80
+ seeker_min = NextSeeker.new(nowmin, min, nil)
81
+ seeker_hour = NextSeeker.new(nowtime.hour, hour, seeker_min)
82
+ seeker_mday = NextSeeker.new(nowtime.mday, mday, seeker_hour)
83
+ seeker_mon = NextSeeker.new(nowtime.mon, mon, seeker_mday)
84
+ seeker_year = NextSeeker.new(nowtime.year, YearField.new, seeker_mon)
85
+ seeker_year.succ
86
+
87
+ Time.local(seeker_year.scalar,
88
+ seeker_mon.scalar,
89
+ seeker_mday.scalar,
90
+ seeker_hour.scalar,
91
+ seeker_min.scalar, 0)
92
+ end
93
+
94
+ def waitsec(nowtime = Time.now)
95
+ nexttime(nowtime).to_i - nowtime.to_i
96
+ end
97
+
98
+ def self.parse_timedate(str)
99
+ minute, hour, day_of_month, month, day_of_week =
100
+ str.scan(/^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/).shift
101
+
102
+ day_of_week = day_of_week.downcase.gsub(/#{WDAY.join("|")}/){
103
+ WDAY.index($&)
104
+ }
105
+
106
+ [
107
+ parse_field(minute, 0, 59),
108
+ parse_field(hour, 0, 23),
109
+ parse_field(day_of_month, 1, 31),
110
+ parse_field(month, 1, 12),
111
+ parse_field(day_of_week, 0, 6),
112
+ ]
113
+ end
114
+
115
+ class Field
116
+ attr_accessor :range, :every
117
+
118
+ def initialize(r, e)
119
+ self.range = r
120
+ self.every = e
121
+ end
122
+
123
+ def ===(rhs)
124
+ b = true
125
+ b = b && ( (rhs - range.first) % every == 0 )
126
+ b = b && ( range === rhs )
127
+ end
128
+
129
+ def nextof(now)
130
+ if now < range.first then
131
+ nextof(range.first)
132
+ elsif range.last < now || (range.exclude_end? && range.last <= now) then
133
+ nil
134
+ else
135
+ now + ( (now - range.first) % every )
136
+ end
137
+ end
138
+ end
139
+
140
+ class FieldSet
141
+ attr_accessor :fields
142
+
143
+ def initialize(f)
144
+ self.fields = f
145
+ end
146
+
147
+ def ===(rhs)
148
+ b = false
149
+ fields.each { |field|
150
+ b ||= (field === rhs)
151
+ }
152
+ b
153
+ end
154
+
155
+ def nextof(now)
156
+ ret = nil
157
+ fields.each { |field|
158
+ field_nextof = field.nextof(now)
159
+ ret = field_nextof if ret.nil? || (field_nextof && field_nextof < ret)
160
+ }
161
+ ret
162
+ end
163
+ end
164
+
165
+ def self.parse_field(str, first, last)
166
+ list = str.split(",")
167
+ list.map!{|r|
168
+ r, every = r.split("/")
169
+ every = every ? every.to_i : 1
170
+ f,l = r.split("-")
171
+ range = if f == "*"
172
+ first..last
173
+ elsif l.nil?
174
+ f.to_i .. f.to_i
175
+ elsif f.to_i < first
176
+ raise FormatError.new("out of range (#{f} for #{first})")
177
+ elsif last < l.to_i
178
+ raise FormatError.new("out of range (#{l} for #{last})")
179
+ else
180
+ f.to_i .. l.to_i
181
+ end
182
+ Field.new(range, every)
183
+ }
184
+ FieldSet.new(list)
185
+ end
186
+
187
+ #alias parse new
188
+ #alias [] new
189
+
190
+ end #/CronTab
191
+
192
+
@@ -0,0 +1,146 @@
1
+ require 'rubygems'
2
+ require 'cgi'
3
+ require 'time'
4
+ require 'openssl'
5
+ require 'base64'
6
+
7
+ # Code to sign a request to Amazon Product Advertising API (formerly known as
8
+ # the AWS ECommerce Service), as per the specs at:
9
+ # http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?RequestAuthenticationArticle.html
10
+ #
11
+ # This code based heavily on that at: http://chrisroos.co.uk/blog/2009-01-31-implementing-version-2-of-the-amazon-aws-http-request-signature-in-ruby
12
+ # Thanks Chris!
13
+ #
14
+ # Example:
15
+ #
16
+ # aws_signer = AwsProductSign(:access_key => "00000000000000000000", :secret_key => "1234567890")
17
+ # params = {
18
+ # "Service" => "AWSECommerceService",
19
+ # "Operation"=>"ItemLookup",
20
+ # "ItemId"=>"0679722769",
21
+ # "ResponseGroup"=>"ItemAttributes,Offers,Images,Reviews"
22
+ # }
23
+ # query_string = aws_signer.query_with_signature( params )
24
+ #
25
+ # params will have a Timestamp AWSAccessKeyId added to it, unless input
26
+ # already had it.
27
+ #
28
+ # Or you can get back a params hash instead of an encoded query string.
29
+ # Beware that the Signature parameter must be URL-encoded precisely, and
30
+ # not over-encoded: "the final signature you send in the request must be URL
31
+ # encoded as specified in RFC 3986
32
+
33
+ # Then you can go on to use those new params in rails url_for or the URI
34
+ # builder of your choice. Values are not URI-escaped yet. Or mutate the
35
+ # params passsed in with #add_signature! instead.
36
+ #
37
+ # Returning a new params hash, leaving your input untouched:
38
+ #
39
+ # query_string_component = aws_signer.add_signature( params )
40
+ #
41
+ # Or mutate your input:
42
+ # aws_signer.add_signature!(params)
43
+ #
44
+ #
45
+ # At the moment this class can't handle a query string where you need the same
46
+ # key twice. I don't think the AWS service ever uses that though?
47
+ #
48
+ # This class also assumes a GET request.
49
+ class AwsProductSign
50
+
51
+ def initialize(options = {})
52
+ @secret_key = options[:secret_key]
53
+ raise Exception.new("You must supply a :secret_key") unless @secret_key
54
+ @access_key = options[:access_key]
55
+ end
56
+
57
+ def query_with_signature(hash)
58
+ return hash_to_query( add_signature(hash) )
59
+ end
60
+
61
+ # Pass in a hash representing params for a query string.
62
+ # param keys should be strings, not symbols please.
63
+ # Will return a param with the "Signature" key/value added, without
64
+ # modifying original.
65
+ def add_signature(params)
66
+ # Make a copy to not modify original
67
+ add_signature!( Hash[params] )
68
+ end
69
+
70
+ # Like #add_signature, but will mutate the hash passed in,
71
+ # adding a "Signature" key/value to hash passed in, and return
72
+ # hash too.
73
+ def add_signature!(params)
74
+
75
+ # supply timestamp and access key if not already provided
76
+ params["Timestamp"] ||= Time.now.iso8601
77
+ params["AWSAccessKeyId"] ||= access_key
78
+ # Existing "Signature"? That's gotta go before we generate a new
79
+ # signature and add it.
80
+ params.delete("Signature")
81
+
82
+ query_string = canonical_querystring(params)
83
+
84
+ string_to_sign = string_to_sign(query_string)
85
+
86
+ # chomp is important! the base64 encoded version will have a newline at the end
87
+ # which amazon does not want.
88
+ digest = OpenSSL::Digest::Digest.new('sha256')
89
+ signature = Base64.encode64(OpenSSL::HMAC.digest(digest, secret_key, string_to_sign)).chomp
90
+
91
+ params["Signature"] = signature
92
+
93
+ #order doesn't matter for the actual request, we return the hash
94
+ #and let client turn it into a url.
95
+ return params
96
+ end
97
+
98
+ # Insist on specific method of URL encoding, RFC3986.
99
+ def url_encode(string)
100
+ # It's kinda like CGI.escape, except CGI.escape is encoding a tilde when
101
+ # it ought not to be, so we turn it back. Also space NEEDS to be %20 not +.
102
+ return CGI.escape(string).gsub("%7E", "~").gsub("+", "%20")
103
+ end
104
+
105
+ # param keys should be strings, not symbols please. return a string joined
106
+ # by & in canonical order.
107
+ def canonical_querystring(params)
108
+ # I hope this built-in sort sorts by byte order, that's what's required.
109
+ values = params.keys.sort.collect {|key| [url_encode(key), url_encode(params[key].to_s)].join("=") }
110
+
111
+ return values.join("&")
112
+ end
113
+
114
+ def string_to_sign(query_string, options = {})
115
+ options[:verb] = "GET"
116
+ options[:request_uri] = "/onca/xml"
117
+ options[:host] = "webservices.amazon.com"
118
+
119
+
120
+ return options[:verb] + "\n" +
121
+ options[:host].downcase + "\n" +
122
+ options[:request_uri] + "\n" +
123
+ query_string
124
+ end
125
+
126
+ # Turns a hash into a query string, returns the query string.
127
+ # url-encodes everything to Amazon's specifications.
128
+ def hash_to_query(hash)
129
+ hash.collect do |key, value|
130
+
131
+ url_encode(key) + "=" + url_encode(value)
132
+
133
+ end.join("&")
134
+ end
135
+
136
+ def secret_key
137
+ return @secret_key
138
+ end
139
+ def access_key
140
+ return @access_key
141
+ end
142
+ def access_key=(a)
143
+ @access_key = a
144
+ end
145
+
146
+ end
@@ -0,0 +1,64 @@
1
+ module Exlibris::Aleph
2
+ class Patron < RestAPI
3
+ attr_accessor :patron_id
4
+
5
+ def initialize(patron_id, uri)
6
+ @patron_id = patron_id
7
+ raise "Initialization error in #{self.class}. Missing patron id." if @patron_id.nil?
8
+ super(uri)
9
+ @uri = @uri+ "/patron/#{patron_id}"
10
+ end
11
+
12
+ # Place a hold on the specificed item.
13
+ # Raises errors if
14
+ def place_hold(adm_library, bib_library, bib_id, item_id, params)
15
+ pickup_location = params[:pickup_location]
16
+ raise "Error in place hold. Missing pickup location." if pickup_location.nil?
17
+ last_interest_date = params.fetch(:last_interest_date, "")
18
+ start_interest_date = params.fetch(:start_interest_date, "")
19
+ sub_author = params.fetch(:sub_author, "")
20
+ sub_title = params.fetch(:sub_title, "")
21
+ pages = params.fetch(:pages, "")
22
+ note_1 = params.fetch(:note_1, "")
23
+ note_2 = params.fetch(:note_2, "")
24
+ rush = params.fetch(:rush, "N")
25
+ body_str = "<hold-request-parameters>"
26
+ body_str << "<pickup-location>#{pickup_location}</pickup-location>"
27
+ body_str << "<last-interest-date>#{last_interest_date}</last-interest-date>"
28
+ body_str << "<start-interest-date>#{start_interest_date}</start-interest-date>"
29
+ body_str << "<sub-author>#{sub_author}</sub-author>"
30
+ body_str << "<sub-title>#{sub_title}</sub-title>"
31
+ body_str << "<pages>#{pages}</pages>"
32
+ body_str << "<note-1>#{note_1}</note-1>"
33
+ body_str << "<note-2>#{note_2}</note-2>"
34
+ body_str << "<rush>#{rush}</rush>"
35
+ body_str << "</hold-request-parameters>"
36
+ options = { :body => "post_xml=#{body_str}"}
37
+ @response = self.class.put(@uri+ "/record/#{bib_library}#{bib_id}/items/#{item_id}/hold", options)
38
+ raise "Error placing hold through Aleph REST APIs. #{error}" unless error.nil?
39
+ return @response
40
+ end
41
+
42
+ def loans()
43
+ @response = self.class.get(@uri+ "/circulationActions/loans?view=full")
44
+ raise "Error getting loans through Aleph REST APIs. #{error}" unless error.nil?
45
+ return @response
46
+ end
47
+
48
+ def renew_loans(item_id="")
49
+ # Will renew all if specific item not specified
50
+ @response = self.class.post(@uri+ "/circulationActions/loans/#{item_id}")
51
+ raise "Error renewing loan(s) through Aleph REST APIs. #{error}" unless error.nil?
52
+ return @response
53
+ end
54
+
55
+ def note
56
+ return (not @response.first.last.kind_of?(Hash) or @response.first.last["create_hold"].nil?) ? "" : ": #{@response.first.last["create_hold"]["note"]}" if @response.instance_of?(Hash)
57
+ end
58
+
59
+ def error
60
+ return nil if reply_code == "0000"
61
+ return "#{reply_text}#{note}"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,54 @@
1
+ module Exlibris::Aleph
2
+ class Record < RestAPI
3
+ def initialize(bib_library, record_id, uri)
4
+ @record_id = record_id
5
+ raise "Initialization error in #{self.class}. Missing record id." if @record_id.nil?
6
+ @bib_library = bib_library
7
+ raise "Initialization error in #{self.class}. Missing bib library." if @bib_library.nil?
8
+ super(uri)
9
+ @uri = @uri+ "/record/#{bib_library}#{record_id}"
10
+ # Format :xml parses response as a hash.
11
+ # Eventually I'd like this to be the default since it raises exceptions for invalid XML.
12
+ # self.class.format :xml
13
+ # Format :html does no parsing, just passes back raw XML for parsing by client
14
+ self.class.format :html
15
+ end
16
+
17
+ # Returns an XML string representation of a bib.
18
+ # Every method call refreshes the data from the underlying API.
19
+ # Raises and exception if there are errors.
20
+ def bib
21
+ @response = self.class.get(@uri+ "?view=full")
22
+ raise "Error getting bib from Aleph REST APIs. #{error}" unless error.nil?
23
+ return @response
24
+ end
25
+
26
+ # Returns an array of items. Each item is represented as an HTTParty hash.
27
+ # Every method call refreshes the data from the underlying API.
28
+ # Raises an exception if the response is not valid XML or there are errors.
29
+ def items
30
+ @items = []
31
+ self.class.format :xml
32
+ # Since we're parsing xml, this will raise an error
33
+ # if the response isn't xml.
34
+ @response = self.class.get(@uri+ "/items?view=full")
35
+ self.class.format :html
36
+ raise "Error getting items from Aleph REST APIs. #{error}" if not error.nil? or
37
+ @response.nil? or @response["get_item_list"].nil? or @response["get_item_list"]["items"].nil?
38
+ item_list = @response["get_item_list"]["items"]["item"]
39
+ @items.push(item_list) if item_list.instance_of?(Hash)
40
+ item_list.each {|item|@items.push(item)} if item_list.instance_of?(Array)
41
+ Rails.logger.warn("No items returned from Aleph in #{self.class}.") if @items.empty?
42
+ return @items
43
+ end
44
+
45
+ # Returns an XML string representation of holdings
46
+ # Every method call refreshes the data from the underlying API.
47
+ # Raises and exception if there are errors.
48
+ def holdings
49
+ @response = self.class.get(@uri+ "/holdings?view=full")
50
+ raise "Error getting holdings from Aleph REST APIs. #{error}" unless error.nil?
51
+ return @response
52
+ end
53
+ end
54
+ end