sdr-client 0.15.1 → 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 133a926c49f83c92a5e68e32c9870480f215f20cbe23fa3bb58d0c9bbedd4167
4
- data.tar.gz: e1df6f6cbe0daec2a0845bdcd170dc6f8ff74284c3311b126a3d401110251edc
3
+ metadata.gz: 67db5756bbb0378efa0f8fc14cf690b790ce8165142487e79b1449d497c30001
4
+ data.tar.gz: 4c6d3c6a5108eece153ebb3880129210423568025db785cbbd12b4c0988d4a3d
5
5
  SHA512:
6
- metadata.gz: a22f7d63d98ae180464e50017014a7ccba537332b17c25d3ae8c2d8374407c7e1b43d11d40c2c2455e511275e62661b627d5a5c9e8a03abeb8ea71d02ce2b51b
7
- data.tar.gz: 7fc0a9b7aa3fc480db874e1b5680b3de651b8d6a7bdcda4f4d0db4319a84b9deedc6a17ac021d77cc84dc6b59083cf0507612886164c5e6cdbde80db566bf882
6
+ metadata.gz: 54da060f2498da93b90292f07ecb3e3076418acfbf19a2a233867a11a251075c1bb56a7b497f11ebe7194eb035285525f9948f90939acfc753bfd2f746b3ad63
7
+ data.tar.gz: bb9a4281ea61338b4acccbca9c2af67801982202378172682e2d17f57d8027018644e1b54d2d3c19a1969328b818f1ce055c04d7040d14c50c7cdab026d572e1
@@ -13,6 +13,7 @@ Layout/LineLength:
13
13
  Metrics/BlockLength:
14
14
  Exclude:
15
15
  - 'spec/**/*'
16
+ - 'sdr-client.gemspec'
16
17
  ExcludedMethods:
17
18
  - 'OptionParser.new'
18
19
 
data/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
  This is a CLI for interacting with the Stanford Digital Repository API.
9
9
  The code for the SDR API server is at https://github.com/sul-dlss/sdr-api
10
10
 
11
+ This provides a way for consumers to easily and correctly deposit files to the SDR without requiring access to the `/dor` NFS mount or to use Hydrus. A primary design goal was for this to have as few dependencies as possible so that it can be easily distributed by `gem install sdr-client` and then it can be used as a CLI.
12
+
11
13
  ## Install
12
14
 
13
15
  `gem install sdr-client`
@@ -2,9 +2,12 @@
2
2
 
3
3
  require 'dry/monads'
4
4
  require 'faraday'
5
+ require 'active_support'
6
+ require 'active_support/core_ext/object/json'
5
7
 
6
8
  require 'sdr_client/version'
7
9
  require 'sdr_client/deposit'
10
+ require 'sdr_client/model_deposit'
8
11
  require 'sdr_client/credentials'
9
12
  require 'sdr_client/login'
10
13
  require 'sdr_client/login_prompt'
@@ -5,10 +5,11 @@ require 'logger'
5
5
  module SdrClient
6
6
  # The namespace for the "deposit" command
7
7
  module Deposit
8
+ BOOK_TYPE = 'http://cocina.sul.stanford.edu/models/book.jsonld'
8
9
  # rubocop:disable Metrics/ParameterLists
9
10
  # rubocop:disable Metrics/MethodLength
10
11
  def self.run(label: nil,
11
- type: 'http://cocina.sul.stanford.edu/models/book.jsonld',
12
+ type: BOOK_TYPE,
12
13
  viewing_direction: nil,
13
14
  access: 'dark',
14
15
  use_statement: nil,
@@ -26,6 +27,7 @@ module SdrClient
26
27
  logger: Logger.new(STDOUT))
27
28
  token = Credentials.read
28
29
 
