bulkrax 9.1.0 → 9.2.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 +4 -4
- data/README.md +26 -0
- data/app/assets/javascripts/bulkrax/importers.js.erb +4 -1
- data/app/factories/bulkrax/object_factory.rb +35 -1
- data/app/factories/bulkrax/object_factory_interface.rb +26 -0
- data/app/factories/bulkrax/valkyrie_object_factory.rb +44 -3
- data/app/jobs/bulkrax/delete_job.rb +11 -0
- data/app/models/bulkrax/csv_entry.rb +41 -10
- data/app/models/concerns/bulkrax/export_behavior.rb +28 -15
- data/app/models/concerns/bulkrax/file_set_entry_behavior.rb +13 -4
- data/app/models/concerns/bulkrax/importer_exporter_behavior.rb +1 -1
- data/app/parsers/bulkrax/application_parser.rb +5 -2
- data/app/parsers/bulkrax/csv_parser.rb +15 -4
- data/app/parsers/bulkrax/xml_parser.rb +1 -1
- data/app/services/bulkrax/factory_class_finder.rb +56 -15
- data/app/services/wings/custom_queries/find_by_source_identifier.rb +8 -2
- data/app/views/bulkrax/entries/show.html.erb +14 -10
- data/lib/bulkrax/version.rb +1 -1
- data/lib/bulkrax.rb +6 -11
- metadata +8 -3
- data/app/factories/bulkrax/valkyrize-hyku.code-workspace +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb04fb1689c90c0cb7b96d0fbab95f371e4e4718509638812e62b0bffa1cc9e0
|
4
|
+
data.tar.gz: b3df0b413a151f7c49c6a39e07a705ec0e8c87d181a66de2a0ae75d567297f08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89a766b0116729946c1ffba1252362fff3f29fd57ab774aebab39fa5b6f2b207a511de3da801b47bce7352a8ef2ca2e3811f306ca0f701ef592acd2a66103f34
|
7
|
+
data.tar.gz: b559f0441c55c23a64568d95d1bcb533dc5b11cf12c9ad8aaed3ab852a0f9ea88b1e9140968b6272af54d05d2edae3ff3dadb5433e07fa6c55d90e615f42447d
|
data/README.md
CHANGED
@@ -208,6 +208,32 @@ We encourage everyone to help improve this project. Bug reports and pull reques
|
|
208
208
|
|
209
209
|
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://contributor-covenant.org) code of conduct.
|
210
210
|
|
211
|
+
### Running tests
|
212
|
+
- The tests use sqlite as the database, so no extra dependencies are required.
|
213
|
+
- Ensure you are using a supported version of Ruby (`ruby -v` should be greater or equal to 2.7)
|
214
|
+
- If you are using a ruby version earlier than 3.0.0, install bundler explicitly (newer versions of ruby will automatically install an appropriate bundler version)
|
215
|
+
```bash
|
216
|
+
/path/to/ruby/install/bin/gem install bundler -v '~> 2.4.0'
|
217
|
+
```
|
218
|
+
- Decide on your version of Hyrax to test against and export it to your environment, then bundle install. The Hyrax version should be greater or equal to 2.3.
|
219
|
+
```bash
|
220
|
+
export HYRAX_VERSION="~> 4.0.0"
|
221
|
+
bundle install
|
222
|
+
```
|
223
|
+
- Run the test migrations
|
224
|
+
```bash
|
225
|
+
bundle exec bin/rails db:migrate RAILS_ENV=test
|
226
|
+
```
|
227
|
+
- Run the tests
|
228
|
+
```bash
|
229
|
+
bundle exec rspec
|
230
|
+
```
|
231
|
+
|
232
|
+
- Run the style checker / linter
|
233
|
+
```bash
|
234
|
+
bundle exec rubocop
|
235
|
+
```
|
236
|
+
|
211
237
|
## Questions
|
212
238
|
Questions can be sent to support@notch8.com. Please make sure to include "Bulkrax" in the subject line of your email.
|
213
239
|
|
@@ -15,7 +15,10 @@ function prepBulkrax(event) {
|
|
15
15
|
// Initialize the uploader only if hyraxUploader is defined
|
16
16
|
if (typeof $.fn.hyraxUploader === 'function') {
|
17
17
|
// Initialize the uploader
|
18
|
-
$('.fileupload-bulkrax').hyraxUploader({
|
18
|
+
$('.fileupload-bulkrax').hyraxUploader({
|
19
|
+
maxNumberOfFiles: 1,
|
20
|
+
maxFileSize: <%= (defined?(Hyrax) && Hyrax.config.uploader[:maxFileSize]) || 524288000 %>
|
21
|
+
});
|
19
22
|
|
20
23
|
// Function to toggle 'required' attribute based on uploaded files
|
21
24
|
function toggleRequiredAttribute() {
|
@@ -28,6 +28,40 @@ module Bulkrax
|
|
28
28
|
resource.file_sets.each(&:update_index) if resource.respond_to?(:file_sets)
|
29
29
|
end
|
30
30
|
|
31
|
+
##
|
32
|
+
# @return [String] the name of the model class for the given resource/object.
|
33
|
+
def self.model_name(resource:)
|
34
|
+
resource.has_model.first
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# A thumbnail is linked to a work rather than the file set itself.
|
39
|
+
# @return [File or FileMetadata] the thumbnail file for the given resource
|
40
|
+
def self.thumbnail_for(resource:)
|
41
|
+
return nil unless resource.respond_to?(:thumbnail)
|
42
|
+
return resource.thumbnail if resource.thumbnail.present?
|
43
|
+
return nil unless resource.respond_to?(:parent) && resource.parent.present?
|
44
|
+
return nil unless resource.parent.respond_to?(:thumbnail)
|
45
|
+
resource.parent.thumbnail
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# @input [Fileset]
|
50
|
+
# @return [File] the original file.
|
51
|
+
def self.original_file(fileset:)
|
52
|
+
fileset.try(:original_file)
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# #input [Fileset or FileMetadata]
|
57
|
+
# @return [String] the file name for the given fileset
|
58
|
+
def self.filename_for(fileset:)
|
59
|
+
file = original_file(fileset: fileset)
|
60
|
+
file.file_name.first
|
61
|
+
rescue NoMethodError
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
31
65
|
##
|
32
66
|
# @see Bulkrax::ObjectFactoryInterface
|
33
67
|
def self.export_properties
|
@@ -166,7 +200,7 @@ module Bulkrax
|
|
166
200
|
|
167
201
|
def delete(_user)
|
168
202
|
obj = find
|
169
|
-
|
203
|
+
raise ObjectFactoryInterface::ObjectNotFoundError, "Object not found to delete" unless obj
|
170
204
|
|
171
205
|
obj.delete(eradicate: true)
|
172
206
|
end
|
@@ -209,6 +209,32 @@ module Bulkrax
|
|
209
209
|
end
|
210
210
|
# rubocop:enable Metrics/ParameterLists
|
211
211
|
|
212
|
+
##
|
213
|
+
# @return [String] the name of the model class for the given resource/object.
|
214
|
+
def self.model_name(resource:)
|
215
|
+
raise NotImplementedError, "#{self}.#{__method__}"
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# @return [File or FileMetadata] the thumbnail file for the given resource
|
220
|
+
def self.thumbnail_for(resource:)
|
221
|
+
raise NotImplementedError, "#{self}.#{__method__}"
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# @input [Fileset or FileMetadata]
|
226
|
+
# @return [File or FileMetadata] the original file
|
227
|
+
def self.original_file(fileset:)
|
228
|
+
raise NotImplementedError, "#{self}.#{__method__}"
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# #input [Fileset or FileMetadata]
|
233
|
+
# @return [String] the file name for the given fileset
|
234
|
+
def self.filename_for(fileset:)
|
235
|
+
raise NotImplementedError, "#{self}.#{__method__}"
|
236
|
+
end
|
237
|
+
|
212
238
|
##
|
213
239
|
# @api private
|
214
240
|
#
|
@@ -155,7 +155,7 @@ module Bulkrax
|
|
155
155
|
Hyrax.query_service.find_by(id: id)
|
156
156
|
# Because Hyrax is not a hard dependency, we need to transform the Hyrax exception into a
|
157
157
|
# common exception so that callers can handle a generalize exception.
|
158
|
-
rescue Hyrax::ObjectNotFoundError => e
|
158
|
+
rescue Hyrax::ObjectNotFoundError, Valkyrie::Persistence::ObjectNotFoundError => e
|
159
159
|
raise ObjectFactoryInterface::ObjectNotFoundError, e.message
|
160
160
|
end
|
161
161
|
|
@@ -210,6 +210,46 @@ module Bulkrax
|
|
210
210
|
update_index(resources: file_sets)
|
211
211
|
end
|
212
212
|
|
213
|
+
##
|
214
|
+
# If we always want the valkyrized resource name, even for unmigrated objects, we can
|
215
|
+
# simply use resource.model_name.name. At this point, we are differentiating
|
216
|
+
# to help identify items which have been migrated to Valkyrie vs those which have not.
|
217
|
+
#
|
218
|
+
# @return [String] the name of the model class for the given resource/object.
|
219
|
+
def self.model_name(resource:)
|
220
|
+
resource.class.to_s
|
221
|
+
end
|
222
|
+
|
223
|
+
##
|
224
|
+
# @return [File or FileMetadata] the thumbnail file for the given resource
|
225
|
+
def self.thumbnail_for(resource:)
|
226
|
+
# recursive call to parent if resource is a fileset - we want the work's thumbnail
|
227
|
+
return thumbnail_for(resource: resource&.parent) if resource.is_a?(Bulkrax.file_model_class)
|
228
|
+
|
229
|
+
return nil unless resource.respond_to?(:thumbnail_id) && resource.thumbnail_id.present?
|
230
|
+
Bulkrax.object_factory.find(resource.thumbnail_id.to_s)
|
231
|
+
rescue Bulkrax::ObjectFactoryInterface::ObjectNotFoundError
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
|
235
|
+
##
|
236
|
+
# @input [Fileset or FileMetadata]
|
237
|
+
# @return [FileMetadata] the original file
|
238
|
+
def self.original_file(fileset:)
|
239
|
+
fileset.try(:original_file) || fileset
|
240
|
+
end
|
241
|
+
|
242
|
+
##
|
243
|
+
# #input [Fileset or FileMetadata]
|
244
|
+
# @return [String] the file name for the given fileset
|
245
|
+
def self.filename_for(fileset:)
|
246
|
+
file = original_file(fileset: fileset)
|
247
|
+
return nil unless file
|
248
|
+
file.original_filename
|
249
|
+
rescue NoMethodError
|
250
|
+
nil
|
251
|
+
end
|
252
|
+
|
213
253
|
##
|
214
254
|
# @param value [String]
|
215
255
|
# @param klass [Class, #where]
|
@@ -252,7 +292,7 @@ module Bulkrax
|
|
252
292
|
|
253
293
|
def delete(user)
|
254
294
|
obj = find
|
255
|
-
|
295
|
+
raise ObjectFactoryInterface::ObjectNotFoundError, "Object not found to delete" unless obj
|
256
296
|
|
257
297
|
Hyrax.persister.delete(resource: obj)
|
258
298
|
Hyrax.index_adapter.delete(resource: obj)
|
@@ -292,6 +332,7 @@ module Bulkrax
|
|
292
332
|
|
293
333
|
def create_file_set(attrs)
|
294
334
|
# TODO: Make it work for Valkyrie
|
335
|
+
raise NotImplementedError, __method__.to_s
|
295
336
|
end
|
296
337
|
|
297
338
|
def create_work(attrs)
|
@@ -381,7 +422,7 @@ module Bulkrax
|
|
381
422
|
end
|
382
423
|
|
383
424
|
def find_by_id
|
384
|
-
find(
|
425
|
+
self.class.find(attributes[:id]) if attributes.key? :id
|
385
426
|
end
|
386
427
|
|
387
428
|
##
|
@@ -6,6 +6,17 @@ module Bulkrax
|
|
6
6
|
|
7
7
|
def perform(entry, importer_run)
|
8
8
|
user = importer_run.importer.user
|
9
|
+
|
10
|
+
# When we delete, we don't go through the build process.
|
11
|
+
# However, we need the identifier to be set for the entry.
|
12
|
+
# This enables us to delete based on the ID, not just the source_identifier.
|
13
|
+
if entry.respond_to?(:build_metadata_for_delete) &&
|
14
|
+
entry.parsed_metadata.nil? &&
|
15
|
+
entry.raw_metadata.present?
|
16
|
+
entry.build_metadata_for_delete
|
17
|
+
entry.save!
|
18
|
+
end
|
19
|
+
|
9
20
|
entry.factory.delete(user)
|
10
21
|
|
11
22
|
# rubocop:disable Rails/SkipsModelValidations
|
@@ -5,6 +5,23 @@ module Bulkrax
|
|
5
5
|
# We do too much in these entry classes. We need to extract the common logic from the various
|
6
6
|
# entry models into a module that can be shared between them.
|
7
7
|
class CsvEntry < Entry # rubocop:disable Metrics/ClassLength
|
8
|
+
class CsvPathError < StandardError
|
9
|
+
def initialize(message)
|
10
|
+
super(message)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class RecordNotFound < StandardError
|
15
|
+
def initialize(message)
|
16
|
+
super(message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class MissingMetadata < StandardError
|
21
|
+
def initialize(message)
|
22
|
+
super(message)
|
23
|
+
end
|
24
|
+
end
|
8
25
|
serialize :raw_metadata, Bulkrax::NormalizedJson
|
9
26
|
|
10
27
|
def self.fields_from_data(data)
|
@@ -16,7 +33,7 @@ module Bulkrax
|
|
16
33
|
# there's a risk that this reads the whole file into memory and could cause a memory leak
|
17
34
|
# we strip any special characters out of the headers. looking at you Excel
|
18
35
|
def self.read_data(path)
|
19
|
-
raise
|
36
|
+
raise CsvPathError, 'CSV path empty' if path.blank?
|
20
37
|
options = {
|
21
38
|
headers: true,
|
22
39
|
header_converters: ->(h) { h.to_s.gsub(/[^\w\d\. -]+/, '').strip.to_sym },
|
@@ -85,10 +102,18 @@ module Bulkrax
|
|
85
102
|
self.parsed_metadata
|
86
103
|
end
|
87
104
|
|
105
|
+
# limited metadata is needed for delete jobs
|
106
|
+
def build_metadata_for_delete
|
107
|
+
self.parsed_metadata = {}
|
108
|
+
establish_factory_class
|
109
|
+
add_ingested_metadata
|
110
|
+
self.parsed_metadata
|
111
|
+
end
|
112
|
+
|
88
113
|
def validate_record
|
89
|
-
raise
|
114
|
+
raise RecordNotFound, 'Record not found' if record.nil?
|
90
115
|
unless importerexporter.parser.required_elements?(record)
|
91
|
-
raise
|
116
|
+
raise MissingMetadata, "Missing required elements, missing element(s) are: "\
|
92
117
|
"#{importerexporter.parser.missing_elements(record).join(', ')}"
|
93
118
|
end
|
94
119
|
end
|
@@ -160,7 +185,7 @@ module Bulkrax
|
|
160
185
|
source_id = source_id.to_a if source_id.is_a?(ActiveTriples::Relation)
|
161
186
|
source_id = Array.wrap(source_id).first
|
162
187
|
self.parsed_metadata[source_identifier] = source_id
|
163
|
-
model_name =
|
188
|
+
model_name = Bulkrax.object_factory.model_name(resource: hyrax_record)
|
164
189
|
self.parsed_metadata[key_for_export('model')] = model_name
|
165
190
|
end
|
166
191
|
|
@@ -179,9 +204,13 @@ module Bulkrax
|
|
179
204
|
|
180
205
|
def build_relationship_metadata
|
181
206
|
# Includes all relationship methods for all exportable record types (works, Collections, FileSets)
|
207
|
+
# @TODO: this logic assumes that the relationships are all available via a method that can be called
|
208
|
+
# on the object. With Valkyrie, this is only true for Hyrax-based models which include the
|
209
|
+
# ArResource module. We need to consider reworking this logic into an object factory method
|
210
|
+
# that can handle different types of models.
|
182
211
|
relationship_methods = {
|
183
|
-
related_parents_parsed_mapping => %i[member_of_collection_ids member_of_work_ids in_work_ids],
|
184
|
-
related_children_parsed_mapping => %i[member_collection_ids member_work_ids file_set_ids]
|
212
|
+
related_parents_parsed_mapping => %i[member_of_collection_ids member_of_work_ids in_work_ids parent],
|
213
|
+
related_children_parsed_mapping => %i[member_collection_ids member_work_ids file_set_ids member_ids]
|
185
214
|
}
|
186
215
|
|
187
216
|
relationship_methods.each do |relationship_key, methods|
|
@@ -189,7 +218,9 @@ module Bulkrax
|
|
189
218
|
|
190
219
|
values = []
|
191
220
|
methods.each do |m|
|
192
|
-
|
221
|
+
value = hyrax_record.public_send(m) if hyrax_record.respond_to?(m)
|
222
|
+
value_id = value.try(:id)&.to_s || value # get the id if it's an object
|
223
|
+
values << value_id if value_id.present?
|
193
224
|
end
|
194
225
|
values = values.flatten.uniq
|
195
226
|
next if values.blank?
|
@@ -316,11 +347,11 @@ module Bulkrax
|
|
316
347
|
|
317
348
|
def build_thumbnail_files
|
318
349
|
return unless importerexporter.include_thumbnails
|
350
|
+
thumbnail = Bulkrax.object_factory.thumbnail_for(resource: hyrax_record)
|
351
|
+
return unless thumbnail
|
319
352
|
|
353
|
+
filenames = map_file_sets(Array.wrap(thumbnail))
|
320
354
|
thumbnail_mapping = 'thumbnail_file'
|
321
|
-
file_sets = Array.wrap(hyrax_record.thumbnail)
|
322
|
-
|
323
|
-
filenames = map_file_sets(file_sets)
|
324
355
|
handle_join_on_export(thumbnail_mapping, filenames, false)
|
325
356
|
end
|
326
357
|
|
@@ -26,25 +26,38 @@ module Bulkrax
|
|
26
26
|
|
27
27
|
# Prepend the file_set id to ensure a unique filename and also one that is not longer than 255 characters
|
28
28
|
def filename(file_set)
|
29
|
-
return if
|
30
|
-
if
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
# return if there are no files on the fileset
|
30
|
+
return if Bulkrax.object_factory.original_file(fileset: file_set).blank?
|
31
|
+
|
32
|
+
fn = Bulkrax.object_factory.filename_for(fileset: file_set)
|
33
|
+
file = Bulkrax.object_factory.original_file(fileset: file_set)
|
34
|
+
ext = file_extension(file: file, filename: fn)
|
35
|
+
|
36
|
+
# Prepend the file_set id to ensure a unique filename
|
37
|
+
filename = File.basename(fn, ".*")
|
38
|
+
# Skip modification if file already has ID or we're in metadata-only mode
|
38
39
|
if fn.include?(file_set.id) || importerexporter.metadata_only?
|
39
|
-
filename
|
40
|
-
filename = fn if mime.to_s == ext_mime.to_s
|
40
|
+
# keep filename as is
|
41
41
|
else
|
42
|
-
filename = "#{file_set.id}_#{
|
43
|
-
filename = "#{file_set.id}_#{fn}" if mime.to_s == ext_mime.to_s
|
42
|
+
filename = "#{file_set.id}_#{filename}"
|
44
43
|
end
|
45
|
-
|
46
|
-
|
44
|
+
filename = ext.present? ? "#{filename}.#{ext}" : fn
|
45
|
+
|
46
|
+
# Remove extension, truncate and reattach
|
47
47
|
"#{File.basename(filename, ext)[0...(220 - ext.length)]}#{ext}"
|
48
48
|
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Generate the appropriate file extension based on the mime type of the file
|
52
|
+
# @return [String] the file extension for the given file
|
53
|
+
def file_extension(file:, filename:)
|
54
|
+
declared_mime = ::Marcel::MimeType.for(declared_type: file.mime_type)
|
55
|
+
# validate the declared mime type
|
56
|
+
declared_mime = ::Marcel::MimeType.for(name: filename) if declared_mime.nil? || declared_mime == "application/octet-stream"
|
57
|
+
# convert the mime type to a file extension
|
58
|
+
Mime::Type.lookup(declared_mime).symbol.to_s
|
59
|
+
rescue Mime::Type::InvalidMimeType
|
60
|
+
nil
|
61
|
+
end
|
49
62
|
end
|
50
63
|
end
|
@@ -2,6 +2,15 @@
|
|
2
2
|
|
3
3
|
module Bulkrax
|
4
4
|
module FileSetEntryBehavior
|
5
|
+
class FileNameError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class OrphanFileSetError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class FilePathError < StandardError
|
12
|
+
end
|
13
|
+
|
5
14
|
extend ActiveSupport::Concern
|
6
15
|
|
7
16
|
included do
|
@@ -21,11 +30,11 @@ module Bulkrax
|
|
21
30
|
|
22
31
|
path_to_file = parser.path_to_files(filename: filename)
|
23
32
|
|
24
|
-
parsed_metadata['file'][i] = path_to_file
|
33
|
+
parsed_metadata['file'][i] = path_to_file if path_to_file.present?
|
25
34
|
end
|
26
35
|
parsed_metadata['file'].delete('')
|
27
36
|
|
28
|
-
raise
|
37
|
+
raise FilePathError, "one or more file paths are invalid: #{parsed_metadata['file'].join(', ')}" unless parsed_metadata['file'].map { |file_path| ::File.file?(file_path) }.all?
|
29
38
|
|
30
39
|
parsed_metadata['file']
|
31
40
|
end
|
@@ -33,13 +42,13 @@ module Bulkrax
|
|
33
42
|
def validate_presence_of_filename!
|
34
43
|
return if parsed_metadata&.[](file_reference)&.map(&:present?)&.any?
|
35
44
|
|
36
|
-
raise
|
45
|
+
raise FileNameError, 'File set must have a filename'
|
37
46
|
end
|
38
47
|
|
39
48
|
def validate_presence_of_parent!
|
40
49
|
return if parsed_metadata[related_parents_parsed_mapping]&.map(&:present?)&.any?
|
41
50
|
|
42
|
-
raise
|
51
|
+
raise OrphanFileSetError, 'File set must be related to at least one work'
|
43
52
|
end
|
44
53
|
|
45
54
|
def parent_jobs
|
@@ -56,7 +56,7 @@ module Bulkrax
|
|
56
56
|
|
57
57
|
returning_value = false
|
58
58
|
File.open(filename) do |file|
|
59
|
-
mime_type = ::Marcel::MimeType.for(file)
|
59
|
+
mime_type = ::Marcel::MimeType.for(name: file)
|
60
60
|
returning_value = mime_type.include?('application/zip') || mime_type.include?('application/gzip')
|
61
61
|
end
|
62
62
|
returning_value
|
@@ -209,8 +209,11 @@ module Bulkrax
|
|
209
209
|
def rebuild_entries(types_array = nil)
|
210
210
|
index = 0
|
211
211
|
(types_array || %w[collection work file_set relationship]).each do |type|
|
212
|
-
# works are not
|
213
|
-
|
212
|
+
# works are not guaranteed to have Work in the type
|
213
|
+
if type.eql?('relationship')
|
214
|
+
ScheduleRelationshipsJob.set(wait: 5.minutes).perform_later(importer_id: importerexporter.id)
|
215
|
+
next
|
216
|
+
end
|
214
217
|
importer.entries.where(rebuild_entry_query(type, parser_fields['entry_statuses'])).find_each do |e|
|
215
218
|
seen[e.identifier] = true
|
216
219
|
e.status_info('Pending', importer.current_run)
|
@@ -38,7 +38,7 @@ module Bulkrax
|
|
38
38
|
# We aren't right now because so many Bulkrax users are in between Fedora and Valkyrie
|
39
39
|
if model.casecmp('collection').zero? || model.casecmp('collectionresource').zero?
|
40
40
|
@collections << r
|
41
|
-
elsif model.casecmp('fileset').zero?
|
41
|
+
elsif model.casecmp('fileset').zero? || model.casecmp('hyrax::fileset').zero?
|
42
42
|
@file_sets << r
|
43
43
|
else
|
44
44
|
@works << r
|
@@ -248,14 +248,22 @@ module Bulkrax
|
|
248
248
|
if file_sets.nil? # for valkyrie
|
249
249
|
file_sets = record.respond_to?(:file_sets) ? record.file_sets : record.members&.select(&:file_set?)
|
250
250
|
end
|
251
|
-
|
251
|
+
|
252
|
+
if importerexporter.include_thumbnails?
|
253
|
+
thumbnail = Bulkrax.object_factory.thumbnail_for(resource: record)
|
254
|
+
file_sets << thumbnail if thumbnail.present?
|
255
|
+
end
|
256
|
+
|
252
257
|
file_sets.each do |fs|
|
253
258
|
path = File.join(exporter_export_path, folder_count, 'files')
|
254
259
|
FileUtils.mkdir_p(path) unless File.exist? path
|
260
|
+
|
261
|
+
original_file = Bulkrax.object_factory.original_file(fileset: fs)
|
262
|
+
next if original_file.blank?
|
255
263
|
file = filename(fs)
|
256
|
-
next if file.blank? || fs.original_file.blank?
|
257
264
|
|
258
|
-
io =
|
265
|
+
io = original_file.respond_to?(:uri) ? open(original_file.uri) : original_file.file.io
|
266
|
+
|
259
267
|
File.open(File.join(path, file), 'wb') do |f|
|
260
268
|
f.write(io.read)
|
261
269
|
f.close
|
@@ -263,6 +271,8 @@ module Bulkrax
|
|
263
271
|
end
|
264
272
|
rescue Ldp::Gone
|
265
273
|
return
|
274
|
+
rescue StandardError => e
|
275
|
+
raise StandardError, "Unable to retrieve files for identifier #{identifier} - #{e.message}"
|
266
276
|
end
|
267
277
|
|
268
278
|
def export_key_allowed(key)
|
@@ -372,6 +382,7 @@ module Bulkrax
|
|
372
382
|
|
373
383
|
return @path_to_files if File.exist?(@path_to_files)
|
374
384
|
|
385
|
+
# TODO: This method silently returns nil if there is no file & no zip file
|
375
386
|
File.join(importer_unzip_path, 'files', filename) if file? && zip?
|
376
387
|
end
|
377
388
|
|
@@ -103,7 +103,7 @@ module Bulkrax
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def good_file_type?(path)
|
106
|
-
%w[.xml .xls .xsd].include?(File.extname(path)) || ::Marcel::MimeType.for(path).include?('application/xml')
|
106
|
+
%w[.xml .xls .xsd].include?(File.extname(path)) || ::Marcel::MimeType.for(path: path).include?('application/xml')
|
107
107
|
end
|
108
108
|
|
109
109
|
def total
|
@@ -66,27 +66,68 @@ module Bulkrax
|
|
66
66
|
entry.default_work_type.constantize
|
67
67
|
end
|
68
68
|
|
69
|
+
private
|
70
|
+
|
69
71
|
##
|
70
72
|
# @api private
|
71
73
|
# @return [String]
|
72
74
|
def name
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
Array.wrap(entry.parsed_metadata['work_type']).first
|
79
|
-
else
|
80
|
-
entry.default_work_type
|
81
|
-
end
|
82
|
-
|
83
|
-
# Let's coerce this into the right shape; we're not mutating the string because it might well
|
84
|
-
# be frozen.
|
85
|
-
fc = fc.tr(' ', '_')
|
86
|
-
fc = fc.downcase if fc.match?(/[-_]/)
|
87
|
-
fc.camelcase
|
75
|
+
# Try each strategy in order until one returns a value
|
76
|
+
fc = find_factory_class_name || entry.default_work_type
|
77
|
+
|
78
|
+
# Normalize the string format
|
79
|
+
normalize_class_name(fc)
|
88
80
|
rescue
|
89
81
|
entry.default_work_type
|
90
82
|
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Try each strategy in sequence to find a factory class name
|
86
|
+
# @return [String, nil] the factory class name or nil if none found
|
87
|
+
def find_factory_class_name
|
88
|
+
prioritized_strategies = [
|
89
|
+
:model_from_parsed_metadata,
|
90
|
+
:work_type_from_parsed_metadata,
|
91
|
+
:model_from_raw_metadata,
|
92
|
+
:model_from_mapped_field
|
93
|
+
]
|
94
|
+
|
95
|
+
# Return the first non-nil result
|
96
|
+
prioritized_strategies.each do |strategy|
|
97
|
+
result = send(strategy)
|
98
|
+
return result if result.present?
|
99
|
+
end
|
100
|
+
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def model_from_parsed_metadata
|
105
|
+
Array.wrap(entry.parsed_metadata['model']).first if entry.parsed_metadata&.[]('model').present?
|
106
|
+
end
|
107
|
+
|
108
|
+
def work_type_from_parsed_metadata
|
109
|
+
Array.wrap(entry.parsed_metadata['work_type']).first if entry.importerexporter&.mapping&.[]('work_type').present?
|
110
|
+
end
|
111
|
+
|
112
|
+
def model_from_raw_metadata
|
113
|
+
Array.wrap(entry.raw_metadata&.[]('model'))&.first if entry.raw_metadata&.[]('model').present?
|
114
|
+
end
|
115
|
+
|
116
|
+
def model_from_mapped_field
|
117
|
+
return nil unless entry.parser.model_field_mappings.any? { |field| entry.raw_metadata&.[](field).present? }
|
118
|
+
field = entry.parser.model_field_mappings.find { |f| entry.raw_metadata&.[](f).present? }
|
119
|
+
Array.wrap(entry.raw_metadata[field]).first
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Normalize a class name string to proper format
|
124
|
+
# @param name [String] the class name to normalize
|
125
|
+
# @return [String] the normalized class name
|
126
|
+
def normalize_class_name(name)
|
127
|
+
name = name.to_s
|
128
|
+
name = name.tr(' ', '_')
|
129
|
+
name = name.downcase if name.match?(/[-_]/)
|
130
|
+
name.camelcase
|
131
|
+
end
|
91
132
|
end
|
92
133
|
end
|
@@ -22,8 +22,14 @@ module Wings
|
|
22
22
|
# NOTE: This is using the Bulkrax::ObjectFactory (e.g. the one envisioned for ActiveFedora).
|
23
23
|
# In doing this, we avoid the situation where Bulkrax::ValkyrieObjectFactory calls this custom query.
|
24
24
|
|
25
|
-
# This is doing a solr search so we have to use the search_field instead of the property
|
26
|
-
|
25
|
+
# This is doing a solr search first, so we have to use the search_field instead of the property for "field"
|
26
|
+
# If it cannot find the object in Solr, it falls back to an ActiveFedora search, if the object is an ActiveFedora object
|
27
|
+
af_object = Bulkrax::ObjectFactory.search_by_property(
|
28
|
+
value: value,
|
29
|
+
klass: ActiveFedora::Base,
|
30
|
+
field: search_field,
|
31
|
+
name_field: property
|
32
|
+
)
|
27
33
|
|
28
34
|
return if af_object.blank?
|
29
35
|
return af_object unless use_valkyrie
|
@@ -34,18 +34,22 @@
|
|
34
34
|
<p class='bulkrax-p-align'>
|
35
35
|
<% if @importer.present? %>
|
36
36
|
<%# TODO Consider how to account for Bulkrax.collection_model_class %>
|
37
|
-
<%
|
38
|
-
|
39
|
-
<%
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
<% begin %>
|
38
|
+
<% factory_record = @entry.factory.find rescue nil %>
|
39
|
+
<% if factory_record.present? %>
|
40
|
+
<% factory_record_class = factory_record.class %>
|
41
|
+
<% factory_record_class_human = factory_record_class.model_name.human %>
|
42
|
+
<strong><%= factory_record_class_human %> Link:</strong>
|
43
|
+
<% if defined?(Hyrax) && factory_record_class_human == 'Collection' %>
|
44
|
+
<%= link_to factory_record_class_human, hyrax.polymorphic_path(factory_record) %>
|
45
|
+
<% else %>
|
46
|
+
<%= link_to factory_record_class_human, main_app.polymorphic_path(factory_record) %>
|
47
|
+
<% end %>
|
44
48
|
<% else %>
|
45
|
-
|
49
|
+
<strong>Item Link:</strong> Item has not yet been imported successfully
|
46
50
|
<% end %>
|
47
|
-
<%
|
48
|
-
<strong>Item Link:</strong>
|
51
|
+
<% rescue => e %>
|
52
|
+
<strong>Item Link:</strong> Unable to retrieve item (<%= e.message %>)
|
49
53
|
<% end %>
|
50
54
|
<% else %>
|
51
55
|
<% record = @entry&.hyrax_record %>
|
data/lib/bulkrax/version.rb
CHANGED
data/lib/bulkrax.rb
CHANGED
@@ -107,11 +107,8 @@ module Bulkrax
|
|
107
107
|
# Hyrax::FileSet.try(:internal_resource) || 'hi'
|
108
108
|
# => #<Dry::Types::Result::Failure input=:internal_resource error=...
|
109
109
|
# ```
|
110
|
-
|
111
|
-
|
112
|
-
else
|
113
|
-
collection_model_class.to_s
|
114
|
-
end
|
110
|
+
instance = collection_model_class.new
|
111
|
+
instance.respond_to?(:internal_resource) ? instance.internal_resource : collection_model_class.to_s
|
115
112
|
end
|
116
113
|
|
117
114
|
def file_model_class
|
@@ -130,11 +127,8 @@ module Bulkrax
|
|
130
127
|
# Hyrax::FileSet.try(:internal_resource) || 'hi'
|
131
128
|
# => #<Dry::Types::Result::Failure input=:internal_resource error=...
|
132
129
|
# ```
|
133
|
-
|
134
|
-
|
135
|
-
else
|
136
|
-
file_model_class.to_s
|
137
|
-
end
|
130
|
+
instance = file_model_class.new
|
131
|
+
instance.respond_to?(:internal_resource) ? instance.internal_resource : file_model_class.to_s
|
138
132
|
end
|
139
133
|
|
140
134
|
def curation_concerns
|
@@ -154,7 +148,8 @@ module Bulkrax
|
|
154
148
|
# Hyrax::FileSet.try(:internal_resource) || 'hi'
|
155
149
|
# => #<Dry::Types::Result::Failure input=:internal_resource error=...
|
156
150
|
# ```
|
157
|
-
|
151
|
+
instance = cc.new
|
152
|
+
instance.respond_to?(:internal_resource) ? instance.internal_resource : cc.to_s
|
158
153
|
end.uniq
|
159
154
|
end
|
160
155
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bulkrax
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 9.
|
4
|
+
version: 9.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Kaufman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -17,6 +17,9 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 5.1.6
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 7.0.0
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -24,6 +27,9 @@ dependencies:
|
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: 5.1.6
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 7.0.0
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: bagit
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -345,7 +351,6 @@ files:
|
|
345
351
|
- app/factories/bulkrax/object_factory.rb
|
346
352
|
- app/factories/bulkrax/object_factory_interface.rb
|
347
353
|
- app/factories/bulkrax/valkyrie_object_factory.rb
|
348
|
-
- app/factories/bulkrax/valkyrize-hyku.code-workspace
|
349
354
|
- app/helpers/bulkrax/application_helper.rb
|
350
355
|
- app/helpers/bulkrax/exporters_helper.rb
|
351
356
|
- app/helpers/bulkrax/importers_helper.rb
|
@@ -1,19 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"folders": [
|
3
|
-
{
|
4
|
-
"path": "../../../../../hyku"
|
5
|
-
},
|
6
|
-
{
|
7
|
-
"path": "../../../../hyrax"
|
8
|
-
},
|
9
|
-
{
|
10
|
-
"path": "../../.."
|
11
|
-
},
|
12
|
-
{
|
13
|
-
"path": "../../../../iiif_print"
|
14
|
-
}
|
15
|
-
],
|
16
|
-
"settings": {
|
17
|
-
"github.copilot.inlineSuggest.enable": true
|
18
|
-
}
|
19
|
-
}
|