sdr-client 2.12.0 → 2.13.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|