30
+ augmented_metadata = FileMetadataBuilder.build(files: files, files_metadata: files_metadata)
29
31
  metadata = Request.new(label: label,
30
32
  type: type,
31
33
  access: access,
@@ -38,7 +40,7 @@ module SdrClient
38
40
  embargo_release_date: embargo_release_date,
39
41
  embargo_access: embargo_access,
40
42
  viewing_direction: viewing_direction,
41
- files_metadata: files_metadata)
43
+ files_metadata: augmented_metadata)
42
44
  Process.new(metadata: metadata, url: url, token: token, files: files,
43
45
  grouping_strategy: grouping_strategy, logger: logger).run
44
46
  end
@@ -52,6 +54,7 @@ require 'sdr_client/deposit/matching_file_grouping_strategy'
52
54
  require 'sdr_client/deposit/files/direct_upload_request'
53
55
  require 'sdr_client/deposit/files/direct_upload_response'
54
56
  require 'sdr_client/deposit/file'
57
+ require 'sdr_client/deposit/file_metadata_builder'
55
58
  require 'sdr_client/deposit/file_set'
56
59
  require 'sdr_client/deposit/request'
57
60
  require 'sdr_client/deposit/upload_files'
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sdr_client/deposit/file_metadata_builder_operations/mime_type'
4
+ require 'sdr_client/deposit/file_metadata_builder_operations/md5'
5
+ require 'sdr_client/deposit/file_metadata_builder_operations/sha1'
6
+
7
+ module SdrClient
8
+ module Deposit
9
+ # Build basic metadata for files, iterating over a series of operations
10
+ # The available options are here: https://github.com/sul-dlss/sdr-client/blob/v0.8.1/lib/sdr_client/deposit/file.rb#L8-L10
11
+ class FileMetadataBuilder
12
+ OPERATIONS = [
13
+ FileMetadataBuilderOperations::MimeType,
14
+ FileMetadataBuilderOperations::MD5,
15
+ FileMetadataBuilderOperations::SHA1
16
+ ].freeze
17
+ private_constant :OPERATIONS
18
+
19
+ # @param (see #initialize)
20
+ # @return (see #build)
21
+ def self.build(files:, files_metadata:)
22
+ new(files: files, files_metadata: files_metadata.dup).build
23
+ end
24
+
25
+ # @param [Array<String>] files the list of files for which to generate metadata
26
+ def initialize(files:, files_metadata:)
27
+ @files = files
28
+ @files_metadata = files_metadata
29
+ end
30
+
31
+ # @return [Hash<String, Hash<String, String>>]
32
+ def build
33
+ files.each do |file_path|
34
+ file_key = ::File.basename(file_path)
35
+ OPERATIONS.each do |operation|
36
+ result = operation.for(file_path: file_path)
37
+ next if result.nil?
38
+
39
+ files_metadata[file_key] ||= {}
40
+ files_metadata[file_key][operation::NAME] = result
41
+ end
42
+ end
43
+ files_metadata
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :files, :files_metadata
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module SdrClient
6
+ module Deposit
7
+ module FileMetadataBuilderOperations
8
+ # MD5 for this file.
9
+ class MD5
10
+ NAME = 'md5'
11
+ def self.for(file_path:, **)
12
+ Digest::MD5.file(file_path).hexdigest
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module SdrClient
6
+ module Deposit
7
+ module FileMetadataBuilderOperations
8
+ # Mime-type for this file.
9
+ class MimeType
10
+ NAME = 'mime_type'
11
+ def self.for(file_path:, **)
12
+ `file --mime-type -b #{file_path}`.chomp
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module SdrClient
6
+ module Deposit
7
+ module FileMetadataBuilderOperations
8
+ # SHA1 for this file.
9
+ class SHA1
10
+ NAME = 'sha1'
11
+ def self.for(file_path:, **)
12
+ Digest::SHA1.file(file_path).hexdigest
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -16,6 +16,8 @@ module SdrClient
16
16
  @grouping_strategy = grouping_strategy
17
17
  end
18
18
 
