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.
- data/LICENSE +7 -0
- data/README.md +49 -0
- data/Rakefile +37 -0
- data/app/assets/images/error.gif +0 -0
- data/app/assets/images/export_bg_bot.gif +0 -0
- data/app/assets/images/export_bg_mid.gif +0 -0
- data/app/assets/images/export_bg_top.gif +0 -0
- data/app/assets/images/famfamfam/book_open.png +0 -0
- data/app/assets/images/famfamfam/cross.png +0 -0
- data/app/assets/images/famfamfam/page_sound.gif +0 -0
- data/app/assets/images/famfamfam/page_text.gif +0 -0
- data/app/assets/images/famfamfam/page_up.gif +0 -0
- data/app/assets/images/famfamfam/page_white.png +0 -0
- data/app/assets/images/famfamfam/readme.html +1495 -0
- data/app/assets/images/famfamfam/tiny_cross.png +0 -0
- data/app/assets/images/frame_remove.gif +0 -0
- data/app/assets/images/ico_go.gif +0 -0
- data/app/assets/images/jhu_findit.gif +0 -0
- data/app/assets/images/list_closed.png +0 -0
- data/app/assets/images/list_open.png +0 -0
- data/app/assets/images/more_info.gif +0 -0
- data/app/assets/images/rails.png +0 -0
- data/app/assets/images/request.gif +0 -0
- data/app/assets/images/spinner.gif +0 -0
- data/app/assets/javascripts/umlaut/ajax_windows.js +35 -0
- data/app/assets/javascripts/umlaut/ensure_window_size.js.erb +34 -0
- data/app/assets/javascripts/umlaut/expand_contract_toggle.js +25 -0
- data/app/assets/javascripts/umlaut/search_autocomplete.js +46 -0
- data/app/assets/javascripts/umlaut/simple_visible_toggle.js +8 -0
- data/app/assets/javascripts/umlaut/update_html.js +152 -0
- data/app/assets/javascripts/umlaut.js +17 -0
- data/app/assets/stylesheets/umlaut.css +857 -0
- data/app/controllers/application_controller.rb +14 -0
- data/app/controllers/export_email_controller.rb +123 -0
- data/app/controllers/js_helper_controller.rb +10 -0
- data/app/controllers/link_router_controller.rb +87 -0
- data/app/controllers/open_search_controller.rb +9 -0
- data/app/controllers/resolve_controller.rb +288 -0
- data/app/controllers/resource_controller.rb +83 -0
- data/app/controllers/search_controller.rb +328 -0
- data/app/controllers/search_methods/sfx3.rb +148 -0
- data/app/controllers/search_methods/sfx4.rb +257 -0
- data/app/controllers/search_methods/sfx_api.rb +47 -0
- data/app/controllers/store_controller.rb +64 -0
- data/app/controllers/umlaut/controller_behavior.rb +20 -0
- data/app/controllers/umlaut/controller_logic.rb +96 -0
- data/app/controllers/umlaut/error_handling.rb +48 -0
- data/app/controllers/umlaut_controller.rb +112 -0
- data/app/helpers/application_helper.rb +4 -0
- data/app/helpers/emailer_helper.rb +43 -0
- data/app/helpers/export_email_helper.rb +34 -0
- data/app/helpers/open_search_helper.rb +7 -0
- data/app/helpers/resolve_helper.rb +225 -0
- data/app/helpers/search_helper.rb +50 -0
- data/app/helpers/umlaut/footer_helper.rb +64 -0
- data/app/helpers/umlaut/helper.rb +62 -0
- data/app/helpers/umlaut/html_head_helper.rb +37 -0
- data/app/helpers/umlaut/url_generation.rb +77 -0
- data/app/mailers/emailer.rb +48 -0
- data/app/models/clickthrough.rb +2 -0
- data/app/models/collection.rb +259 -0
- data/app/models/crossref_lookup.rb +2 -0
- data/app/models/dispatched_service.rb +58 -0
- data/app/models/permalink.rb +29 -0
- data/app/models/referent.rb +473 -0
- data/app/models/referent_value.rb +14 -0
- data/app/models/request.rb +449 -0
- data/app/models/service_response.rb +179 -0
- data/app/models/service_store.rb +59 -0
- data/app/models/service_type_value.rb +58 -0
- data/app/models/service_wave.rb +150 -0
- data/app/models/sfx_db/az_additional_title.rb +11 -0
- data/app/models/sfx_db/az_letter_group.rb +11 -0
- data/app/models/sfx_db/az_title.rb +38 -0
- data/app/models/sfx_db/az_title_v2.rb +34 -0
- data/app/models/sfx_db/isbn.rb +12 -0
- data/app/models/sfx_db/issn.rb +12 -0
- data/app/models/sfx_db/object.rb +35 -0
- data/app/models/sfx_db/object_portfolio.rb +6 -0
- data/app/models/sfx_db/publisher.rb +10 -0
- data/app/models/sfx_db/sfx_db_base.rb +54 -0
- data/app/models/sfx_db/target.rb +9 -0
- data/app/models/sfx_db/target_service.rb +10 -0
- data/app/models/sfx_db/title.rb +10 -0
- data/app/models/sfx_db.rb +10 -0
- data/app/models/sfx_url.rb +35 -0
- data/app/views/emailer/citation.text.erb +28 -0
- data/app/views/emailer/short_citation.text.erb +8 -0
- data/app/views/export_email/_email.html.erb +25 -0
- data/app/views/export_email/_send_email.html.erb +3 -0
- data/app/views/export_email/_send_txt.html.erb +3 -0
- data/app/views/export_email/_txt.html.erb +62 -0
- data/app/views/export_email/email.html.erb +3 -0
- data/app/views/export_email/send_email.html.erb +1 -0
- data/app/views/export_email/send_txt.html.erb +1 -0
- data/app/views/export_email/txt.html.erb +3 -0
- data/app/views/js_helper/loader.erb.js +13 -0
- data/app/views/layouts/umlaut.html.erb +52 -0
- data/app/views/open_search/index.html.erb +9 -0
- data/app/views/resolve/_api_in_progress.xml.erb +21 -0
- data/app/views/resolve/_background_progress.html.erb +51 -0
- data/app/views/resolve/_background_updater.html.erb +38 -0
- data/app/views/resolve/_citation.html.erb +87 -0
- data/app/views/resolve/_coins.html.erb +1 -0
- data/app/views/resolve/_compact_citation.html.erb +33 -0
- data/app/views/resolve/_cover_image.html.erb +35 -0
- data/app/views/resolve/_fulltext.html.erb +55 -0
- data/app/views/resolve/_help.html.erb +17 -0
- data/app/views/resolve/_holding.html.erb +91 -0
- data/app/views/resolve/_related_items.html.erb +35 -0
- data/app/views/resolve/_search_inside.html.erb +62 -0
- data/app/views/resolve/_section_display.html.erb +49 -0
- data/app/views/resolve/_service_errors.html.erb +29 -0
- data/app/views/resolve/_standard_response_item.html.erb +89 -0
- data/app/views/resolve/api.xml.builder +72 -0
- data/app/views/resolve/background_status.html.erb +26 -0
- data/app/views/resolve/index.html.erb +73 -0
- data/app/views/resolve/partial_html_sections.xml.erb +30 -0
- data/app/views/search/_a_to_z.html.erb +6 -0
- data/app/views/search/_citation.html.erb +94 -0
- data/app/views/search/_pager.html.erb +60 -0
- data/app/views/search/books.html.erb +103 -0
- data/app/views/search/journal_search.html.erb +90 -0
- data/app/views/search/journals.html.erb +167 -0
- data/app/views/search/opensearch_description.rxml +10 -0
- data/app/views/testing/index.html.erb +1 -0
- data/app/views/umlaut/README +5 -0
- data/app/views/umlaut/error.html.erb +45 -0
- data/db/migrate/01_umlaut_init.rb +113 -0
- data/db/orig_fixed_data/service_type_values.yml +120 -0
- data/db/seeds.rb +7 -0
- data/lib/CronTab.rb +192 -0
- data/lib/aws_product_sign.rb +146 -0
- data/lib/exlibris/aleph/patron.rb +64 -0
- data/lib/exlibris/aleph/record.rb +54 -0
- data/lib/exlibris/aleph/rest_api.rb +29 -0
- data/lib/exlibris/primo/holding.rb +192 -0
- data/lib/exlibris/primo/rsrc.rb +17 -0
- data/lib/exlibris/primo/searcher.rb +276 -0
- data/lib/exlibris/primo/source/aleph.rb +46 -0
- data/lib/exlibris/primo/source/distribution/nyu_aleph.rb +323 -0
- data/lib/exlibris/primo/toc.rb +17 -0
- data/lib/exlibris/primo_ws.rb +140 -0
- data/lib/generators/templates/umlaut_services.yml +237 -0
- data/lib/generators/umlaut/asset_hooks_generator.rb +44 -0
- data/lib/generators/umlaut/install_generator.rb +110 -0
- data/lib/hip3/bib.rb +291 -0
- data/lib/hip3/bib_searcher.rb +302 -0
- data/lib/hip3/custom_field_lookup.rb +44 -0
- data/lib/hip3/holding.rb +50 -0
- data/lib/hip3/item.rb +65 -0
- data/lib/hip3/receipt.rb +7 -0
- data/lib/hip3/serial_copy.rb +82 -0
- data/lib/holding.rb +32 -0
- data/lib/marc_helper.rb +254 -0
- data/lib/metadata_helper.rb +312 -0
- data/lib/opensearch_feed.rb +398 -0
- data/lib/opensearch_query.rb +98 -0
- data/lib/referent_filter.rb +16 -0
- data/lib/referent_filters/dissertation_catch.rb +45 -0
- data/lib/section_renderer.rb +503 -0
- data/lib/service.rb +336 -0
- data/lib/service_adaptors/ajax_export.rb +37 -0
- data/lib/service_adaptors/amazon.rb +412 -0
- data/lib/service_adaptors/blacklight.rb +327 -0
- data/lib/service_adaptors/book_finder.rb +40 -0
- data/lib/service_adaptors/bx.rb +51 -0
- data/lib/service_adaptors/cover_thing.rb +73 -0
- data/lib/service_adaptors/elsevier_cover.rb +57 -0
- data/lib/service_adaptors/email_export.rb +10 -0
- data/lib/service_adaptors/ezproxy.rb +171 -0
- data/lib/service_adaptors/google_book_search.rb +442 -0
- data/lib/service_adaptors/gpo.rb +124 -0
- data/lib/service_adaptors/hathi_trust.rb +308 -0
- data/lib/service_adaptors/hip3_service.rb +150 -0
- data/lib/service_adaptors/hip_holding_search.rb +237 -0
- data/lib/service_adaptors/internet_archive.rb +488 -0
- data/lib/service_adaptors/isbn_db.rb +86 -0
- data/lib/service_adaptors/isi.rb +258 -0
- data/lib/service_adaptors/jcr.rb +146 -0
- data/lib/service_adaptors/opac.rb +351 -0
- data/lib/service_adaptors/open_library.rb +316 -0
- data/lib/service_adaptors/open_library_cover.rb +73 -0
- data/lib/service_adaptors/primo_service.rb +392 -0
- data/lib/service_adaptors/primo_source.rb +78 -0
- data/lib/service_adaptors/pubmed.rb +133 -0
- data/lib/service_adaptors/request_to_fixture.rb +68 -0
- data/lib/service_adaptors/scopus.rb +295 -0
- data/lib/service_adaptors/sfx-new.rb +557 -0
- data/lib/service_adaptors/sfx.rb +566 -0
- data/lib/service_adaptors/sfx_backchannel_record.rb +69 -0
- data/lib/service_adaptors/txt_holding_export.rb +32 -0
- data/lib/service_adaptors/ulrichs_cover.rb +57 -0
- data/lib/service_adaptors/ulrichs_link.rb +47 -0
- data/lib/service_adaptors/worldcat.rb +116 -0
- data/lib/service_adaptors/worldcat_identities.rb +591 -0
- data/lib/tasks/umlaut.rake +134 -0
- data/lib/umlaut/default_configuration.rb +5 -0
- data/lib/umlaut/routes.rb +136 -0
- data/lib/umlaut/version.rb +3 -0
- data/lib/umlaut.rb +37 -0
- data/lib/umlaut_configurable.rb +343 -0
- data/lib/umlaut_http.rb +100 -0
- data/lib/xml_schema_helper.rb +109 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/umlaut_controller.rb +112 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +45 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database-jhu.yml +44 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +34 -0
- data/test/dummy/config/environments/production.rb +60 -0
- data/test/dummy/config/environments/test.rb +39 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +10 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +61 -0
- data/test/dummy/config/umlaut_services.yml +237 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20111228211210_umlaut_init.rb +113 -0
- data/test/dummy/db/schema.rb +124 -0
- data/test/dummy/log/development.log +12981 -0
- data/test/dummy/log/production.log +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/tmp/cache/assets/C5F/340/sprockets%2F99692920160b7a279b86a80415b79db7 +0 -0
- data/test/dummy/tmp/cache/assets/C70/4D0/sprockets%2F034ad2036e623081bd352800786dfe80 +0 -0
- data/test/dummy/tmp/cache/assets/C73/920/sprockets%2Fd371318f22900492fd180f17c5e2a504 +9268 -0
- data/test/dummy/tmp/cache/assets/C80/980/sprockets%2Fc94807409c1523d43e18d25f35d93c41 +0 -0
- data/test/dummy/tmp/cache/assets/C8F/780/sprockets%2Fe47e28558116fb5f8038754e60d1961d +11769 -0
- data/test/dummy/tmp/cache/assets/CAA/EB0/sprockets%2F1d179210e8b76f1ea63c802688a015e4 +9271 -0
- data/test/dummy/tmp/cache/assets/CBB/9C0/sprockets%2F706f28923fb754cad04b9107c89986a1 +0 -0
- data/test/dummy/tmp/cache/assets/CBF/B60/sprockets%2F08ca89671549936265dcb673bf02e36f +0 -0
- data/test/dummy/tmp/cache/assets/CC9/9F0/sprockets%2F306166316e2cafd13c15e62b51a2339d +0 -0
- data/test/dummy/tmp/cache/assets/CF6/F20/sprockets%2F5b2ffa1103079dfd555197838f87a99f +0 -0
- data/test/dummy/tmp/cache/assets/CF7/2B0/sprockets%2F25a7c73655bd3598173b39d9f98bcd46 +862 -0
- data/test/dummy/tmp/cache/assets/CFE/080/sprockets%2F37fe9f4255baddbd549a659914929398 +0 -0
- data/test/dummy/tmp/cache/assets/D22/060/sprockets%2F9aec77b768e91a802d284271c58e2f7e +21357 -0
- data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/D33/6D0/sprockets%2F500129c57f1146e556ec3aacd6cd38c1 +0 -0
- data/test/dummy/tmp/cache/assets/D33/FD0/sprockets%2F2ba0b4e6334a77b923e5f770381bb2bf +0 -0
- data/test/dummy/tmp/cache/assets/D42/C20/sprockets%2Fbcf14e437b1582bf93b77670acf8e090 +21353 -0
- data/test/dummy/tmp/cache/assets/D50/A30/sprockets%2F7d8b294ac433db5d056538f8cf7c66b9 +0 -0
- data/test/dummy/tmp/cache/assets/D54/ED0/sprockets%2F71c9fa01091d432b131da3bb73faf3d4 +872 -0
- data/test/dummy/tmp/cache/assets/D65/590/sprockets%2Fc1bb92fc3406a126b7dd302edc96d629 +0 -0
- data/test/dummy/tmp/cache/assets/D71/6B0/sprockets%2Fde558b71b494cf09b1bf055c8dff0353 +0 -0
- data/test/dummy/tmp/cache/assets/D72/610/sprockets%2Fa8c708eeb30ef93de34d755d4f45d023 +859 -0
- data/test/dummy/tmp/cache/assets/D76/AD0/sprockets%2Fe2158cde93188cf5ab6457bc6d6602ec +0 -0
- data/test/dummy/tmp/cache/assets/D7A/E40/sprockets%2F9622ffcc499a57627cd1bb18fe31b8e4 +11772 -0
- data/test/dummy/tmp/cache/assets/D84/210/sprockets%2Fabd0103ccec2b428ac62c94e4c40b384 +0 -0
- data/test/dummy/tmp/cache/assets/D9B/770/sprockets%2F8aacf02eb7dbb0949704b28f27b87e0b +0 -0
- data/test/dummy/tmp/cache/assets/DA6/A80/sprockets%2F92e26d8e58d5bcc8b8f6c25d1b05b9c1 +0 -0
- data/test/dummy/tmp/cache/assets/DE8/790/sprockets%2Fd1333bde2b9aafcc712d11dd09ab35d8 +0 -0
- data/test/dummy/tmp/cache/assets/DF7/F30/sprockets%2F7bc16c4109b17fabe29f8ddbbf732d1c +374 -0
- data/test/dummy/tmp/cache/assets/E03/570/sprockets%2F493bdc0ac14cd4f57fdfe4253f992bde +0 -0
- data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/E0B/4B0/sprockets%2F7988df51a61c81ce6ede4a2d4c8cce4f +377 -0
- data/test/dummy/tmp/cache/assets/E5F/960/sprockets%2Fdc007b6cad5c7ef08e33ec28cfff0ef6 +0 -0
- data/test/fixtures/dispatched_services.yml +5 -0
- data/test/fixtures/permalinks.yml +5 -0
- data/test/fixtures/referent_values.yml +1734 -0
- data/test/fixtures/referents.yml +156 -0
- data/test/fixtures/requests.yml +284 -0
- data/test/fixtures/service_responses.yml +5 -0
- data/test/fixtures/sfx_urls.yml +4 -0
- data/test/performance/browsing_test.rb +9 -0
- data/test/test_helper.rb +10 -0
- data/test/umlaut_test.rb +7 -0
- data/test/unit/aleph_patron_test.rb +39 -0
- data/test/unit/aleph_record_benchmarks.rb +28 -0
- data/test/unit/aleph_record_test.rb +30 -0
- data/test/unit/aws_product_sign_test.rb +93 -0
- data/test/unit/collection_test.rb +76 -0
- data/test/unit/google_book_search_test.rb +101 -0
- data/test/unit/primo_searcher_test.rb +403 -0
- data/test/unit/primo_service_test.rb +939 -0
- data/test/unit/primo_ws_test.rb +131 -0
- data/test/unit/service_response_test.rb +9 -0
- data/test/unit/service_test.rb +33 -0
- 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
|