sdr-client 2.12.0 → 2.13.0.beta1
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/.rubocop.yml +3 -0
- data/.rubocop_todo.yml +24 -20
- data/Gemfile.lock +11 -8
- data/exe/sdr_redesigned +10 -0
- data/lib/sdr_client/redesigned_client/authenticator.rb +40 -0
- data/lib/sdr_client/redesigned_client/cli/config.rb +32 -0
- data/lib/sdr_client/redesigned_client/cli/credentials.rb +35 -0
- data/lib/sdr_client/redesigned_client/cli/update.rb +186 -0
- data/lib/sdr_client/redesigned_client/cli.rb +198 -0
- data/lib/sdr_client/redesigned_client/create_resource.rb +71 -0
- data/lib/sdr_client/redesigned_client/deposit.rb +115 -0
- data/lib/sdr_client/redesigned_client/direct_upload_request.rb +45 -0
- data/lib/sdr_client/redesigned_client/direct_upload_response.rb +9 -0
- data/lib/sdr_client/redesigned_client/file.rb +100 -0
- data/lib/sdr_client/redesigned_client/file_set.rb +53 -0
- data/lib/sdr_client/redesigned_client/file_type_file_set_strategy.rb +13 -0
- data/lib/sdr_client/redesigned_client/find.rb +42 -0
- data/lib/sdr_client/redesigned_client/image_file_set_strategy.rb +13 -0
- data/lib/sdr_client/redesigned_client/job_status.rb +74 -0
- data/lib/sdr_client/redesigned_client/matching_file_grouping_strategy.rb +19 -0
- data/lib/sdr_client/redesigned_client/metadata.rb +64 -0
- data/lib/sdr_client/redesigned_client/operations/md5.rb +16 -0
- data/lib/sdr_client/redesigned_client/operations/mime_type.rb +17 -0
- data/lib/sdr_client/redesigned_client/operations/sha1.rb +16 -0
- data/lib/sdr_client/redesigned_client/request_builder.rb +171 -0
- data/lib/sdr_client/redesigned_client/single_file_grouping_strategy.rb +14 -0
- data/lib/sdr_client/redesigned_client/structural_grouper.rb +72 -0
- data/lib/sdr_client/redesigned_client/structural_metadata_builder.rb +51 -0
- data/lib/sdr_client/redesigned_client/unexpected_response.rb +25 -0
- data/lib/sdr_client/redesigned_client/update_dro_with_file_identifiers.rb +35 -0
- data/lib/sdr_client/redesigned_client/update_resource.rb +61 -0
- data/lib/sdr_client/redesigned_client/upload_files.rb +71 -0
- data/lib/sdr_client/redesigned_client/upload_files_metadata_builder.rb +40 -0
- data/lib/sdr_client/redesigned_client.rb +192 -0
- data/lib/sdr_client/version.rb +1 -1
- data/lib/sdr_client.rb +3 -1
- metadata +35 -3
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module SdrClient
|
6
|
+
class RedesignedClient
|
7
|
+
# Wraps operations waiting for results from jobs
|
8
|
+
class JobStatus
|
9
|
+
def initialize(job_id:)
|
10
|
+
@job_id = job_id
|
11
|
+
@result = {
|
12
|
+
status: 'not started',
|
13
|
+
output: {
|
14
|
+
errors: nil,
|
15
|
+
druid: ''
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def complete?
|
21
|
+
@result = client.get(path: path)
|
22
|
+
@result[:status] == 'complete'
|
23
|
+
end
|
24
|
+
|
25
|
+
def druid
|
26
|
+
@result[:output][:druid]
|
27
|
+
end
|
28
|
+
|
29
|
+
def errors
|
30
|
+
@result[:output][:errors]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Polls using exponential backoff, so as not to overrwhelm the server.
|
34
|
+
# @param [Float] secs_between_requests (3.0) initially, how many secs between polling requests
|
35
|
+
# @param [Integer] timeout_in_secs (180) timeout after this many secs
|
36
|
+
# @param [Float] backoff_factor (2.0) how quickly to backoff. This should be > 1.0 and probably ought to be <= 2.0
|
37
|
+
# @return [Boolean] true if successful false if unsuccessful.
|
38
|
+
def wait_until_complete(secs_between_requests: 3.0,
|
39
|
+
timeout_in_secs: 180,
|
40
|
+
backoff_factor: 2.0,
|
41
|
+
max_secs_between_requests: 60)
|
42
|
+
poll_until_complete(secs_between_requests, timeout_in_secs, backoff_factor, max_secs_between_requests)
|
43
|
+
errors.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :job_id
|
49
|
+
|
50
|
+
def client
|
51
|
+
SdrClient::RedesignedClient.instance
|
52
|
+
end
|
53
|
+
|
54
|
+
def path
|
55
|
+
"/v1/background_job_results/#{job_id}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def poll_until_complete(secs_between_requests, timeout_in_secs, backoff_factor, max_secs_between_requests)
|
59
|
+
interval = secs_between_requests
|
60
|
+
Timeout.timeout(timeout_in_secs) do
|
61
|
+
loop do
|
62
|
+
break if complete?
|
63
|
+
|
64
|
+
sleep(interval)
|
65
|
+
# Exponential backoff, limited to max_secs_between_requests
|
66
|
+
interval = [interval * backoff_factor, max_secs_between_requests].min
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rescue Timeout::Error
|
70
|
+
@result[:output][:errors] = ["Not complete after #{timeout_in_secs} seconds"]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# This strategy is for building one file set per set of similarly prefixed uploaded files
|
6
|
+
class MatchingFileGroupingStrategy
|
7
|
+
# @param [Array<SdrClient::RedesignedClient::DirectUploadResponse>] uploads the uploaded files to attach.
|
8
|
+
# @return [Array<Array<SdrClient::RedesignedClient::DirectUploadResponse>>] uploads the grouped uploaded files.
|
9
|
+
def self.run(uploads: [])
|
10
|
+
# Call `#values` on the result of the grouping operation because:
|
11
|
+
# 1) `StructuralGrouper#build_filesets` expects an array of arrays (not hashes); and
|
12
|
+
# 2) the keys aren't used anywhere
|
13
|
+
uploads.group_by do |ul|
|
14
|
+
::File.join(::File.dirname(ul.filename), ::File.basename(ul.filename, '.*'))
|
15
|
+
end.values
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# Build an object and then deposit it
|
6
|
+
class Metadata
|
7
|
+
def self.deposit(...)
|
8
|
+
new(...).deposit
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [String] apo object ID (druid) of the admin policy/APO
|
12
|
+
# @param [String] basepath the base path of the files (to make relative paths absolute)
|
13
|
+
# @param [String] source_id source ID
|
14
|
+
# @param [Hash] options optional parameters
|
15
|
+
# @option options [Array<String>] files a list of relative filepaths to upload
|
16
|
+
# @option options [Hash<String, Hash<String, String>>] files_metadata file name, hash of additional file metadata
|
17
|
+
def initialize(apo:, basepath:, source_id:, **options)
|
18
|
+
@apo = apo
|
19
|
+
@basepath = basepath
|
20
|
+
@source_id = source_id
|
21
|
+
@options = options
|
22
|
+
end
|
23
|
+
|
24
|
+
def deposit # rubocop:disable Metrics/MethodLength
|
25
|
+
structural_metadata = SdrClient::RedesignedClient::StructuralMetadataBuilder.build(
|
26
|
+
files: files, files_metadata: files_metadata, basepath: basepath
|
27
|
+
)
|
28
|
+
request_builder = SdrClient::RedesignedClient::RequestBuilder.new(
|
29
|
+
apo: apo,
|
30
|
+
source_id: source_id,
|
31
|
+
files_metadata: structural_metadata,
|
32
|
+
**options
|
33
|
+
)
|
34
|
+
client.deposit_model(
|
35
|
+
model: request_builder.to_cocina,
|
36
|
+
basepath: basepath,
|
37
|
+
files: files,
|
38
|
+
request_builder: request_builder,
|
39
|
+
**options
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :apo, :basepath, :source_id, :options
|
46
|
+
|
47
|
+
def client
|
48
|
+
SdrClient::RedesignedClient.instance
|
49
|
+
end
|
50
|
+
|
51
|
+
def files
|
52
|
+
options.fetch(:files, [])
|
53
|
+
end
|
54
|
+
|
55
|
+
def files_metadata
|
56
|
+
options.fetch(:files_metadata, {})
|
57
|
+
end
|
58
|
+
|
59
|
+
def accession
|
60
|
+
options.fetch(:accession, false)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
module Operations
|
6
|
+
# MD5 for this file.
|
7
|
+
class MD5
|
8
|
+
NAME = 'md5'
|
9
|
+
|
10
|
+
def self.for(filepath:, **)
|
11
|
+
Digest::MD5.file(filepath).hexdigest
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
module Operations
|
6
|
+
# Mime-type for this file.
|
7
|
+
class MimeType
|
8
|
+
NAME = 'mime_type'
|
9
|
+
|
10
|
+
def self.for(filepath:, **)
|
11
|
+
argv = Shellwords.escape(filepath)
|
12
|
+
`file --mime-type -b #{argv}`.chomp
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
module Operations
|
6
|
+
# SHA1 for this file.
|
7
|
+
class SHA1
|
8
|
+
NAME = 'sha1'
|
9
|
+
|
10
|
+
def self.for(filepath:, **)
|
11
|
+
Digest::SHA1.file(filepath).hexdigest
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# Builds a Cocina request object from metadata. This is what we send to the server when doing a deposit.
|
6
|
+
class RequestBuilder # rubocop:disable Metrics/ClassLength
|
7
|
+
# @param [String] apo the object ID of the administrative policy object
|
8
|
+
# @param [String] source_id the source ID of the object
|
9
|
+
# @param [Hash] options optional parameters
|
10
|
+
# @option options [String] label the required object label
|
11
|
+
# @option options [String] view the access level for viewing the object
|
12
|
+
# @option options [String] download the access level for downloading the object
|
13
|
+
# @option options [String] type (https://cocina.sul.stanford.edu/models/object) the required object type.
|
14
|
+
# @option options [String] use_and_reproduction the use and reproduction statement
|
15
|
+
# @option options [String] copyright the copyright statement
|
16
|
+
# @option options [String] collection the object ID of the collection object
|
17
|
+
# @option options [String] catkey the catalog key (from now unused Symphony ILS)
|
18
|
+
# @option options [String] folio_instance_hrid the instance ID from the Folio ILS
|
19
|
+
# @option options [String] viewing_direction the viewing direction (left to right, right to left)
|
20
|
+
# @option options [Date|nil] embargo_release_date when to release the embargo (or nil if none)
|
21
|
+
# @option options [String] embargo_access the access level for viewing the object after the embargo period
|
22
|
+
# @option options [String] embargo_download the access level for downloading the object after the embargo period
|
23
|
+
# @option options [Array<FileSet>] file_sets the file sets to attach.
|
24
|
+
# @option options [Hash<String, Hash<String, String>>] files_metadata file name, hash of additional file metadata
|
25
|
+
# Additional metadata includes access, preserve, shelve, publish, md5, sha1
|
26
|
+
def initialize(apo:, source_id:, **options)
|
27
|
+
@apo = apo
|
28
|
+
@source_id = source_id
|
29
|
+
@options = options
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_h
|
33
|
+
{
|
34
|
+
access: access_struct,
|
35
|
+
type: type,
|
36
|
+
administrative: administrative,
|
37
|
+
identification: identification,
|
38
|
+
structural: structural,
|
39
|
+
version: 1,
|
40
|
+
label: label.nil? ? ':auto' : label
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_cocina
|
45
|
+
Cocina::Models.build_request(to_h.with_indifferent_access)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param [String] filename
|
49
|
+
# @return [Hash] the metadata for the file
|
50
|
+
def for(filename)
|
51
|
+
files_metadata
|
52
|
+
.fetch(filename, {})
|
53
|
+
.with_indifferent_access
|
54
|
+
.tap do |metadata|
|
55
|
+
metadata[:view] = view unless metadata.key?(:view)
|
56
|
+
metadata[:download] = download unless metadata.key?(:download)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def type
|
61
|
+
options.fetch(:type, Cocina::Models::ObjectType.object)
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_writer :file_sets
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
attr_reader :apo, :source_id, :options
|
69
|
+
|
70
|
+
def administrative
|
71
|
+
{
|
72
|
+
hasAdminPolicy: apo
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def identification
|
77
|
+
{ sourceId: source_id }.tap do |json|
|
78
|
+
json[:catalogLinks] = []
|
79
|
+
json[:catalogLinks] << { catalog: 'symphony', catalogRecordId: catkey, refresh: true } if catkey
|
80
|
+
if folio_instance_hrid
|
81
|
+
json[:catalogLinks] << { catalog: 'folio', catalogRecordId: folio_instance_hrid,
|
82
|
+
refresh: true }
|
83
|
+
end
|
84
|
+
json.delete(:catalogLinks) if json[:catalogLinks].empty?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def structural
|
89
|
+
{}.tap do |json|
|
90
|
+
json[:isMemberOf] = [collection] if collection
|
91
|
+
json[:contains] = file_sets.map(&:to_h) unless file_sets.empty?
|
92
|
+
json[:hasMemberOrders] = [{ viewingDirection: viewing_direction }] if viewing_direction
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def access_struct # rubocop:disable Metrics/MethodLength
|
97
|
+
{
|
98
|
+
view: view,
|
99
|
+
download: download
|
100
|
+
}.tap do |json|
|
101
|
+
json[:useAndReproductionStatement] = use_and_reproduction if use_and_reproduction
|
102
|
+
json[:copyright] = copyright if copyright
|
103
|
+
|
104
|
+
if embargo_release_date
|
105
|
+
json[:embargo] = {
|
106
|
+
releaseDate: embargo_release_date.strftime('%FT%T%:z'),
|
107
|
+
view: embargo_access,
|
108
|
+
download: embargo_download
|
109
|
+
}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def view
|
115
|
+
options.fetch(:view, 'dark')
|
116
|
+
end
|
117
|
+
|
118
|
+
def download
|
119
|
+
options.fetch(:download, 'none')
|
120
|
+
end
|
121
|
+
|
122
|
+
def label
|
123
|
+
options[:label]
|
124
|
+
end
|
125
|
+
|
126
|
+
def use_and_reproduction
|
127
|
+
options[:use_and_reproduction]
|
128
|
+
end
|
129
|
+
|
130
|
+
def copyright
|
131
|
+
options[:copyright]
|
132
|
+
end
|
133
|
+
|
134
|
+
def collection
|
135
|
+
options[:collection]
|
136
|
+
end
|
137
|
+
|
138
|
+
def catkey
|
139
|
+
options[:catkey]
|
140
|
+
end
|
141
|
+
|
142
|
+
def folio_instance_hrid
|
143
|
+
options[:folio_instance_hrid]
|
144
|
+
end
|
145
|
+
|
146
|
+
def viewing_direction
|
147
|
+
options[:viewing_direction]
|
148
|
+
end
|
149
|
+
|
150
|
+
def embargo_release_date
|
151
|
+
options[:embargo_release_date]
|
152
|
+
end
|
153
|
+
|
154
|
+
def embargo_access
|
155
|
+
options.fetch(:embargo_access, 'world')
|
156
|
+
end
|
157
|
+
|
158
|
+
def embargo_download
|
159
|
+
options.fetch(:embargo_download, 'world')
|
160
|
+
end
|
161
|
+
|
162
|
+
def file_sets
|
163
|
+
@file_sets ||= options.fetch(:file_sets, [])
|
164
|
+
end
|
165
|
+
|
166
|
+
def files_metadata
|
167
|
+
options.fetch(:files_metadata, {})
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# This strategy is for building one file set per uploaded file
|
6
|
+
class SingleFileGroupingStrategy
|
7
|
+
# @param [Array<SdrClient::RedesignedClient::DirectUploadResponse>] uploads the uploaded files to attach.
|
8
|
+
# @return [Array<Array<SdrClient::RedesignedClient::DirectUploadResponse>>] uploads the grouped uploaded files.
|
9
|
+
def self.run(uploads: [])
|
10
|
+
uploads.map { |upload| [upload] }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# Builds and constructs the structural metadata given upload responses (file
|
6
|
+
# IDs, file set grouping strategies, etc.)
|
7
|
+
class StructuralGrouper
|
8
|
+
def self.group(...)
|
9
|
+
new(...).group
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [RequestBuilder] request_biulder a request builder instance
|
13
|
+
# @param [Array<DirectUploadResponse>] upload_responses upload response instances
|
14
|
+
# @param [String] grouping_strategy what strategy will be used to group files
|
15
|
+
# @param [String] file_set_strategy what strategy will be used to group file sets
|
16
|
+
def initialize(request_builder:, upload_responses:, grouping_strategy:, file_set_strategy: nil) # rubocop:disable Metrics/MethodLength
|
17
|
+
@request_builder = request_builder
|
18
|
+
@upload_responses = upload_responses
|
19
|
+
@grouping_strategy = if grouping_strategy == 'filename'
|
20
|
+
SdrClient::RedesignedClient::MatchingFileGroupingStrategy
|
21
|
+
else
|
22
|
+
SdrClient::RedesignedClient::SingleFileGroupingStrategy
|
23
|
+
end
|
24
|
+
@file_set_strategy = if file_set_strategy == 'image'
|
25
|
+
SdrClient::RedesignedClient::ImageFileSetStrategy
|
26
|
+
else
|
27
|
+
SdrClient::RedesignedClient::FileTypeFileSetStrategy
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def group
|
32
|
+
request_builder
|
33
|
+
.tap { |request| request.file_sets = build_filesets(uploads: upload_responses) }
|
34
|
+
.to_cocina
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :request_builder, :file_set_strategy, :grouping_strategy, :upload_responses
|
40
|
+
|
41
|
+
# @param [Array<SdrClient::RedesignedClient::DirectUploadResponse>] uploads the uploaded files to attach.
|
42
|
+
# @return [Array<SdrClient::RedesignedClient::FileSet>] the uploads transformed to filesets
|
43
|
+
def build_filesets(uploads:)
|
44
|
+
grouping_strategy
|
45
|
+
.run(uploads: uploads)
|
46
|
+
.map
|
47
|
+
.with_index(1) do |upload_group, i|
|
48
|
+
SdrClient::RedesignedClient::FileSet.new(uploads: upload_group,
|
49
|
+
uploads_metadata: metadata_group(upload_group),
|
50
|
+
label: label(i),
|
51
|
+
type_strategy: file_set_strategy)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def label(index)
|
56
|
+
case request_builder.type
|
57
|
+
when Cocina::Models::ObjectType.book
|
58
|
+
"Page #{index}"
|
59
|
+
else
|
60
|
+
"Object #{index}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get the metadata for the files belonging to a fileset
|
65
|
+
def metadata_group(upload_group)
|
66
|
+
upload_group.each_with_object({}) do |upload, obj|
|
67
|
+
obj[upload.filename] = request_builder.for(upload.filename)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# Build basic metadata for files, iterating over a series of operations
|
6
|
+
# The available options are here: https://github.com/sul-dlss/sdr-client/blob/v0.8.1/lib/sdr_client/deposit/file.rb#L8-L10
|
7
|
+
class StructuralMetadataBuilder
|
8
|
+
OPERATIONS = [
|
9
|
+
Operations::MimeType,
|
10
|
+
Operations::MD5,
|
11
|
+
Operations::SHA1
|
12
|
+
].freeze
|
13
|
+
private_constant :OPERATIONS
|
14
|
+
|
15
|
+
# @param (see #initialize)
|
16
|
+
# @return (see #build)
|
17
|
+
def self.build(files:, files_metadata:, basepath:)
|
18
|
+
new(files: files, files_metadata: files_metadata.dup, basepath: basepath).build
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [Array<String>] files the list of relative filepaths for which to generate metadata
|
22
|
+
def initialize(files:, files_metadata:, basepath:)
|
23
|
+
@files = files
|
24
|
+
@files_metadata = files_metadata
|
25
|
+
@basepath = basepath
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Hash<String, Hash<String, String>>] a map of relative filepaths to a map of metadata
|
29
|
+
def build
|
30
|
+
files.each do |filepath|
|
31
|
+
OPERATIONS.each do |operation|
|
32
|
+
result = operation.for(filepath: absolute_filepath_for(filepath))
|
33
|
+
next if result.nil?
|
34
|
+
|
35
|
+
files_metadata[filepath] ||= {}
|
36
|
+
files_metadata[filepath][operation::NAME] = result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
files_metadata
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :files, :files_metadata, :basepath
|
45
|
+
|
46
|
+
def absolute_filepath_for(filepath)
|
47
|
+
::File.join(basepath, filepath)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# Handles unexpected responses
|
6
|
+
class UnexpectedResponse
|
7
|
+
# Raised when there is a request error (e.g.: a cocina-models version mismatch)
|
8
|
+
class BadRequest < StandardError; end
|
9
|
+
# Raised when there is a problem with the credentials
|
10
|
+
class Unauthorized < StandardError; end
|
11
|
+
|
12
|
+
# @param [Faraday::Response] response
|
13
|
+
def self.call(response)
|
14
|
+
case response.status
|
15
|
+
when 400
|
16
|
+
raise BadRequest, "There was an error with your request: #{response.body}"
|
17
|
+
when 401
|
18
|
+
raise Unauthorized, 'There was an error with your credentials.'
|
19
|
+
else
|
20
|
+
raise "unexpected response: #{response.status} #{response.body}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# Updates a DRO so that the structural metadata references the uploaded file ids
|
6
|
+
class UpdateDroWithFileIdentifiers
|
7
|
+
# @param [Cocina::Model::RequestDRO] request_dro for depositing
|
8
|
+
# @param [Array<DirectUploadResponse>] upload_responses the responses from uploading files
|
9
|
+
# @return [Cocina::Models::RequestDRO]
|
10
|
+
def self.update(request_dro:, upload_responses:)
|
11
|
+
# Manipulating request_dro as hash since immutable
|
12
|
+
structural = request_dro.to_h[:structural]
|
13
|
+
return request_dro.new({}) unless structural
|
14
|
+
|
15
|
+
signed_ids = signed_id_map(upload_responses)
|
16
|
+
request_dro.new(structural: updated_structural(structural, signed_ids))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.signed_id_map(upload_responses)
|
20
|
+
upload_responses.to_h { |response| [response.filename, response.signed_id] }
|
21
|
+
end
|
22
|
+
private_class_method :signed_id_map
|
23
|
+
|
24
|
+
def self.updated_structural(structural, signed_ids)
|
25
|
+
structural[:contains].each do |file_set|
|
26
|
+
file_set[:structural][:contains].each do |file|
|
27
|
+
file[:externalIdentifier] = signed_ids[file[:filename]] if signed_ids.key?(file[:filename])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
structural
|
31
|
+
end
|
32
|
+
private_class_method :updated_structural
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# Updates a resource (metadata) in SDR
|
6
|
+
class UpdateResource
|
7
|
+
RESOURCE_PATH = '/v1/resources/%<id>s'
|
8
|
+
|
9
|
+
def self.run(...)
|
10
|
+
new(...).run
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [Cocina::Models::DRO] model
|
14
|
+
# @param [String] version_description
|
15
|
+
def initialize(model:, version_description: nil)
|
16
|
+
@model = model
|
17
|
+
@version_description = version_description
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String] job id for the background job result
|
21
|
+
def run # rubocop:disable Metrics/MethodLength
|
22
|
+
json = model.to_json
|
23
|
+
logger.debug("Starting update with model: #{json}")
|
24
|
+
|
25
|
+
response_hash = client.put(
|
26
|
+
path: path,
|
27
|
+
body: json,
|
28
|
+
headers: { 'X-Cocina-Models-Version' => Cocina::Models::VERSION },
|
29
|
+
params: request_params,
|
30
|
+
expected_status: 202
|
31
|
+
)
|
32
|
+
|
33
|
+
logger.info("Response from server: #{response_hash}")
|
34
|
+
|
35
|
+
response_hash.fetch('jobId')
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :model, :version_description
|
41
|
+
|
42
|
+
def client
|
43
|
+
SdrClient::RedesignedClient.instance
|
44
|
+
end
|
45
|
+
|
46
|
+
def logger
|
47
|
+
SdrClient::RedesignedClient.config.logger
|
48
|
+
end
|
49
|
+
|
50
|
+
def path
|
51
|
+
format(RESOURCE_PATH, id: model.externalIdentifier)
|
52
|
+
end
|
53
|
+
|
54
|
+
def request_params
|
55
|
+
return { 'versionDescription' => version_description } if version_description
|
56
|
+
|
57
|
+
{}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|