19
+ # @param [UploadFiles] upload_responses the uploaded file information
20
+ # @return [Request] the metadata with fileset information added in.
19
21
  def with_uploads(upload_responses)
20
22
  file_sets = build_filesets(uploads: upload_responses)
21
23
  metadata.with_file_sets(file_sets)
@@ -30,9 +32,25 @@ module SdrClient
30
32
  def build_filesets(uploads:)
31
33
  grouped_uploads = grouping_strategy.run(uploads: uploads)
32
34
  grouped_uploads.map.with_index(1) do |upload_group, i|
33
- metadata_group = {}
34
- upload_group.each { |upload| metadata_group[upload.filename] = metadata.for(upload.filename) }
35
- FileSet.new(uploads: upload_group, uploads_metadata: metadata_group, label: "Object #{i}")
35
+ FileSet.new(uploads: upload_group,
36
+ uploads_metadata: metadata_group(upload_group),
37
+ label: label(i))
38
+ end
39
+ end
40
+
41
+ def label(index)
42
+ case metadata.type
43
+ when BOOK_TYPE
44
+ "Page #{index}"
45
+ else
46
+ "Object #{index}"
47
+ end
48
+ end
49
+
50
+ # Get the metadata for the files belonging to a fileset
51
+ def metadata_group(upload_group)
52
+ upload_group.each_with_object({}) do |upload, obj|
53
+ obj[upload.filename] = metadata.for(upload.filename)
36
54
  end
37
55
  end
38
56
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module SdrClient
6
+ module Deposit
7
+ # The process for doing a deposit from a Cocina Model
8
+ class ModelProcess
9
+ DRO_PATH = '/v1/resources'
10
+ # @param [Cocina::Model::RequestDRO] request_dro for depositing
11
+ # @param [String] url the server to send to
12
+ # @param [String] token the bearer auth token for the server
13
+ # @param [Array<String>] files a list of file names to upload
14
+ # @param [Logger] logger the logger to use
15
+ def initialize(request_dro:, url:,
16
+ token:, files: [], logger: Logger.new(STDOUT))
17
+ @files = files
18
+ @url = url
19
+ @token = token
20
+ @request_dro = request_dro
21
+ @logger = logger
22
+ end
23
+
24
+ def run
25
+ check_files_exist
26
+ child_files_match
27
+
28
+ upload_responses = UploadFiles.new(files: files,
29
+ logger: logger,
30
+ connection: connection,
31
+ mime_types: mime_types).run
32
+ new_request_dro = with_external_identifiers(upload_responses)
33
+ upload_request_dro(new_request_dro.to_json)
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :request_dro, :files, :url, :token, :logger
39
+
40
+ def check_files_exist
41
+ logger.info('checking to see if files exist')
42
+ files.each do |file_name|
43
+ raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
44
+ end
45
+ end
46
+
47
+ def child_files_match
48
+ # Files without request files.
49
+ files.each do |filepath|
50
+ filename = ::File.basename(filepath)
51
+
52
+ raise "Request file not provided for #{filepath}" if request_files[filename].nil?
53
+ end
54
+
55
+ # Request files without files
56
+ filenames = files.map { |filepath| ::File.basename(filepath) }
57
+ request_files.keys.each do |request_filename|
58
+ raise "File not provided for request file #{request_filename}" unless filenames.include?(request_filename)
59
+ end
60
+ end
61
+
62
+ # @return [Hash<Symbol,String>] the result of the metadata call
63
+ def upload_request_dro(request_json)
64
+ logger.info("Starting upload metadata: #{request_json}")
65
+ response = connection.post(DRO_PATH, request_json, 'Content-Type' => 'application/json')
66
+ unexpected_response(response) unless response.status == 201
67
+
68
+ logger.info("Response from server: #{response.body}")
69
+
70
+ { druid: JSON.parse(response.body)['druid'], background_job: response.headers['Location'] }
71
+ end
72
+
73
+ def unexpected_response(response)
74
+ raise "There was an error with your request: #{response.body}" if response.status == 400
75
+ raise 'There was an error with your credentials. Perhaps they have expired?' if response.status == 401
76
+
77
+ raise "unexpected response: #{response.status} #{response.body}"
78
+ end
79
+
80
+ def connection
81
+ @connection ||= Faraday.new(url: url) do |conn|
82
+ conn.authorization :Bearer, token
83
+ conn.adapter :net_http
84
+ end
85
+ end
86
+
87
+ # Map of filenames to mimetypes
88
+ def mime_types
89
+ @mime_types ||=
90
+ Hash[
91
+ request_files.map do |filename, file|
92
+ [filename, file.hasMimeType || 'application/octet-stream']
93
+ end
94
+ ]
95
+ end
96
+
97
+ # Map of filenames to request files
98
+ def request_files
99
+ @request_files ||=
100
+ Hash[
101
+ request_dro.structural.contains.map do |file_set|
102
+ file_set.structural.contains.map do |file|
103
+ [file.filename, file]
104
+ end
105
+ end.flatten(1)
106
+ ]
107
+ end
108
+
109
+ def with_external_identifiers(upload_responses)
110
+ signed_id_map = Hash[upload_responses.map { |response| [response.filename, response.signed_id] }]
111
+
112
+ # Manipulating request_dro as hash since immutable
113
+ request_dro_hash = request_dro.to_h
114
+ request_dro_hash[:structural][:contains].each do |file_set|
115
+ file_set[:structural][:contains].each do |file|
116
+ file[:externalIdentifier] = signed_id_map[file[:filename]]
117
+ end
118
+ end
119
+
120
+ Cocina::Models::RequestDRO.new(request_dro_hash)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -30,7 +30,7 @@ module SdrClient
30
30
  upload_responses = UploadFiles.new(files: files,
31
31
  logger: logger,
32
32
  connection: connection,
33
- metadata: metadata).run
33
+ mime_types: mime_types).run
34
34
  metadata_builder = MetadataBuilder.new(metadata: metadata,
35
35
  grouping_strategy: grouping_strategy,
36
36
  logger: logger)
