oai 0.0.1 → 1.3.0

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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +128 -0
  3. data/Rakefile +64 -0
  4. data/bin/oai +66 -0
  5. data/examples/models/file_model.rb +63 -0
  6. data/examples/providers/dublin_core.rb +474 -0
  7. data/lib/oai/{get_record.rb → client/get_record.rb} +5 -1
  8. data/lib/oai/client/header.rb +18 -0
  9. data/lib/oai/{identify.rb → client/identify.rb} +2 -0
  10. data/lib/oai/{list_identifiers.rb → client/list_identifiers.rb} +1 -0
  11. data/lib/oai/{list_records.rb → client/list_records.rb} +6 -5
  12. data/lib/oai/{list_sets.rb → client/list_sets.rb} +7 -6
  13. data/lib/oai/client/record.rb +27 -0
  14. data/lib/oai/client/response.rb +53 -0
  15. data/lib/oai/client/resumable.rb +42 -0
  16. data/lib/oai/client.rb +313 -108
  17. data/lib/oai/constants.rb +34 -0
  18. data/lib/oai/exception.rb +80 -1
  19. data/lib/oai/harvester/config.rb +41 -0
  20. data/lib/oai/harvester/harvest.rb +162 -0
  21. data/lib/oai/harvester/logging.rb +68 -0
  22. data/lib/oai/harvester/mailer.rb +17 -0
  23. data/lib/oai/harvester/shell.rb +337 -0
  24. data/lib/oai/harvester.rb +43 -0
  25. data/lib/oai/provider/metadata_format/oai_dc.rb +28 -0
  26. data/lib/oai/provider/metadata_format.rb +119 -0
  27. data/lib/oai/provider/model/activerecord_caching_wrapper.rb +129 -0
  28. data/lib/oai/provider/model/activerecord_wrapper.rb +217 -0
  29. data/lib/oai/provider/model.rb +80 -0
  30. data/lib/oai/provider/partial_result.rb +18 -0
  31. data/lib/oai/provider/response/error.rb +16 -0
  32. data/lib/oai/provider/response/get_record.rb +27 -0
  33. data/lib/oai/provider/response/identify.rb +37 -0
  34. data/lib/oai/provider/response/list_identifiers.rb +30 -0
  35. data/lib/oai/provider/response/list_metadata_formats.rb +39 -0
  36. data/lib/oai/provider/response/list_records.rb +47 -0
  37. data/lib/oai/provider/response/list_sets.rb +23 -0
  38. data/lib/oai/provider/response/record_response.rb +103 -0
  39. data/lib/oai/provider/response.rb +133 -0
  40. data/lib/oai/provider/resumption_token.rb +155 -0
  41. data/lib/oai/provider.rb +487 -0
  42. data/lib/oai/set.rb +14 -5
  43. data/lib/oai/xpath.rb +59 -5
  44. data/lib/oai.rb +7 -13
  45. data/lib/test.rb +25 -0
  46. data/test/activerecord_provider/config/connection.rb +30 -0
  47. data/test/activerecord_provider/database/0001_oaipmh_tables.rb +70 -0
  48. data/test/activerecord_provider/fixtures/dc.yml +1501 -0
  49. data/test/activerecord_provider/helpers/providers.rb +68 -0
  50. data/test/activerecord_provider/helpers/set_provider.rb +24 -0
  51. data/test/activerecord_provider/helpers/transactional_test_case.rb +35 -0
  52. data/test/activerecord_provider/models/dc_field.rb +15 -0
  53. data/test/activerecord_provider/models/dc_lang.rb +3 -0
  54. data/test/activerecord_provider/models/dc_set.rb +7 -0
  55. data/test/activerecord_provider/models/exclusive_set_dc_field.rb +17 -0
  56. data/test/activerecord_provider/models/oai_token.rb +3 -0
  57. data/test/activerecord_provider/tc_activerecord_wrapper.rb +63 -0
  58. data/test/activerecord_provider/tc_ar_provider.rb +199 -0
  59. data/test/activerecord_provider/tc_ar_sets_provider.rb +159 -0
  60. data/test/activerecord_provider/tc_caching_paging_provider.rb +50 -0
  61. data/test/activerecord_provider/tc_simple_paging_provider.rb +71 -0
  62. data/test/activerecord_provider/test_helper_ar_provider.rb +7 -0
  63. data/test/client/helpers/provider.rb +65 -0
  64. data/test/client/helpers/test_wrapper.rb +6 -0
  65. data/test/client/tc_exception.rb +36 -0
  66. data/test/client/tc_get_record.rb +39 -0
  67. data/test/client/tc_http_client.rb +129 -0
  68. data/test/client/tc_identify.rb +13 -0
  69. data/test/client/tc_libxml.rb +61 -0
  70. data/test/{tc_list_identifiers.rb → client/tc_list_identifiers.rb} +28 -9
  71. data/test/{tc_list_metadata_formats.rb → client/tc_list_metadata_formats.rb} +4 -1
  72. data/test/client/tc_list_records.rb +26 -0
  73. data/test/client/tc_list_sets.rb +34 -0
  74. data/test/client/tc_low_resolution_dates.rb +14 -0
  75. data/test/client/tc_utf8_escaping.rb +19 -0
  76. data/test/client/tc_xpath.rb +26 -0
  77. data/test/client/test_helper_client.rb +5 -0
  78. data/test/harvester/tc_harvest.rb +42 -0
  79. data/test/harvester/test_helper_harvester.rb +6 -0
  80. data/test/provider/models.rb +246 -0
  81. data/test/provider/tc_exceptions.rb +81 -0
  82. data/test/provider/tc_functional_tokens.rb +53 -0
  83. data/test/provider/tc_instance_provider.rb +41 -0
  84. data/test/provider/tc_provider.rb +136 -0
  85. data/test/provider/tc_resumption_tokens.rb +52 -0
  86. data/test/provider/tc_simple_provider.rb +138 -0
  87. data/test/provider/test_helper_provider.rb +62 -0
  88. data/test/test.xml +22 -0
  89. metadata +238 -56
  90. data/lib/oai/header.rb +0 -12
  91. data/lib/oai/record.rb +0 -11
  92. data/lib/oai/response.rb +0 -20
  93. data/test/tc_get_record.rb +0 -27
  94. data/test/tc_identify.rb +0 -8
  95. data/test/tc_list_records.rb +0 -9
  96. data/test/tc_list_sets.rb +0 -17
  97. data/test.rb +0 -12
  98. /data/lib/oai/{list_metadata_formats.rb → client/list_metadata_formats.rb} +0 -0
  99. /data/lib/oai/{metadata_format.rb → client/metadata_format.rb} +0 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f07e6aaa014da7ec0e3fc389e2dd8dfe05c91e3583cea3123852558ba90e9acf
