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.
- checksums.yaml +7 -0
- data/README.md +128 -0
- data/Rakefile +64 -0
- data/bin/oai +66 -0
- data/examples/models/file_model.rb +63 -0
- data/examples/providers/dublin_core.rb +474 -0
- data/lib/oai/{get_record.rb → client/get_record.rb} +5 -1
- data/lib/oai/client/header.rb +18 -0
- data/lib/oai/{identify.rb → client/identify.rb} +2 -0
- data/lib/oai/{list_identifiers.rb → client/list_identifiers.rb} +1 -0
- data/lib/oai/{list_records.rb → client/list_records.rb} +6 -5
- data/lib/oai/{list_sets.rb → client/list_sets.rb} +7 -6
- data/lib/oai/client/record.rb +27 -0
- data/lib/oai/client/response.rb +53 -0
- data/lib/oai/client/resumable.rb +42 -0
- data/lib/oai/client.rb +313 -108
- data/lib/oai/constants.rb +34 -0
- data/lib/oai/exception.rb +80 -1
- data/lib/oai/harvester/config.rb +41 -0
- data/lib/oai/harvester/harvest.rb +162 -0
- data/lib/oai/harvester/logging.rb +68 -0
- data/lib/oai/harvester/mailer.rb +17 -0
- data/lib/oai/harvester/shell.rb +337 -0
- data/lib/oai/harvester.rb +43 -0
- data/lib/oai/provider/metadata_format/oai_dc.rb +28 -0
- data/lib/oai/provider/metadata_format.rb +119 -0
- data/lib/oai/provider/model/activerecord_caching_wrapper.rb +129 -0
- data/lib/oai/provider/model/activerecord_wrapper.rb +217 -0
- data/lib/oai/provider/model.rb +80 -0
- data/lib/oai/provider/partial_result.rb +18 -0
- data/lib/oai/provider/response/error.rb +16 -0
- data/lib/oai/provider/response/get_record.rb +27 -0
- data/lib/oai/provider/response/identify.rb +37 -0
- data/lib/oai/provider/response/list_identifiers.rb +30 -0
- data/lib/oai/provider/response/list_metadata_formats.rb +39 -0
- data/lib/oai/provider/response/list_records.rb +47 -0
- data/lib/oai/provider/response/list_sets.rb +23 -0
- data/lib/oai/provider/response/record_response.rb +103 -0
- data/lib/oai/provider/response.rb +133 -0
- data/lib/oai/provider/resumption_token.rb +155 -0
- data/lib/oai/provider.rb +487 -0
- data/lib/oai/set.rb +14 -5
- data/lib/oai/xpath.rb +59 -5
- data/lib/oai.rb +7 -13
- data/lib/test.rb +25 -0
- data/test/activerecord_provider/config/connection.rb +30 -0
- data/test/activerecord_provider/database/0001_oaipmh_tables.rb +70 -0
- data/test/activerecord_provider/fixtures/dc.yml +1501 -0
- data/test/activerecord_provider/helpers/providers.rb +68 -0
- data/test/activerecord_provider/helpers/set_provider.rb +24 -0
- data/test/activerecord_provider/helpers/transactional_test_case.rb +35 -0
- data/test/activerecord_provider/models/dc_field.rb +15 -0
- data/test/activerecord_provider/models/dc_lang.rb +3 -0
- data/test/activerecord_provider/models/dc_set.rb +7 -0
- data/test/activerecord_provider/models/exclusive_set_dc_field.rb +17 -0
- data/test/activerecord_provider/models/oai_token.rb +3 -0
- data/test/activerecord_provider/tc_activerecord_wrapper.rb +63 -0
- data/test/activerecord_provider/tc_ar_provider.rb +199 -0
- data/test/activerecord_provider/tc_ar_sets_provider.rb +159 -0
- data/test/activerecord_provider/tc_caching_paging_provider.rb +50 -0
- data/test/activerecord_provider/tc_simple_paging_provider.rb +71 -0
- data/test/activerecord_provider/test_helper_ar_provider.rb +7 -0
- data/test/client/helpers/provider.rb +65 -0
- data/test/client/helpers/test_wrapper.rb +6 -0
- data/test/client/tc_exception.rb +36 -0
- data/test/client/tc_get_record.rb +39 -0
- data/test/client/tc_http_client.rb +129 -0
- data/test/client/tc_identify.rb +13 -0
- data/test/client/tc_libxml.rb +61 -0
- data/test/{tc_list_identifiers.rb → client/tc_list_identifiers.rb} +28 -9
- data/test/{tc_list_metadata_formats.rb → client/tc_list_metadata_formats.rb} +4 -1
- data/test/client/tc_list_records.rb +26 -0
- data/test/client/tc_list_sets.rb +34 -0
- data/test/client/tc_low_resolution_dates.rb +14 -0
- data/test/client/tc_utf8_escaping.rb +19 -0
- data/test/client/tc_xpath.rb +26 -0
- data/test/client/test_helper_client.rb +5 -0
- data/test/harvester/tc_harvest.rb +42 -0
- data/test/harvester/test_helper_harvester.rb +6 -0
- data/test/provider/models.rb +246 -0
- data/test/provider/tc_exceptions.rb +81 -0
- data/test/provider/tc_functional_tokens.rb +53 -0
- data/test/provider/tc_instance_provider.rb +41 -0
- data/test/provider/tc_provider.rb +136 -0
- data/test/provider/tc_resumption_tokens.rb +52 -0
- data/test/provider/tc_simple_provider.rb +138 -0
- data/test/provider/test_helper_provider.rb +62 -0
- data/test/test.xml +22 -0
- metadata +238 -56
- data/lib/oai/header.rb +0 -12
- data/lib/oai/record.rb +0 -11
- data/lib/oai/response.rb +0 -20
- data/test/tc_get_record.rb +0 -27
- data/test/tc_identify.rb +0 -8
- data/test/tc_list_records.rb +0 -9
- data/test/tc_list_sets.rb +0 -17
- data/test.rb +0 -12
- /data/lib/oai/{list_metadata_formats.rb → client/list_metadata_formats.rb} +0 -0
- /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
|
+
[](https://github.com/code4lib/ruby-oai/actions)
|
|
4
|
+
|
|
5
|
+
[](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
|