@@ -74,6 +74,16 @@ module SdrClient
74
74
  conn.adapter :net_http
75
75
  end
76
76
  end
77
+
78
+ def mime_types
79
+ @mime_types ||=
80
+ Hash[
81
+ files.map do |filepath|
82
+ filename = ::File.basename(filepath)
83
+ [filename, metadata.for(filename)['mime_type']]
84
+ end
85
+ ]
86
+ end
77
87
  end
78
88
  end
79
89
  end
@@ -79,10 +79,12 @@ module SdrClient
79
79
  files_metadata.fetch(filename, {})
80
80
  end
81
81
 
82
+ attr_reader :type
83
+
82
84
  private
83
85
 
84
86
  attr_reader :access, :label, :file_sets, :source_id, :catkey, :apo, :collection,
85
- :type, :files_metadata, :embargo_release_date, :embargo_access,
87
+ :files_metadata, :embargo_release_date, :embargo_access,
86
88
  :viewing_direction, :use_statement, :copyright
87
89
 
88
90
  def administrative
@@ -7,13 +7,13 @@ module SdrClient
7
7
  # The file uploading part of a deposit
8
8
  class UploadFiles
9
9
  BLOB_PATH = '/v1/direct_uploads'
10
- # @param [Array<String>] files a list of file names to upload
10
+ # @param [Array<String>] files a list of filepaths to upload
11
11
  # @param [Logger] logger the logger to use
12
12
  # @param [Faraday::Connection] connection
13
- # @param [Request] metadata information about the object
14
- def initialize(files:, metadata:, logger:, connection:)
13
+ # @param [Hash<String,String] mime_types a map of filenames to mime types
14
+ def initialize(files:, mime_types:, logger:, connection:)
15
15
  @files = files