4
+ data.tar.gz: 88f2c92e7697899526e6df57582b4b4ae054c8e7b9ad5dc0b27774bd51198544
5
+ SHA512:
6
+ metadata.gz: 85064e01e23a76b7dfec02dac0f6cdd76389683401052d20b8021f16b8b499f33b344ef850c1a1b5b38a14ac8a70bbb6e0013429c0ad4e0d9ad2ab7d6750052c
7
+ data.tar.gz: 2b294bf969081f3fd310156a7ad3b3dd7aff977c49473f2892a625998dff3b01ae4c0b74a2ce04215c3832ccb1c8a0a5bec468f54dae46b1e5ce111f438c4607
data/README.md ADDED
@@ -0,0 +1,128 @@
1
+ ruby-oai
2
+ ========
3
+ [![Build Status](https://github.com/code4lib/ruby-oai/workflows/CI/badge.svg)](https://github.com/code4lib/ruby-oai/actions)
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/oai.svg)](https://badge.fury.io/rb/oai)
6
+
7
+ ruby-oai is a Open Archives Protocol for Metadata Harvesting (OAI-PMH)
8
+ library for Ruby. [OAI-PMH](http://openarchives.org) is a somewhat
9
+ archaic protocol for sharing metadata between digital library repositories.
10
+ If you are looking to share metadata on the web you are probably better off
11
+ using a feed format like [RSS](http://www.rssboard.org/rss-specification) or
12
+ [Atom](http://www.atomenabled.org/). If have to work with a backwards
13
+ digital repository that only offers OAI-PMH access then ruby-oai is your
14
+ friend.
15
+
16
+ The [OAI-PMH](http://openarchives.org) spec defines six verbs
17
+ (`Identify`, `ListIdentifiers`, `ListRecords`,
18
+ `GetRecords`, `ListSets`, `ListMetadataFormat`) used for discovery and sharing of
19
+ metadata.
20
+
21
+ The ruby-oai gem includes a client library, a server/provider library and
22
+ a interactive harvesting shell.
23
+
24
+ Client
25
+ ------
26
+
27
+ The OAI client library is used for harvesting metadata from repositories.
28
+ For example to initiate a ListRecords request to pubmed you can:
29
+
30
+ ```ruby
31
+ require 'oai'
32
+ client = OAI::Client.new 'http://www.pubmedcentral.gov/oai/oai.cgi', :headers => { "From" => "oai@example.com" }
33
+ response = client.list_records
34
+ # Get the first page of records
35
+ response.each do |record|
36
+ puts record.metadata
37
+ end
38
+ # Get the second page of records
39
+ response = client.list_records(:resumption_token => response.resumption_token)
40
+ response.each do |record|
41
+ puts record.metadata
42
+ end
43
+ # Get all pages together (may take a *very* long time to complete)
44
+ client.list_records.full.each do |record|
45
+ puts record.metadata
46
+ end
47
+ ```
48
+
49
+ ### Retry-After
50
+ This library depends on faraday, but allows a wide range of versions. Depending on the client application's installed version of faraday, there may be different middleware libraries required to support automatically retrying requests that are rate limited/denied with a `Retry-After` header. The OAI client can, however, accept an externally configured faraday http client for handling this. For example, to retry on `429 Too Many Requests`:
51
+
52
+ ```ruby
53
+ require 'oai'
54
+ require 'faraday_middleware' # if using faraday version < 2
55
+ http_client = Faraday.new do |conn|
56
+ conn.request(:retry, max: 5, retry_statuses: 429)
57
+ conn.response(:follow_redirects, limit: 5)
58
+ conn.adapter :net_http
59
+ end
60
+ client = OAI::Client.new(base_url, http: http_client)
61
+ opts = {from:'2012-03-01', until:'2012-04-01', metadata_prefix:'oai_dc'}
62
+ puts client.list_records(opts).full.count
63
+ ```
64
+
65
+ See {OAI::Client} for more details
66
+
67
+ Server
68
+ ------
69
+
70
+ The OAI provider library handles serving local content to other clients. Here's how to set up a simple provider:
71
+
72
+ ```ruby
73
+ class MyProvider < Oai::Provider
74
+ repository_name 'My little OAI provider'
75
+ repository_url 'http://localhost/provider'
76
+ record_prefix 'oai:localhost'
77
+ admin_email 'root@localhost' # String or Array
78
+ source_model MyModel.new # Subclass of OAI::Provider::Model
79
+ end
80
+ ```
81
+
82
+ See comment docs at top of [OAI::Provider](./lib/oai/provider.rb) for more details, including discussion of the `OAI::Provider::ActiveRecordWrapper` class for quich setup of an OAI provider for an ActiveRecord model class (single database table)
83
+
84
+ Interactive Harvester
85
+ ---------------------
86
+
87
+ The OAI-PMH client shell allows OAI Harvesting to be configured in an interactive manner. Typing `oai` on the command line starts the shell. After initial configuration, the shell can be used to manage harvesting operations.
88
+
89
+ See {OAI::Harvester::Shell} for more details
90
+
91
+ Installation
92
+ ------------
93
+
94
+ Normally the best way to install oai is as part of your `Gemfile`:
95
+
96
+ source :rubygems
97
+ gem 'oai'
98
+
99
+ Alternately it can be installed globally using RubyGems:
100
+
101
+ $ gem install oai
102
+
103
+ Running tests
104
+ -------------
105
+
106
+ Tests are with Test::Unit, in a somewhat archaic/legacy style. Test setup especially is not how we would do things today. Run all tests with:
107
+
108
+ $ bundle exec rake test
109
+
110
+ There are also convenience tasks to run subsets of tests.
111
+
112
+ We use [appraisal](https://github.com/thoughtbot/appraisal) to test ActiveRecord-related functionality under multiple versions of ActiveRecord. While the above commands will test with latest ActiveRecord (allowed in our .gemspec development dependency), you can test under a particular version defined in the [Appraisals](./Appraisals) file like so:
113
+
114
+ $ bundle exec appraisal rails-52 rake test
115
+ $ bundle exec appraisal rails-70 rake test
116
+
117
+ If you run into trouble with appraisal's gemfiles getting out of date and bundler complaining,
118
+ try:
119
+
120
+ $ bundle exec appraisal clean
121
+ $ appraisal generate
122
+
123
+ That may make changes to appraisal gemfiles that you should commit to repo.
124
+
125
+ License
126
+ -------
127
+
128
+ [MIT](./LICENSE)
data/Rakefile ADDED
@@ -0,0 +1,64 @@
1
+
2
+ require 'rubygems'
3
+ require 'rake'
4
+ begin
5
+ require 'bundler/setup'
6
+ rescue LoadError
7
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
8
+ end
9
+
10
+ Bundler::GemHelper.install_tasks
11
+
12
+ require 'rake/testtask'
13
+ require 'yard'
14
+
15
+ task :default => ["test", "yard"]
16
+
17
+ Rake::TestTask.new('test') do |t|
18
+ t.description = "Run all Test::Unit tests"
19
+
20
+ t.libs << ['lib', 'test/client', 'test/provider', 'test/activerecord_provider']
21
+
22
+ t.pattern = 'test/{client,provider,activerecord_provider}/tc_*.rb'
23
+ #t.verbose = true
24
+ t.warning = false
25
+ end
26
+
27
+
28
+ # To run just subsets of tests
29
+ namespace :test do
30
+ Rake::TestTask.new('client') do |t|
31
+ t.libs << ['lib', 'test/client']
32
+ t.pattern = 'test/client/tc_*.rb'
33
+ #t.verbose = true
34
+ t.warning = false
35
+ end
36
+
37
+ Rake::TestTask.new('harvester') do |t|
38
+ t.libs << ['lib', 'test/harvester']
39
+ t.pattern = 'test/harvester/tc_*.rb'
40
+ #t.verbose = true
41
+ t.warning = false
42
+ end
43
+
44
+ Rake::TestTask.new('provider') do |t|
45
+ t.libs << ['lib', 'test/provider']
46
+ t.pattern = 'test/provider/tc_*.rb'
47
+ #t.verbose = true
48
+ t.warning = false
49
+ end
50
+
51
+ Rake::TestTask.new('activerecord_provider') do |t|
52
+ t.description = "Active Record base Provider Tests"
53
+
54
+ t.libs << ['lib', 'test/activerecord_provider']
55
+ t.pattern = 'test/activerecord_provider/tc_*.rb'
56
+ #t.verbose = true
57
+ t.warning = false
58
+ end
59
+ end
60
+
61
+ YARD::Rake::YardocTask.new do |t|
62
+ t.files = ["lib/**/*.rb"]
63
+ t.options = ['--output-dir', 'doc']
64
+ end
data/bin/oai ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby -rubygems
2
+ #
3
+ # Created by William Groppe on 2006-11-05.
4
+ # Copyright (c) 2006. All rights reserved.
5
+
6
+ require 'optparse'
7
+
8
+ require 'oai/harvester'
9
+
10
+ include OAI::Harvester
11
+
12
+ conf = OAI::Harvester::Config.load
13
+
14
+ startup = :interactive
15
+
16
+ rexml = false
17
+
18
+ opts = OptionParser.new do |opts|
19
+ opts.banner = "Usage: oai ..."
20
+ opts.define_head "#{File.basename($0)}, a OAI harvester shell."
21
+ opts.separator ""
22
+ opts.separator "Options:"
23
+
24
+ opts.on("-D", "--daemon", "Non-interactive mode, to be called via scheduler") { startup = :daemon }
25
+ opts.on("-R", "--rexml", "Use rexml even if libxml is available") { rexml = true }
26
+ opts.on("-?", "--help", "Show this message") do
27
+ puts opts
28
+ exit
29
+ end
30
+
31
+ # Another typical switch to print the version.
32
+ opts.on_tail("-v", "--version", "Show version") do
33
+ class << Gem; attr_accessor :loaded_specs; end
34
+ puts Gem.loaded_specs['oai'].version
35
+ exit
36
+ end
37
+ end
38
+
39
+ begin
40
+ opts.parse! ARGV
41
+ rescue
42
+ puts opts
43
+ exit
44
+ end
45
+
46
+ unless rexml
47
+ begin # Try to load libxml to speed up harvesting
48
+ require 'xml/libxml'
49
+ rescue LoadError
50
+ end
51
+ end
52
+
53
+ case startup
54
+ when :interactive
55
+ shell = Shell.new(conf)
56
+ shell.start
57
+ when :daemon
58
+ if conf.storage
59
+ harvest = Harvest.new(conf)
60
+ harvest.start(harvestable_sites(conf))
61
+ else
62
+ puts "Missing or corrupt configuration file, cannot continue."
63
+ exit(-1)
64
+ end
65
+ end
66
+
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by William Groppe on 2007-02-01.
4
+ #
5
+ # Simple file based Model. Basically just serves a directory of xml files to the
6
+ # Provider.
7
+ #
8
+ class File
9
+ def id
10
+ File.basename(self.path)
11
+ end
12
+
13
+ def to_oai_dc
14
+ self.read
15
+ end
16
+ end
17
+
18
+ class FileModel < OAI::Provider::Model
19
+ include OAI::Provider
20
+
21
+ def initialize(directory = 'data')
22
+ # nil specifies no partial results aka resumption tokens, and 'mtime' is the
23
+ # method that the provider will call for determining the timestamp
24
+ super(nil, 'mtime')
25
+ @directory = directory
26
+ end
27
+
28
+ def earliest
29
+ e = Dir["#{@directory}/*.xml"].min { |a,b| File.stat(a).mtime <=> File.stat(b).mtime }
30
+ File.stat(e).mtime.utc.xmlschema
31
+ end
32
+
33
+ def latest
34
+ e = Dir["#{@directory}/*.xml"].max { |a,b| File.stat(a).mtime <=> File.stat(b).mtime }
35
+ File.stat(e).mtime.utc.xmlschema
36
+ end
37
+
38
+ def sets
39
+ nil
40
+ end
41
+
42
+ def find(selector, opts={})
43
+ return nil unless selector
44
+
45
+ case selector
46
+ when :all
47
+ records = Dir["#{@directory}/*.xml"].sort.collect do |file|
48
+ File.new(file) unless File.stat(file).mtime.utc < opts[:from].to_time or
49
+ File.stat(file).mtime.utc > opts[:until].to_time
50
+ end
51
+ records
52
+ else
53
+ Find.find("#{@directory}/#{selector}") rescue nil
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ # == Example Usage:
60
+ # class FileProvider < OAI::Provider::Base
61
+ # repository_name 'XML File Provider'
62
+ # source_model FileModel.new('/tmp')
63
+ # end