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
@@ -0,0 +1,162 @@
1
+ #
2
+ # Created by William Groppe on 2006-11-03.
3
+
4
+ module OAI
5
+ module Harvester
6
+ class Harvest
7
+ DIRECTORY_LAYOUT = "%Y/%m".freeze
8
+
9
+ def initialize(config = nil, directory = nil, date = nil, to = nil)
10
+ @config = config || Config.load
11
+ @directory = directory || @config.storage
12
+ @from = date
13
+ @from.freeze
14
+ @until = to
15
+ @until.freeze
16
+ @parser = defined?(XML::Document) ? 'libxml' : 'rexml'
17
+ end
18
+
19
+ def start(sites = nil, interactive = false)
20
+ @interactive = interactive
21
+ sites = (@config.sites.keys rescue {}) unless sites
22
+ begin
23
+ sites.each do |site|
24
+ harvest(site)
25
+ end
26
+ ensure
27
+ @config.save
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def harvest(site)
34
+ opts = build_options_hash(@config.sites[site])
35
+ if @until
36
+ harvest_time = @until.to_time.utc
37
+ else
38
+ harvest_time = Time.now.utc
39
+ end
40
+
41
+ if OAI::Const::Granularity::LOW == granularity(opts[:url])
42
+ opts[:until] = harvest_time.strftime("%Y-%m-%d")
43
+ opts[:from] = @from.strftime("%Y-%m-%d") if @from
44
+ else
45
+ opts[:until] = harvest_time.xmlschema
46
+ opts[:from] = @from.xmlschema if @from
47
+ end
48
+
49
+ # Allow a from date to be passed in
50
+ opts[:from] = earliest(opts[:url]) unless opts[:from]
51
+ opts.delete(:set) if 'all' == opts[:set]
52
+ begin
53
+ # Connect, and download
54
+ file, records = call(opts.delete(:url), opts)
55
+
56
+ # Move document to storage directory if configured
57
+ if @directory
58
+ directory_layout = @config.layouts[site] if @config.layouts
59
+ dir = File.join(@directory, date_based_directory(harvest_time, directory_layout))
60
+ FileUtils.mkdir_p dir
61
+ FileUtils.mv(file.path,
62
+ File.join(dir, "#{site}-#{filename(Time.parse(opts[:from]),
63
+ harvest_time)}.xml.gz"))
64
+ else
65
+ puts "no configured destination for temp file" if @interactive
66
+ end
67
+ @config.sites[site]['last'] = harvest_time
68
+ rescue OAI::NoMatchException
69
+ puts "No new records available" if @interactive
70
+ rescue OAI::Exception => ex
71
+ raise ex if not @interactive
72
+ puts ex.message
73
+ end
74
+ end
75
+
76
+ def call(url, opts)
77
+ # Preserve original options
78
+ options = opts.dup
79
+
80
+ records = 0;
81
+ client = OAI::Client.new(url, :parser => @parser)
82
+ provider_config = client.identify
83
+
84
+ file = Tempfile.new('oai_data')
85
+ gz = Zlib::GzipWriter.new(file)
86
+ gz << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
87
+ gz << "<records>"
88
+ begin
89
+ response = client.list_records(options)
90
+ response.each do |rec|
91
+ gz << rec._source
92
+ records += 1
93
+ end
94
+ puts "#{records} records retrieved" if @interactive
95
+
96
+ # Get a full response by iterating with the resumption tokens.
97
+ # Not very Ruby like. Should fix OAI::Client to handle resumption
98
+ # tokens internally.
99
+ while(response.resumption_token and not response.resumption_token.empty?)
100
+ puts "\nresumption token recieved, continuing" if @interactive
101
+ response = client.list_records(:resumption_token =>
102
+ response.resumption_token)
103
+ response.each do |rec|
104
+ gz << rec._source
105
+ records += 1
106
+ end
107
+ puts "#{records} records retrieved" if @interactive
108
+ end
109
+
110
+ gz << "</records>"
111
+
112
+ ensure
113
+ gz.close
114
+ file.close
115
+ end
116
+
117
+ [file, records]
118
+ end
119
+
120
+ def get_records(doc)
121
+ doc.find("/OAI-PMH/ListRecords/record").to_a
122
+ end
123
+
124
+ def build_options_hash(site)
125
+ options = {:url => site['url']}
126
+ options[:set] = site['set'] if site['set']
127
+ options[:from] = site['last'].utc.xmlschema if site['last']
128
+ options[:metadata_prefix] = site['prefix'] if site['prefix']
129
+ options
130
+ end
131
+
132
+ def date_based_directory(time, directory_layout = nil)
133
+ directory_layout ||= Harvest::DIRECTORY_LAYOUT
134
+ "#{time.strftime(directory_layout)}"
135
+ end
136
+
137
+ def filename(from_time, until_time)
138
+ format = "%Y-%m-%d"
139
+ "#{from_time.strftime(format)}_til_#{until_time.strftime(format)}"\
140
+ "_at_#{until_time.strftime('%H-%M-%S')}"
141
+ end
142
+
143
+ def granularity(url)
144
+ client = OAI::Client.new url
145
+ client.identify.granularity
146
+ end
147
+
148
+ # Get earliest timestamp from repository
149
+ def earliest(url)
150
+ client = OAI::Client.new url
151
+ identify = client.identify
152
+ if OAI::Const::Granularity::LOW == identify.granularity
153
+ Time.parse(identify.earliest_datestamp).strftime("%Y-%m-%d")
154
+ else
155
+ Time.parse(identify.earliest_datestamp).xmlschema
156
+ end
157
+ end
158
+
159
+ end
160
+
161
+ end
162
+ end
@@ -0,0 +1,68 @@
1
+ # Reopen Harvest and add logging
2
+ module OAI
3
+ module Harvester
4
+
5
+ class Harvest
6
+ alias_method :orig_start, :start
7
+ alias_method :orig_harvest, :harvest
8
+ alias_method :orig_call, :call
9
+ alias_method :orig_init, :initialize
10
+
11
+ def initialize(*args)
12
+ orig_init(*args)
13
+ @summary = []
14
+ @logger = @config.logfile ? Logger.new(File.join(@config.logfile, "harvester.log"), 'weekly') : Logger.new(STDOUT)
15
+ @logger.datetime_format = "%Y-%m-%d %H:%M"
16
+
17
+ # Turn off logging if no logging directory is specified.
18
+ @logger.level = Logger::FATAL unless @config.logfile
19
+ end
20
+
21
+ def start(sites = nil, interactive = false)
22
+ if not interactive
23
+ @logger.info { "Starting regular harvest" }
24
+ orig_start(sites)
25
+ begin
26
+ OAI::Harvester::Mailer.send(@config.mail_server, @config.email, @summary) if @config.email
27
+ rescue
28
+ @logger.error { "Error sending out summary email: #{$!}"}
29
+ end
30
+ else
31
+ @logger.info { "Starting interactive harvest"}
32
+ orig_start(sites, true)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def harvest(site)
39
+ begin
40
+ @logger.info { "Harvest of '#{site}' starting" }
41
+ @summary << "Harvest of '#{site}' attempted"
42
+ orig_harvest(site)
43
+ rescue OAI::Exception
44
+ if "noRecordsMatch" == $!.code
45
+ @logger.info "No new records available"
46
+ @summary << "'#{site}' had no new records."
47
+ else
48
+ @logger.error { "Harvesting of '#{site}' failed, message: #{$!}" }
49
+ @summary << "'#{site}' had an OAI Error! #{$!}"
50
+ end
51
+ rescue
52
+ @logger.error { "Harvesting of '#{site}' failed, message: #{$!}" }
53
+ @logger.error { "#{$!.backtrace.join('\n')}" }
54
+ @summary << "'#{site}' had an Error! #{$!}"
55
+ end
56
+ end
57
+
58
+ def call(url, options)
59
+ @logger.info { "fetching: #{url} with options #{options.inspect}" }
60
+ file, records = orig_call(url, options)
61
+ @logger.info { "retrieved #{records} records" }
62
+ @summary << "Retrieved #{records} records."
63
+ return file, records
64
+ end
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,17 @@
1
+ module OAI
2
+ module Harvester
3
+
4
+ class Mailer
5
+
6
+ def self.send(server = nil, email = nil, message = nil)
7
+ msg = %{Subject: Harvester Summary\n\n#{message.join("\n")}}
8
+ to = (email.map { |e| "'#{e}'"}).join(", ")
9
+ Net::SMTP.start(server) do |smtp|
10
+ smtp.send_message msg, "harvester@#{Socket.gethostname}", to
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,337 @@
1
+ module OAI
2
+ module Harvester
3
+ # A OAI-PMH client shell allowing OAI Harvesting to be configured in
4
+ # an interactive manner. Typing `oai` on the command line starts the
5
+ # shell. The first time the shell is run it will prompt for the following
6
+ # configuration details:
7
+ #
8
+ # 1. A storage directory for all harvested records. Harvests will be
9
+ # stored under this directory in a directory structure based on the
10
+ # date of the harvest.
11
+ # 2. A log file directory.
12
+ # 3. Email address(es) for sending daily harvesting activity reports.
13
+ # 4. Network address of the SMTP server for sending mail.
14
+ #
15
+ # After the initial configuration, new harvest sites can be added by using
16
+ # the 'new' command. Sites are identified via nickname assigned by the
17
+ # user. After choosing a nickname, provide the URL of a harvestable site,
18
+ # and the shell will prompt you for the rest of the configuration
19
+ # information.
20
+ #
21
+ # The shell automatically pulls down the list of sets in the repository, and
22
+ # the supported metadata prefixes. Making it very simple to setup harvests.
23
+ #
24
+ class Shell
25
+ include Readline
26
+
27
+ def initialize(config)
28
+ @conf = config
29
+ @conf.sites ||= {} # Initialize sites hash there isn't one
30
+ end
31
+
32
+ def start
33
+ unless @conf.storage
34
+ banner "Entering first-time setup"
35
+ config
36
+ setup_cron
37
+ end
38
+ puts "type 'help' for help"
39
+ while((input = readline("oai> ", true)) != 'exit')
40
+ begin
41
+ cmd = input.split
42
+ if 1 == cmd.size
43
+ self.send(cmd[0])
44
+ else
45
+ self.send(cmd.shift, cmd.join(" "))
46
+ end
47
+ rescue NoMethodError
48
+ puts "Not a recognized command. Type 'help' for clues."
49
+ rescue
50
+ puts "An error occurred:"
51
+ puts $!
52
+ puts $!.backtrace.join("\n")
53
+ end
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def help
60
+ banner "Commands:"
61
+ puts "\tharvest site [date] - Harvest site(s) manually"
62
+ puts "\tconfig - Configure harvester"
63
+ puts "\tlist <config> - List known providers or configuration"
64
+ puts "\tinfo [site[, site]] - Show information about a provider."
65
+ puts "\tnew - Add a new provider site to harvester"
66
+ puts "\tremove [site] - Remove a provider site from harvester"
67
+ puts "\tedit [site] - Change settings for a provider site"
68
+ puts "\texit - Exit the harvester shell.\n\n"
69
+ end
70
+
71
+ def harvest(options)
72
+ site, *date = options.split(/\s/)
73
+ if @conf.sites.keys.include?(site)
74
+ banner "Harvesting '#{site}'"
75
+ if date && !date.empty?
76
+ begin
77
+ date = Time.parse(date.join(' ')).utc
78
+ rescue NoMethodError
79
+ puts "Couldn't parse the date supplied"
80
+ return
81
+ end
82
+ else
83
+ date = nil
84
+ end
85
+ harvester = Harvest.new(@conf, @conf.storage, date)
86
+ harvester.start(site, true)
87
+ puts "done"
88
+ else
89
+ puts "Unknown repository: '#{args[0]}'"
90
+ end
91
+ puts # blank line
92
+ end
93
+
94
+ def list(args = nil)
95
+ if 'config' == args
96
+ banner "Current Configuration"
97
+ list_config
98
+ else
99
+ banner "Configured Repositories"
100
+ @conf.sites.keys.each do |k|
101
+ puts k
102
+ end
103
+ end
104
+ puts # blank line
105
+ end
106
+
107
+ def info(args)
108
+ banner "Provider Site Information"
109
+ sites = args.split(/[,\s|\s|,]/)
110
+ sites.each do |site|
111
+ print_site(site)
112
+ end
113
+ puts
114
+ rescue
115
+ puts args + " doesn't appear to be configured, use list to see configured repositories."
116
+ end
117
+
118
+ def new
119
+ banner "Define New Harvesting Site"
120
+ name, site = form
121
+ @conf.sites[name] = site
122
+ @conf.save
123
+ end
124
+
125
+ def edit(name)
126
+ banner "Edit Harvesting Site"
127
+ name, site = form(name)
128
+ @conf.sites[name] = site
129
+ @conf.save
130
+ end
131
+
132
+ def remove(site)
133
+ if 'Y' == readline("Remove #{site}? (Y/N): ").upcase
134
+ @conf.sites.delete(site)
135
+ @conf.save
136
+ puts "#{site} removed"
137
+ end
138
+ end
139
+
140
+ # http://oai.getty.edu:80/oaicat/OAIHandler
141
+ def form(name = nil)
142
+ begin
143
+ if not name
144
+ name = prompt("nickname", nil)
145
+ while(@conf.sites.keys.include?(name))
146
+ show 0, "Nickname already in use, choose another."
147
+ name = prompt("nickname")
148
+ end
149
+ end
150
+ site = @conf.sites[name] || {}
151
+
152
+ # URL
153
+ url = prompt("url", site['url'])
154
+ while(not (site['url'] = verify(url)))
155
+ puts "Trouble contacting provider, bad url?"
156
+ url = prompt("url", site['url'])
157
+ end
158
+
159
+ # Metadata formats
160
+ formats = metadata(site['url'])
161
+ report "Repository supports [#{formats.join(', ')}] metadata formats."
162
+ prefix = prompt("prefix", site['prefix'])
163
+ while(not formats.include?(prefix))
164
+ prefix = prompt("prefix", site['prefix'])
165
+ end
166
+ site['prefix'] = prefix
167
+
168
+ # Sets
169
+ sets = ['all']
170
+ begin
171
+ sets.concat sets(site['url'])
172
+ site['set'] = 'all' unless site['set'] # default to all sets
173
+ report "Repository supports [#{sets.join(', ')}] metadata sets."
174
+ set = prompt("set", site['set'])
175
+ while(not sets.include?(site['set']))
176
+ set = prompt("set", site['set'])
177
+ end
178
+ site['set'] = set
179
+ rescue
180
+ site['set'] = 'all'
181
+ end
182
+
183
+ # Period
184
+ period = expand_period(prompt("period", "daily"))
185
+ while(not Config::PERIODS.include?(period))
186
+ puts "Must be daily, weekly, or monthly"
187
+ period = expand_period(prompt("period", "daily"))
188
+ end
189
+
190
+ site['period'] = period
191
+
192
+ return [name, site]
193
+ rescue
194
+ puts "Problem adding/updating provider, aborting. (#{$!})"
195
+ end
196
+ end
197
+
198
+ def config
199
+ begin
200
+ directory = prompt("storage directory", @conf.storage)
201
+ while not directory_acceptable(directory)
202
+ directory = prompt("storage directory: ", @conf.storage)
203
+ end
204
+
205
+ email = @conf.email.join(', ') rescue nil
206
+ @conf.email = parse_emails(prompt("email", email))
207
+
208
+ @conf.mail_server = prompt("mail server", @conf.mail_server)
209
+
210
+ logfile = prompt("log file(s) directory", @conf.logfile)
211
+ while not directory_acceptable(logfile)
212
+ logfile = prompt("log file(s) directory", @conf.logfile)
213
+ end
214
+ @conf.storage = directory
215
+ @conf.logfile = logfile
216
+ @conf.save
217
+ rescue
218
+ nil
219
+ end
220
+ end
221
+
222
+ def display(key, value, split = 40)
223
+ (split - key.size).times { print " " } if key.size < split
224
+ puts "#{key}: #{value}"
225
+ end
226
+
227
+ def banner(str)
228
+ puts "\n#{str}"
229
+ str.size.times { print "-" }
230
+ puts "\n"
231
+ end
232
+
233
+ def report(str)
234
+ puts "\n#{str}\n"
235
+ end
236
+
237
+ def indent(number)
238
+ number.times do
239
+ print "\t"
240
+ end
241
+ end
242
+
243
+ def prompt(text, default = nil, split = 20)
244
+ prompt_text = "#{text} [#{default}]: "
245
+ (split - prompt_text.size).times { print " " } if prompt_text.size < split
246
+ value = readline(prompt_text, true)
247
+ raise RuntimeError.new("Exit loop") unless value
248
+ return value.empty? ? default : value
249
+ end
250
+
251
+ def verify(url)
252
+ begin
253
+ client = OAI::Client.new(url, :redirects => false)
254
+ identify = client.identify
255
+ puts "Repository name \"#{identify.repository_name}\""
256
+ return url
257
+ rescue
258
+ if $!.to_s =~ /^Permanently Redirected to \[(.*)\?.*\]/
259
+ report "Provider redirected to: #{$1}"
260
+ verify($1)
261
+ else
262
+ puts "Error selecting repository: #{$!}"
263
+ end
264
+ end
265
+ end
266
+
267
+ def metadata(url)
268
+ formats = []
269
+ client = OAI::Client.new url
270
+ response = client.list_metadata_formats
271
+ response.to_a.each do |format|
272
+ formats << format.prefix
273
+ end
274
+ formats
275
+ end
276
+
277
+ def sets(url)
278
+ sets = []
279
+ client = OAI::Client.new url
280
+ response = client.list_sets
281
+ response.to_a.each do |set|
282
+ sets << set.spec
283
+ end
284
+ sets
285
+ end
286
+
287
+ def directory_acceptable(dir)
288
+ if not (dir && File.exists?(dir) && File.writable?(dir))
289
+ puts "Directory doesn't exist, or isn't writtable."
290
+ return false
291
+ end
292
+ true
293
+ end
294
+
295
+ def expand_period(str)
296
+ return str if Config::PERIODS.include?(str)
297
+ Config::PERIODS.each { |p| return p if p =~ /^#{str}/}
298
+ nil
299
+ end
300
+
301
+ def parse_emails(emails)
302
+ return nil unless emails
303
+ addresses = emails.split(/[,\s|\s|,]/)
304
+ end
305
+
306
+ def list_config
307
+ display("storage directory", @conf.storage, 20)
308
+ display("email", @conf.email.join(', '), 20) if @conf.email
309
+ display("mail server", @conf.mail_server, 20) if @conf.mail_server
310
+ display("log location", @conf.logfile, 20) if @conf.logfile
311
+ end
312
+
313
+ def list_sites
314
+ banner "Sites"
315
+ @conf.sites.each_key { |site| print_site(site) }
316
+ end
317
+
318
+ def print_site(site)
319
+ puts site
320
+ @conf.sites[site].each { |k,v| display(k, v, 15)}
321
+ end
322
+
323
+ def setup_cron
324
+ banner "Scheduling Automatic Harvesting"
325
+ puts "To activate automatic harvesting you must add an entry to"
326
+ puts "your scheduler. Linux/Mac OS X users should add the following"
327
+ puts "entry to their crontabs:\n\n"
328
+ puts "0 0 * * * #{$0} -D\n\n"
329
+ puts "Windows users should use WinAt to schedule"
330
+ puts "#{$0} to run every night.\n\n\n"
331
+ end
332
+
333
+ end
334
+
335
+ end
336
+ end
337
+
@@ -0,0 +1,43 @@
1
+ require 'time'
2
+ require 'zlib'
3
+ require 'net/smtp'
4
+ require 'yaml'
5
+ require 'tempfile'
6
+ require 'logger'
7
+ require 'fileutils'
8
+ require 'ostruct'
9
+ require 'readline'
10
+ require 'socket'
11
+
12
+ if not defined?(OAI::Const::VERBS)
13
+ require 'oai/constants'
14
+ end
15
+
16
+ require 'oai/client'
17
+ require 'oai/harvester/config'
18
+ require 'oai/harvester/harvest'
19
+ require 'oai/harvester/logging'
20
+ require 'oai/harvester/mailer'
21
+ require 'oai/harvester/shell'
22
+
23
+ def harvestable_sites(conf)
24
+ sites = []
25
+ conf.sites.each do |k, v|
26
+ sites << k if needs_updating(v['period'], v['last'])
27
+ end if conf.sites
28
+ sites
29
+ end
30
+
31
+ def needs_updating(period, last)
32
+ return true if last.nil?
33
+ case period
34
+ when 'daily'
35
+ return true if Time.now - last > 86000
36
+ when 'weekly'
37
+ return true if Time.now - last > 604000
38
+ when 'monthly'
39
+ return true if Time.now - last > 2591000
40
+ end
41
+ return false
42
+ end
43
+
@@ -0,0 +1,28 @@
1
+ module OAI::Provider::Metadata
2
+
3
+ # Simple implementation of the Dublin Core metadata format.
4
+ class DublinCore < Format
5
+
6
+ def initialize
7
+ @prefix = 'oai_dc'
8
+ @schema = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd'
9
+ @namespace = 'http://www.openarchives.org/OAI/2.0/oai_dc/'
10
+ @element_namespace = 'dc'
11
+ @fields = [ :title, :creator, :subject, :description, :publisher,
12
+ :contributor, :date, :type, :format, :identifier,
13
+ :source, :language, :relation, :coverage, :rights]
14
+ end
15
+
16
+ def header_specification
17
+ {
18
+ 'xmlns:oai_dc' => "http://www.openarchives.org/OAI/2.0/oai_dc/",
19
+ 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
20
+ 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
21
+ 'xsi:schemaLocation' =>
22
+ %{http://www.openarchives.org/OAI/2.0/oai_dc/
23
+ http://www.openarchives.org/OAI/2.0/oai_dc.xsd}.gsub(/\s+/, ' ')
24
+ }
25
+ end
26
+
27
+ end
28
+ end