16
- @metadata = metadata
16
+ @mime_types = mime_types
17
17
  @logger = logger
18
18
  @connection = connection
19
19
  end
@@ -28,14 +28,14 @@ module SdrClient
28
28
 
29
29
  private
30
30
 
31
- attr_reader :files, :metadata, :logger, :connection
31
+ attr_reader :files, :mime_types, :logger, :connection
32
32
 
33
33
  def collect_file_metadata
34
34
  files.each_with_object({}) do |path, obj|
35
35
  file_name = ::File.basename(path)
36
36
  obj[path] = Files::DirectUploadRequest.from_file(path,
37
37
  file_name: file_name,
38
- content_type: metadata.for(file_name)[:mime_type])
38
+ content_type: mime_types[file_name])
39
39
  end
40
40
  end
41
41
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module SdrClient
6
+ # The namespace for the "deposit" command
7
+ module Deposit
8
+ def self.model_run(request_dro:,
9
+ files: [],
10
+ url:,
11
+ logger: Logger.new(STDOUT))
12
+ token = Credentials.read
13
+
14
+ ModelProcess.new(request_dro: request_dro, url: url, token: token, files: files, logger: logger).run
15
+ end
16
+ end
17
+ end
18
+ require 'sdr_client/deposit/model_process'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SdrClient
4
- VERSION = '0.15.1'
4
+ VERSION = '0.17.1'
5
5
  end
@@ -27,10 +27,12 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
+ spec.add_dependency 'activesupport'
30
31
  spec.add_dependency 'dry-monads'
31
32
  spec.add_dependency 'faraday', '>= 0.16'
32
33
 
33
34
  spec.add_development_dependency 'bundler', '~> 2.0'
35
+ spec.add_development_dependency 'cocina-models', '~> 0.28.0'
34
36
  spec.add_development_dependency 'rake', '~> 13.0'
35
37
  spec.add_development_dependency 'rspec', '~> 3.0'
36
38
  spec.add_development_dependency 'rubocop', '~> 0.79.0'
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sdr-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-03 00:00:00.000000000 Z
11
+ date: 2020-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: dry-monads
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +66,20 @@ dependencies:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: cocina-models
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.28.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.28.0
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: rake
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -164,17 +192,23 @@ files:
164
192
  - lib/sdr_client/credentials.rb
165
193
  - lib/sdr_client/deposit.rb
166
194
  - lib/sdr_client/deposit/file.rb
195
+ - lib/sdr_client/deposit/file_metadata_builder.rb
196
+ - lib/sdr_client/deposit/file_metadata_builder_operations/md5.rb
197
+ - lib/sdr_client/deposit/file_metadata_builder_operations/mime_type.rb
198
+ - lib/sdr_client/deposit/file_metadata_builder_operations/sha1.rb
167
199
  - lib/sdr_client/deposit/file_set.rb
168
200
  - lib/sdr_client/deposit/files/direct_upload_request.rb
169
201
  - lib/sdr_client/deposit/files/direct_upload_response.rb
170
202
  - lib/sdr_client/deposit/matching_file_grouping_strategy.rb
171
203
  - lib/sdr_client/deposit/metadata_builder.rb
204
+ - lib/sdr_client/deposit/model_process.rb
172
205
  - lib/sdr_client/deposit/process.rb
173
206
  - lib/sdr_client/deposit/request.rb
174
207
  - lib/sdr_client/deposit/single_file_grouping_strategy.rb
175
208
  - lib/sdr_client/deposit/upload_files.rb
176
209
  - lib/sdr_client/login.rb
177
210
  - lib/sdr_client/login_prompt.rb
211
+ - lib/sdr_client/model_deposit.rb
178
212
  - lib/sdr_client/version.rb
179
213
  - sdr-client.gemspec
180
214
  homepage: https://github.com/sul-dlss/sdr-client