sdr-client 0.97.0 → 2.0.0

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: c562c90ba48ff8d6168c1c7488537bde46faab61ec81963de5917776ffa9f079
4
- data.tar.gz: 1e22559c270a5312f975a382c87daac40af750c24755f62c02873775d07c0855
3
+ metadata.gz: 1889b3e5a4bda3ce260dd4a0d9671f8ce3ab0db9ae7f05584d18f9f556e7bd0a
4
+ data.tar.gz: aa6ad237bd7febc4c74d40fdff148faa9ede2c0a560b010bce7d42b3cb295589
5
5
  SHA512:
6
- metadata.gz: 5aa3b0ff99dfedf0751c5c0f9782d5c17b0092a5aa98a222e9f15c0011945f6fa67403f82334bcdfc6d405883e49682032dee0b498a1a0f56d7b84a52cf36756
7
- data.tar.gz: ed9acb2561e273f516804b60b734d3a4c900b79c3648bfa62cde33d3164ab42e27d13d675233ca45f3f1f0d75314cca3702810a0a9ed3a9f7b68359332f12a89
6
+ metadata.gz: 9d5f381820d655416d1c8da19db0911af0a016d83fe4970f1a64d03bc8f9f2709f93c05ff0f367ad9bcb1e94f44fed86ef61798312a98660e3bf0e8ca2f1dfbc
7
+ data.tar.gz: 8b5dadcc030463318a87444611294571af60c18bd35d52fe5d0c89ddf7dbd71e4ecf66ec9df3bb21a60a4846cedf3f9e3b6d77835a14a959ec7268d75a17f618
data/.rubocop.yml CHANGED
@@ -27,6 +27,9 @@ Naming/FileName:
27
27
  Exclude:
28
28
  - 'lib/sdr-client.rb'
29
29
 
30
+ RSpec/MultipleMemoizedHelpers:
31
+ Enabled: false
32
+
30
33
  Gemspec/DeprecatedAttributeAssignment: # new in 1.10
31
34
  Enabled: true
32
35
  Gemspec/RequireMFA: # new in 1.23
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sdr-client (0.97.0)
4
+ sdr-client (2.0.0)
5
5
  activesupport
6
6
  cocina-models (~> 0.86.0)
7
7
  dry-monads
@@ -18,7 +18,7 @@ GEM
18
18
  addressable (2.8.1)
19
19
  public_suffix (>= 2.0.2, < 6.0)
20
20
  ast (2.4.2)
21
- attr_extras (6.2.5)
21
+ attr_extras (7.0.0)
22
22
  byebug (11.1.3)
23
23
  cocina-models (0.86.0)
24
24
  activesupport
@@ -37,73 +37,94 @@ GEM
37
37
  zeitwerk (~> 2.1)
38
38
  commonmarker (0.23.6)
39
39
  concurrent-ruby (1.1.10)
40
+ config (4.1.0)
41
+ deep_merge (~> 1.2, >= 1.2.1)
42
+ dry-validation (~> 1.0, >= 1.0.0)
40
43
  crack (0.4.5)
41
44
  rexml
45
+ deep_merge (1.2.2)
42
46
  deprecation (1.1.0)
43
47
  activesupport
44
48
  diff-lcs (1.5.0)
45
49
  docile (1.4.0)
46
- dry-container (0.11.0)
50
+ dry-configurable (1.0.1)
51
+ dry-core (~> 1.0, < 2)
52
+ zeitwerk (~> 2.6)
53
+ dry-core (1.0.0)
47
54
  concurrent-ruby (~> 1.0)
48
- dry-core (0.9.1)
55
+ zeitwerk (~> 2.6)
56
+ dry-inflector (1.0.0)
57
+ dry-initializer (3.1.1)
58
+ dry-logic (1.5.0)
49
59
  concurrent-ruby (~> 1.0)
60
+ dry-core (~> 1.0, < 2)
50
61
  zeitwerk (~> 2.6)
51
- dry-inflector (0.3.0)
52
- dry-logic (1.3.0)
62
+ dry-monads (1.6.0)
53
63
  concurrent-ruby (~> 1.0)
54
- dry-core (~> 0.9, >= 0.9)
64
+ dry-core (~> 1.0, < 2)
55
65
  zeitwerk (~> 2.6)
56
- dry-monads (1.5.0)
66
+ dry-schema (1.13.0)
57
67
  concurrent-ruby (~> 1.0)
58
- dry-core (~> 0.9, >= 0.9)
68
+ dry-configurable (~> 1.0, >= 1.0.1)
69
+ dry-core (~> 1.0, < 2)
70
+ dry-initializer (~> 3.0)
71
+ dry-logic (>= 1.5, < 2)
72
+ dry-types (>= 1.7, < 2)
59
73
  zeitwerk (~> 2.6)
60
- dry-struct (1.5.2)
61
- dry-core (~> 0.9, >= 0.9)
62
- dry-types (~> 1.6)
74
+ dry-struct (1.6.0)
75
+ dry-core (~> 1.0, < 2)
76
+ dry-types (>= 1.7, < 2)
63
77
  ice_nine (~> 0.11)
64
78
  zeitwerk (~> 2.6)
65
- dry-types (1.6.1)
79
+ dry-types (1.7.0)
80
+ concurrent-ruby (~> 1.0)
81
+ dry-core (~> 1.0, < 2)
82
+ dry-inflector (~> 1.0, < 2)
83
+ dry-logic (>= 1.4, < 2)
84
+ zeitwerk (~> 2.6)
85
+ dry-validation (1.10.0)
66
86
  concurrent-ruby (~> 1.0)
67
- dry-container (~> 0.3)
68
- dry-core (~> 0.9, >= 0.9)
69
- dry-inflector (~> 0.1, >= 0.1.2)
70
- dry-logic (~> 1.3, >= 1.3)
87
+ dry-core (~> 1.0, < 2)
88
+ dry-initializer (~> 3.0)
89
+ dry-schema (>= 1.12, < 2)
71
90
  zeitwerk (~> 2.6)
72
91
  edtf (3.1.0)
73
92
  activesupport (>= 3.0, < 8.0)
74
93
  equivalent-xml (0.6.0)
75
94
  nokogiri (>= 1.4.3)
76
- faraday (2.6.0)
95
+ faraday (2.7.1)
77
96
  faraday-net_http (>= 2.0, < 3.1)
78
97
  ruby2_keywords (>= 0.0.4)
79
- faraday-net_http (3.0.1)
98
+ faraday-net_http (3.0.2)
80
99
  hashdiff (1.0.1)
81
100
  i18n (1.12.0)
82
101
  concurrent-ruby (~> 1.0)
83
102
  ice_nine (0.11.2)
84
- json (2.6.2)
103
+ json (2.6.3)
85
104
  jsonpath (1.1.2)
86
105
  multi_json
106
+ launchy (2.5.0)
107
+ addressable (~> 2.7)
87
108
  minitest (5.16.3)
88
109
  multi_json (1.15.0)
89
- nokogiri (1.13.9-x86_64-darwin)
110
+ nokogiri (1.13.10-x86_64-darwin)
90
111
  racc (~> 1.4)
91
- nokogiri (1.13.9-x86_64-linux)
112
+ nokogiri (1.13.10-x86_64-linux)
92
113
  racc (~> 1.4)
93
114
  openapi3_parser (0.9.2)
94
115
  commonmarker (~> 0.17)
95
116
  openapi_parser (0.15.0)
96
117
  optimist (3.0.1)
97
118
  parallel (1.22.1)
98
- parser (3.1.2.1)
119
+ parser (3.1.3.0)
99
120
  ast (~> 2.4.1)
100
121
  patience_diff (1.2.0)
101
122
  optimist (~> 3.0)
102
- public_suffix (5.0.0)
103
- racc (1.6.0)
123
+ public_suffix (5.0.1)
124
+ racc (1.6.1)
104
125
  rainbow (3.1.1)
105
126
  rake (13.0.6)
106
- regexp_parser (2.6.0)
127
+ regexp_parser (2.6.1)
107
128
  rexml (3.2.5)
108
129
  rspec (3.12.0)
109
130
  rspec-core (~> 3.12.0)
@@ -114,7 +135,7 @@ GEM
114
135
  rspec-expectations (3.12.0)
115
136
  diff-lcs (>= 1.2.0, < 2.0)
116
137
  rspec-support (~> 3.12.0)
117
- rspec-mocks (3.12.0)
138
+ rspec-mocks (3.12.1)
118
139
  diff-lcs (>= 1.2.0, < 2.0)
119
140
  rspec-support (~> 3.12.0)
120
141
  rspec-support (3.12.0)
@@ -122,7 +143,7 @@ GEM
122
143
  rspec-core (>= 2, < 4, != 2.12.0)
123
144
  rss (0.2.9)
124
145
  rexml
125
- rubocop (1.37.1)
146
+ rubocop (1.40.0)
126
147
  json (~> 2.3)
127
148
  parallel (~> 1.10)
128
149
  parser (>= 3.1.2.1)
@@ -132,11 +153,11 @@ GEM
132
153
  rubocop-ast (>= 1.23.0, < 2.0)
133
154
  ruby-progressbar (~> 1.7)
134
155
  unicode-display_width (>= 1.4.0, < 3.0)
135
- rubocop-ast (1.23.0)
156
+ rubocop-ast (1.24.0)
136
157
  parser (>= 3.1.1.0)
137
158
  rubocop-rake (0.6.0)
138
159
  rubocop (~> 1.0)
139
- rubocop-rspec (2.14.2)
160
+ rubocop-rspec (2.15.0)
140
161
  rubocop (~> 1.33)
141
162
  ruby-progressbar (1.11.0)
142
163
  ruby2_keywords (0.0.5)
@@ -158,7 +179,7 @@ GEM
158
179
  addressable (>= 2.8.0)
159
180
  crack (>= 0.3.2)
160
181
  hashdiff (>= 0.4.0, < 2.0.0)
161
- zeitwerk (2.6.4)
182
+ zeitwerk (2.6.6)
162
183
 
163
184
  PLATFORMS
164
185
  x86_64-darwin-19
@@ -168,6 +189,8 @@ PLATFORMS
168
189
  DEPENDENCIES
169
190
  bundler (~> 2.0)
170
191
  byebug
192
+ config
193
+ launchy
171
194
  rake (~> 13.0)
172
195
  rspec (~> 3.0)
173
196
  rspec_junit_formatter
@@ -0,0 +1,4 @@
1
+ authentication_proxy_url:
2
+ 'https://sdr-api-prod.stanford.edu': 'https://argo.stanford.edu/settings/tokens'
3
+ 'https://sdr-api-qa.stanford.edu': 'https://argo-qa.stanford.edu/settings/tokens'
4
+ 'https://sdr-api-stage.stanford.edu': 'https://argo-stage.stanford.edu/settings/tokens'
data/exe/sdr CHANGED
@@ -4,5 +4,6 @@
4
4
  $LOAD_PATH.unshift 'lib'
5
5
 
6
6
  require 'sdr_client'
7
+ require 'sdr_client/cli'
7
8
 
8
9
  SdrClient::CLI.start(ARGV)
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'config'
4
+
5
+ Config.setup do |config|
6
+ # Name of the constant exposing loaded settings
7
+ config.const_name = 'Settings'
8
+
9
+ # Load environment variables from the `ENV` object and override any
10
+ # settings defined in files.
11
+ config.use_env = true
12
+
13
+ # Define ENV variable prefix deciding which variables to load into
14
+ # config.
15
+ config.env_prefix = 'SETTINGS'
16
+
17
+ # What string to use as level separator for settings loaded from ENV
18
+ # variables. Default value of '.' works well with Heroku, but you might
19
+ # want to change it for example for '__' to easy override settings from
20
+ # command line, where using dots in variable names might not be allowed
21
+ # (eg. Bash).
22
+ config.env_separator = '__'
23
+
24
+ # Ability to process variables names:
25
+ # * nil - no change
26
+ # * :downcase - convert to lower case
27
+ config.env_converter = :downcase
28
+ end
29
+
30
+ Config.load_and_set_settings('config/settings.yml')
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'launchy'
3
4
  require 'thor'
5
+ require_relative 'cli/config'
4
6
 
5
7
  module SdrClient
6
8
  # The SDR command-line interface
@@ -31,32 +33,19 @@ module SdrClient
31
33
 
32
34
  desc 'get DRUID', 'Retrieve an object from the SDR'
33
35
  def get(druid)
34
- say SdrClient::Find.run(druid, url: options[:url], logger: Logger.new($stderr))
35
- rescue SdrClient::Credentials::NoCredentialsError
36
- say_error 'Log in first'
37
- exit(1)
36
+ rescue_expected_exceptions do
37
+ say Find.run(druid, url: options[:url], logger: Logger.new($stderr))
38
+ end
38
39
  end
39
40
 
40
- desc 'login', 'Prompt for email & password and create a login token (saved in ~/.sdr/token)'
41
+ desc 'login', 'Open authentication proxy UI, or prompt for username and password, and then prompt for token (saved in ~/.sdr/credentials)'
41
42
  def login
42
- status = SdrClient::Login.run(
43
- url: options[:url],
44
- login_service: lambda do
45
- {
46
- email: ask('Email:'),
47
- password: ask('Password:', echo: false)
48
- }
49
- end
50
- )
51
- return puts unless status.failure?
52
-
53
- say_error status.failure
54
- exit(1)
43
+ authentication_proxy_url ? login_via_proxy : login_via_credentials
55
44
  end
56
45
 
57
46
  desc 'version', 'Display the SDR CLI version'
58
47
  def version
59
- say SdrClient::VERSION
48
+ say VERSION
60
49
  end
61
50
 
62
51
  desc 'update DRUID', 'Update an object in the SDR'
@@ -72,13 +61,13 @@ module SdrClient
72
61
  option :cdl, type: :boolean, default: false, desc: 'Controlled digital lending'
73
62
  option :cocina_file, desc: 'Path to a file containing Cocina JSON'
74
63
  option :cocina_pipe, type: :boolean, default: false, desc: 'Indicate Cocina JSON is being piped in'
64
+ option :basepath, default: Dir.getwd, desc: 'Base path for the files'
75
65
  def update(druid)
76
- validate_druid!(druid)
77
- job_id = SdrClient::Update.run(druid, **options)
78
- poll_for_job_complete(job_id: job_id, url: options[:url])
79
- rescue SdrClient::Credentials::NoCredentialsError
80
- say_error 'Log in first'
81
- exit(1)
66
+ rescue_expected_exceptions do
67
+ validate_druid!(druid)
68
+ job_id = Update.run(druid, **options)
69
+ poll_for_job_complete(job_id: job_id, url: options[:url])
70
+ end
82
71
  end
83
72
 
84
73
  desc 'deposit OPTIONAL_FILES', 'Deposit (accession) an object into the SDR'
@@ -95,6 +84,7 @@ module SdrClient
95
84
  option :view, enum: %w[world stanford location-based citation-only dark], desc: 'Access view level for the object'
96
85
  option :files_metadata, desc: 'JSON string representing per-file metadata'
97
86
  option :grouping_strategy, enum: %w[default filename], desc: 'Strategy for grouping files into filesets'
87
+ option :basepath, default: Dir.getwd, desc: 'Base path for the files'
98
88
  def deposit(*files)
99
89
  register_or_deposit(files: files, accession: true)
100
90
  end
@@ -113,22 +103,70 @@ module SdrClient
113
103
  option :view, enum: %w[world stanford location-based citation-only dark], desc: 'Access view level for the object'
114
104
  option :files_metadata, desc: 'JSON string representing per-file metadata'
115
105
  option :grouping_strategy, enum: %w[default filename], desc: 'Strategy for grouping files into filesets'
106
+ option :basepath, default: Dir.getwd, desc: 'Base path for the files'
116
107
  def register(*files)
117
108
  register_or_deposit(files: files, accession: false)
118
109
  end
119
110
 
120
111
  private
121
112
 
122
- def register_or_deposit(files:, accession:)
123
- opts = munge_options(options, files)
124
- skip_polling = opts.delete(:skip_polling)
125
- job_id = SdrClient::Deposit.run(accession: accession, **opts)
126
- return if skip_polling
127
-
128
- poll_for_job_complete(job_id: job_id, url: opts[:url])
129
- rescue SdrClient::Credentials::NoCredentialsError
130
- say_error 'Log in first'
113
+ def rescue_expected_exceptions
114
+ yield
115
+ rescue UnexpectedResponse::TokenExpired
116
+ say_error 'Token has expired! Please log in again.'
131
117
  exit(1)
118
+ rescue Credentials::NoCredentialsError
119
+ say_error 'No token found! Please log in first.'
120
+ exit(1)
121
+ end
122
+
123
+ def login_via_proxy
124
+ say 'Opened the configured authentication proxy in your browser. Once there, generate a new token and copy the entire value.'
125
+ Launchy.open(authentication_proxy_url)
126
+ # Some CLI environments will pop up a message about opening the URL in
127
+ # an existing browse. Since this is OS-dependency, and not something
128
+ # we can control via Launchy, just wait a bit before rendering the
129
+ # `ask` prompt so it's clearer to the user what's happening
130
+ sleep 0.5
131
+ token_string = ask('Paste token here:')
132
+ Credentials.write(token_string)
133
+ expiry = JSON.parse(token_string)['exp']
134
+ say "You are now authenticated for #{options[:url]} until #{expiry}"
135
+ rescue StandardError => e
136
+ say_error "Error logging in via proxy: #{e}"
137
+ exit(1)
138
+ end
139
+
140
+ def login_via_credentials
141
+ status = Login.run(
142
+ url: options[:url],
143
+ login_service: lambda do
144
+ {
145
+ email: ask('Email:'),
146
+ password: ask('Password:', echo: false)
147
+ }
148
+ end
149
+ )
150
+
151
+ return puts unless status.failure?
152
+
153
+ say_error status.failure
154
+ exit(1)
155
+ end
156
+
157
+ def authentication_proxy_url
158
+ @authentication_proxy_url ||= Settings.authentication_proxy_url[options[:url]]
159
+ end
160
+
161
+ def register_or_deposit(files:, accession:)
162
+ rescue_expected_exceptions do
163
+ opts = munge_options(options, files)
164
+ skip_polling = opts.delete(:skip_polling)
165
+ job_id = Deposit.run(accession: accession, **opts)
166
+ return if skip_polling
167
+
168
+ poll_for_job_complete(job_id: job_id, url: opts[:url])
169
+ end
132
170
  end
133
171
 
134
172
  def munge_options(options, files)
@@ -138,9 +176,9 @@ module SdrClient
138
176
  opts[:files_metadata] = JSON.parse(options[:files_metadata]) if options[:files_metadata]
139
177
  if options[:grouping_strategy]
140
178
  opts[:grouping_strategy] = if options[:grouping_strategy] == 'filename'
141
- SdrClient::Deposit::MatchingFileGroupingStrategy
179
+ Deposit::MatchingFileGroupingStrategy
142
180
  else
143
- SdrClient::Deposit::SingleFileGroupingStrategy
181
+ Deposit::SingleFileGroupingStrategy
144
182
  end
145
183
  end
146
184
  end
@@ -158,7 +196,7 @@ module SdrClient
158
196
  say('SDR is processing your request.', nil, false)
159
197
  result = nil
160
198
  1.upto(60) do
161
- result = SdrClient::BackgroundJobResults.show(url: url, job_id: job_id)
199
+ result = BackgroundJobResults.show(url: url, job_id: job_id)
162
200
  break unless %w[pending processing].include?(result['status'])
163
201
 
164
202
  # the extra args to `say` prevent appending a newline
@@ -18,25 +18,26 @@ module SdrClient
18
18
 
19
19
  # @param (see #initialize)
20
20
  # @return (see #build)
21
- def self.build(files:, files_metadata:)
22
- new(files: files, files_metadata: files_metadata.dup).build
21
+ def self.build(files:, files_metadata:, basepath:)
22
+ new(files: files, files_metadata: files_metadata.dup, basepath: basepath).build
23
23
  end
24
24
 
25
- # @param [Array<String>] files the list of files for which to generate metadata
26
- def initialize(files:, files_metadata:)
25
+ # @param [Array<String>] files the list of relative filepaths for which to generate metadata
26
+ def initialize(files:, files_metadata:, basepath:)
27
27
  @files = files
28
28
  @files_metadata = files_metadata
29
+ @basepath = basepath
29
30
  end
30
31
 
31
- # @return [Hash<String, Hash<String, String>>]
32
+ # @return [Hash<String, Hash<String, String>>] a map of relative filepaths to a map of metadata
32
33
  def build
33
- files.each do |file_path|
34
+ files.each do |filepath|
34
35
  OPERATIONS.each do |operation|
35
- result = operation.for(file_path: file_path)
36
+ result = operation.for(filepath: absolute_filepath_for(filepath))
36
37
  next if result.nil?
37
38
 
38
- files_metadata[file_path] ||= {}
39
- files_metadata[file_path][operation::NAME] = result
39
+ files_metadata[filepath] ||= {}
40
+ files_metadata[filepath][operation::NAME] = result
40
41
  end
41
42
  end
42
43
  files_metadata
@@ -44,7 +45,11 @@ module SdrClient
44
45
 
45
46
  private
46
47
 
47
- attr_reader :files, :files_metadata
48
+ attr_reader :files, :files_metadata, :basepath
49
+
50
+ def absolute_filepath_for(filepath)
51
+ ::File.join(basepath, filepath)
52
+ end
48
53
  end
49
54
  end
50
55
  end
@@ -8,8 +8,8 @@ module SdrClient
8
8
  # MD5 for this file.
9
9
  class MD5
10
10
  NAME = 'md5'
11
- def self.for(file_path:, **)
12
- Digest::MD5.file(file_path).hexdigest
11
+ def self.for(filepath:, **)
12
+ Digest::MD5.file(filepath).hexdigest
13
13
  end
14
14
  end
15
15
  end
@@ -8,8 +8,8 @@ module SdrClient
8
8
  # Mime-type for this file.
9
9
  class MimeType
10
10
  NAME = 'mime_type'
11
- def self.for(file_path:, **)
12
- argv = Shellwords.escape(file_path)
11
+ def self.for(filepath:, **)
12
+ argv = Shellwords.escape(filepath)
13
13
  `file --mime-type -b #{argv}`.chomp
14
14
  end
15
15
  end
@@ -8,8 +8,8 @@ module SdrClient
8
8
  # SHA1 for this file.
9
9
  class SHA1
10
10
  NAME = 'sha1'
11
- def self.for(file_path:, **)
12
- Digest::SHA1.file(file_path).hexdigest
11
+ def self.for(filepath:, **)
12
+ Digest::SHA1.file(filepath).hexdigest
13
13
  end
14
14
  end
15
15
  end
@@ -11,17 +11,20 @@ module SdrClient
11
11
  # @param [Boolean] accession should the accessionWF be started
12
12
  # @param [String] priority (nil) what processing priority should be used
13
13
  # either 'low' or 'default'
14
- # @param [Array<String>] files a list of file names to upload
14
+ # @param [Array<String>] files a list of relative filepaths to upload
15
+ # @param [String] basepath filepath to which filepaths are relative
15
16
  # @param [Boolean] assign_doi should a DOI be assigned to this item
16
17
  # @param [Logger] logger the logger to use
17
18
  def initialize(request_dro:, # rubocop:disable Metrics/ParameterLists
18
19
  connection:,
19
20
  accession:,
21
+ basepath:,
20
22
  priority: nil,
21
23
  files: [],
22
24
  assign_doi: false,
23
25
  logger: Logger.new($stdout))
24
26
  @files = files
27
+ @basepath = basepath
25
28
  @connection = connection
26
29
  @request_dro = request_dro
27
30
  @logger = logger
@@ -34,10 +37,14 @@ module SdrClient
34
37
  check_files_exist
35
38
  child_files_match
36
39
 
37
- file_metadata = UploadFilesMetadataBuilder.build(files: files, mime_types: mime_types)
40
+ # file_metadata is a map of relative filepaths to Files::DirectUploadRequests
41
+ file_metadata = UploadFilesMetadataBuilder.build(files: files, mime_types: mime_types, basepath: basepath)
42
+ # upload_response is an array of Files::DirectUploadResponse
38
43
  upload_responses = UploadFiles.upload(file_metadata: file_metadata,
44
+ filepath_map: filepath_map,
39
45
  logger: logger,
40
46
  connection: connection)
47
+
41
48
  new_request_dro = UpdateDroWithFileIdentifiers.update(request_dro: request_dro, upload_responses: upload_responses)
42
49
  CreateResource.run(accession: @accession,
43
50
  priority: @priority,
@@ -49,12 +56,12 @@ module SdrClient
49
56
 
50
57
  private
51
58
 
52
- attr_reader :request_dro, :files, :logger, :connection
59
+ attr_reader :request_dro, :files, :logger, :connection, :basepath
53
60
 
54
61
  def check_files_exist
55
62
  logger.info('checking to see if files exist')
56
- files.each do |file_name|
57
- raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
63
+ files.each do |filepath|
64
+ raise Errno::ENOENT, filepath unless ::File.exist?(absolute_filepath_for(filepath))
58
65
  end
59
66
  end
60
67
 
@@ -70,7 +77,7 @@ module SdrClient
70
77
  end
71
78
  end
72
79
 
73
- # Map of filenames to mimetypes
80
+ # Map of relative filepaths to mimetypes
74
81
  def mime_types
75
82
  @mime_types ||=
76
83
  request_files.transform_values do |file|
@@ -78,7 +85,7 @@ module SdrClient
78
85
  end
79
86
  end
80
87
 
81
- # Map of filenames to request files
88
+ # Map of absolute filepaths to Cocina::Models::RequestFiles
82
89
  def request_files
83
90
  @request_files ||= begin
84
91
  return {} unless request_dro.structural
@@ -90,6 +97,16 @@ module SdrClient
90
97
  end.flatten(1).to_h
91
98
  end
92
99
  end
100
+
101
+ def absolute_filepath_for(filename)
102
+ ::File.join(basepath, filename)
103
+ end
104
+
105
+ def filepath_map
106
+ @filepath_map ||= files.each_with_object({}) do |filepath, obj|
107
+ obj[filepath] = absolute_filepath_for(filepath)
108
+ end
109
+ end
93
110
  end
94
111
  end
95
112
  end
@@ -13,7 +13,8 @@ module SdrClient
13
13
  # either 'low' or 'default'
14
14
  # @param [Class] grouping_strategy class whose run method groups an array of uploads
15
15
  # @param [Class] file_set_type_strategy class whose run method determines file_set type
16
- # @param [Array<String>] files a list of file names to upload
16
+ # @param [Array<String>] files a list of relative filepaths to upload
17
+ # @param [String] basepath filepath to which filepaths are relative
17
18
  # @param [Boolean] assign_doi should a DOI be assigned to this item
18
19
  # @param [Logger] logger the logger to use
19
20
  #
@@ -21,6 +22,7 @@ module SdrClient
21
22
  def initialize(metadata:,
22
23
  connection:,
23
24
  accession:,
25
+ basepath:,
24
26
  priority: nil,
25
27
  grouping_strategy: SingleFileGroupingStrategy,
26
28
  file_set_type_strategy: FileTypeFileSetStrategy,
@@ -36,6 +38,7 @@ module SdrClient
36
38
  @accession = accession
37
39
  @priority = priority
38
40
  @assign_doi = assign_doi
41
+ @basepath = basepath
39
42
  end
40
43
  # rubocop:enable Metrics/ParameterLists
41
44
 
@@ -44,8 +47,9 @@ module SdrClient
44
47
  def run
45
48
  check_files_exist
46
49
 
47
- file_metadata = UploadFilesMetadataBuilder.build(files: files, mime_types: mime_types)
50
+ file_metadata = UploadFilesMetadataBuilder.build(files: files, mime_types: mime_types, basepath: basepath)
48
51
  upload_responses = UploadFiles.upload(file_metadata: file_metadata,
52
+ filepath_map: filepath_map,
49
53
  logger: logger,
50
54
  connection: connection)
51
55
  metadata_builder = MetadataBuilder.new(metadata: metadata,
@@ -65,12 +69,12 @@ module SdrClient
65
69
 
66
70
  private
67
71
 
68
- attr_reader :metadata, :files, :connection, :logger, :grouping_strategy, :file_set_type_strategy
72
+ attr_reader :metadata, :files, :connection, :logger, :grouping_strategy, :file_set_type_strategy, :basepath
69
73
 
70
74
  def check_files_exist
71
75
  logger.info('checking to see if files exist')
72
- files.each do |file_name|
73
- raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
76
+ files.each do |filepath|
77
+ raise Errno::ENOENT, filepath unless ::File.exist?(absolute_filepath_for(filepath))
74
78
  end
75
79
  end
76
80
 
@@ -80,6 +84,16 @@ module SdrClient
80
84
  [filepath, metadata.for(filepath)['mime_type']]
81
85
  end
82
86
  end
87
+
88
+ def filepath_map
89
+ @filepath_map ||= files.each_with_object({}) do |filepath, obj|
90
+ obj[filepath] = absolute_filepath_for(filepath)
91
+ end
92
+ end
93
+
94
+ def absolute_filepath_for(filepath)
95
+ ::File.join(basepath, filepath)
96
+ end
83
97
  end
84
98
  end
85
99
  end
@@ -8,40 +8,43 @@ module SdrClient
8
8
  class UploadFiles
9
9
  BLOB_PATH = '/v1/direct_uploads'
10
10
 
11
- # @param [Hash<String,Files::DirectUploadRequest>] the metadata for uploading the files
11
+ # @param [Hash<String,Files::DirectUploadRequest>] file_metadata map of relative filepaths to file metadata
12
+ # @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
12
13
  # @param [Logger] logger the logger to use
13
14
  # @param [Connection] connection
14
- def self.upload(file_metadata:, logger:, connection:)
15
- new(file_metadata: file_metadata, logger: logger, connection: connection).run
15
+ def self.upload(file_metadata:, filepath_map:, logger:, connection:)
16
+ new(file_metadata: file_metadata, filepath_map: filepath_map, logger: logger, connection: connection).run
16
17
  end
17
18
 
18
- # @param [Hash<String,Files::DirectUploadRequest>] the metadata for uploading the files
19
+ # @param [Hash<String,Files::DirectUploadRequest>] file_metadata map of relative filepaths to file metadata
20
+ # @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
19
21
  # @param [Logger] logger the logger to use
20
22
  # @param [Connection] connection
21
- def initialize(file_metadata:, logger:, connection:)
23
+ def initialize(file_metadata:, filepath_map:, logger:, connection:)
22
24
  @file_metadata = file_metadata
25
+ @filepath_map = filepath_map
23
26
  @logger = logger
24
27
  @connection = connection
25
28
  end
26
29
 
27
30
  # @return [Array<Files::DirectUploadResponse>] the responses from the server for the uploads
28
31
  def run
29
- file_metadata.map do |filename, metadata|
32
+ file_metadata.map do |filepath, metadata|
30
33
  direct_upload(metadata.to_json).tap do |response|
31
- # ActiveStorage modifies the filename provided in response, so setting here.
32
- response.filename = filename
33
- upload_file(filename: filename,
34
+ # ActiveStorage modifies the filename provided in response, so setting here with the relative filename
35
+ response.filename = filepath
36
+ upload_file(filename: filepath,
34
37
  url: response.direct_upload.fetch('url'),
35
38
  content_type: response.content_type,
36
39
  content_length: response.byte_size)
37
- logger.info("Upload of #{filename} complete")
40
+ logger.info("Upload of #{filepath} complete")
38
41
  end
39
42
  end
40
43
  end
41
44
 
42
45
  private
43
46
 
44
- attr_reader :logger, :connection, :file_metadata
47
+ attr_reader :logger, :connection, :file_metadata, :filepath_map
45
48
 
46
49
  def direct_upload(metadata_json)
47
50
  logger.info("Starting an upload request: #{metadata_json}")
@@ -63,7 +66,7 @@ module SdrClient
63
66
  logger.info("Uploading `#{filename}' to #{url}")
64
67
 
65
68
  upload_response = connection.put(url) do |req|
66
- req.body = ::File.open(filename)
69
+ req.body = ::File.open(filepath_map[filename])
67
70
  req.headers['Content-Type'] = content_type
68
71
  req.headers['Content-Length'] = content_length.to_s
69
72
  end
@@ -6,39 +6,36 @@ module SdrClient
6
6
  module Deposit
7
7
  # Collecting all the metadata about the files for a deposit
8
8
  class UploadFilesMetadataBuilder
9
- # @param [Array<String>] files a list of filepaths to upload
9
+ # @param [Array<String>] files a list of relative filepaths to upload
10
10
  # @param [Hash<String,String>] mime_types a map of filenames to mime types
11
+ # @param [String] basepath path to which files are relative
11
12
  # @return [Hash<String, Files::DirectUploadRequest>] the metadata for uploading the files
12
- def self.build(files:, mime_types:)
13
- new(files: files, mime_types: mime_types).build
13
+ def self.build(files:, mime_types:, basepath:)
14
+ new(files: files, mime_types: mime_types, basepath: basepath).build
14
15
  end
15
16
 
16
- # @param [Array<String>] files a list of filepaths to upload
17
+ # @param [Array<String>] files a list of absolute filepaths to upload
17
18
  # @param [Hash<String,String>] mime_types a map of filenames to mime types
18
- def initialize(files:, mime_types:)
19
+ # @param [String] basepath path to which files are relative
20
+ def initialize(files:, mime_types:, basepath:)
19
21
  @files = files
20
22
  @mime_types = mime_types
23
+ @basepath = basepath
21
24
  end
22
25
 
23
- attr_reader :files, :mime_types
26
+ attr_reader :files, :mime_types, :basepath
24
27
 
25
28
  # @return [Hash<String, Files::DirectUploadRequest>] the metadata for uploading the files
26
29
  def build
27
- files.each_with_object({}) do |path, obj|
28
- obj[path] = Files::DirectUploadRequest.from_file(path,
29
- file_name: filename_for(path),
30
- content_type: mime_type_for(path))
30
+ files.each_with_object({}) do |filepath, obj|
31
+ obj[filepath] = Files::DirectUploadRequest.from_file(absolute_filepath_for(filepath),
32
+ file_name: filepath,
33
+ content_type: mime_types[filepath])
31
34
  end
32
35
  end
33
36
 
34
- # This can be overridden in the case that the file on disk has a different
35
- # name than we want to repo to know about.
36
- def filename_for(file_path)
37
- file_path
38
- end
39
-
40
- def mime_type_for(file_path)
41
- mime_types[filename_for(file_path)]
37
+ def absolute_filepath_for(filepath)
38
+ ::File.join(basepath, filepath)
42
39
  end
43
40
  end
44
41
  end
@@ -8,6 +8,9 @@ module SdrClient
8
8
  BOOK_TYPE = Cocina::Models::ObjectType.book
9
9
  # rubocop:disable Metrics/ParameterLists
10
10
  # rubocop:disable Metrics/MethodLength
11
+ # params [Array<String>] files a list of relative filepaths to upload
12
+ # params [String] basepath filepath to which filepaths are relative, defaults to current directory
13
+ # params [Hash<String,Hash>] file_metadata relative filepath, hash of metadata per-file metadata
11
14
  # @return [String] job id for the background job result
12
15
  def self.run(label: nil,
13
16
  type: BOOK_TYPE,
@@ -26,29 +29,34 @@ module SdrClient
26
29
  url:,
27
30
  files: [],
28
31
  files_metadata: {},
32
+ basepath: Dir.getwd,
29
33
  accession: false,
30
34
  priority: nil,
31
35
  grouping_strategy: SingleFileGroupingStrategy,
32
36
  file_set_type_strategy: FileTypeFileSetStrategy,
33
37
  logger: Logger.new($stdout))
34
- augmented_metadata = FileMetadataBuilder.build(files: files, files_metadata: files_metadata)
35
- metadata = Request.new(label: label,
36
- type: type,
37
- view: view,
38
- download: download,
39
- apo: apo,
40
- use_and_reproduction: use_and_reproduction,
41
- copyright: copyright,
42
- collection: collection,
43
- source_id: source_id,
44
- catkey: catkey,
45
- embargo_release_date: embargo_release_date,
46
- embargo_access: embargo_access,
47
- embargo_download: embargo_download,
48
- viewing_direction: viewing_direction,
49
- files_metadata: augmented_metadata)
38
+ # augmented_metadata is a map of relative filepaths to file metadata
39
+ augmented_metadata = FileMetadataBuilder.build(files: files, files_metadata: files_metadata, basepath: basepath)
40
+ request = Request.new(label: label,
41
+ type: type,
42
+ view: view,
43
+ download: download,
44
+ apo: apo,
45
+ use_and_reproduction: use_and_reproduction,
46
+ copyright: copyright,
47
+ collection: collection,
48
+ source_id: source_id,
49
+ catkey: catkey,
50
+ embargo_release_date: embargo_release_date,
51
+ embargo_access: embargo_access,
52
+ embargo_download: embargo_download,
53
+ viewing_direction: viewing_direction,
54
+ files_metadata: augmented_metadata)
50
55
  connection = Connection.new(url: url)
51
- Process.new(metadata: metadata, connection: connection, files: files,
56
+ Process.new(metadata: request,
57
+ connection: connection,
58
+ files: files,
59
+ basepath: basepath,
52
60
  grouping_strategy: grouping_strategy,
53
61
  file_set_type_strategy: file_set_type_strategy,
54
62
  accession: accession,
@@ -57,9 +65,11 @@ module SdrClient
57
65
  end
58
66
  # rubocop:enable Metrics/MethodLength
59
67
 
60
- # @param [Array<String>] files absolute paths to files
68
+ # @param [Array<String>] files relative paths to files
69
+ # @params [String] basepath path to which files are relative
61
70
  def self.model_run(request_dro:,
62
71
  files: [],
72
+ basepath: Dir.getwd,
63
73
  url:,
64
74
  accession:,
65
75
  priority: nil,
@@ -68,6 +78,7 @@ module SdrClient
68
78
  ModelProcess.new(request_dro: request_dro,
69
79
  connection: connection,
70
80
  files: files,
81
+ basepath: basepath,
71
82
  logger: logger,
72
83
  accession: accession,
73
84
  priority: priority).run
@@ -90,7 +101,6 @@ require 'sdr_client/deposit/request'
90
101
  require 'sdr_client/deposit/metadata_builder'
91
102
  require 'sdr_client/deposit/model_process'
92
103
  require 'sdr_client/deposit/process'
93
- require 'sdr_client/deposit/unexpected_response'
94
104
  require 'sdr_client/deposit/update_resource'
95
105
  require 'sdr_client/deposit/update_dro_with_file_identifiers'
96
106
  require 'sdr_client/deposit/upload_files'
@@ -7,9 +7,6 @@ module SdrClient
7
7
  module Find
8
8
  DRO_PATH = '/v1/resources/%<id>s'
9
9
 
10
- # Raised when find returns an unsuccessful response
11
- class Failed < StandardError; end
12
-
13
10
  # @raise [Failed] if the find operation fails
14
11
  # @return [String] JSON for the given Cocina object or an error
15
12
  def self.run(druid, url:, logger: Logger.new($stdout))
@@ -17,12 +14,10 @@ module SdrClient
17
14
  path = format(DRO_PATH, id: druid)
18
15
  logger.info("Retrieving metadata from: #{path}")
19
16
  response = connection.get(path)
20
- unless response.success?
21
- error_message = "There was an HTTP #{response.status} error making the request: #{response.body}"
22
- logger.error(error_message)
23
- raise Failed, error_message
24
- end
25
- response.body
17
+ return response.body if response.success?
18
+
19
+ logger.error("There was an HTTP #{response.status} error making the request: #{response.body}")
20
+ UnexpectedResponse.call(response)
26
21
  end
27
22
  end
28
23
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ # Handles unexpected responses
5
+ class UnexpectedResponse
6
+ # Raised when there is a request error (e.g.: a cocina-models version mismatch)
7
+ class BadRequest < StandardError; end
8
+ # Raised when there is a problem with the credentials
9
+ class Unauthorized < StandardError; end
10
+ # Raised when there is an expired token
11
+ class TokenExpired < StandardError; end
12
+
13
+ # @param [Faraday::Response] response
14
+ def self.call(response)
15
+ case response.status
16
+ when 400
17
+ raise BadRequest, "There was an error with your request: #{response.body}"
18
+ when 401
19
+ raise TokenExpired, 'Your token has expired' if response.body.match?('Signature has expired')
20
+
21
+ raise Unauthorized, 'There was an error with your credentials.'
22
+ else
23
+ raise "unexpected response: #{response.status} #{response.body}"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SdrClient
4
- VERSION = '0.97.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/sdr_client.rb CHANGED
@@ -9,13 +9,13 @@ require 'active_support/core_ext/file/atomic'
9
9
  require 'cocina/models'
10
10
 
11
11
  require 'sdr_client/version'
12
+ require 'sdr_client/unexpected_response'
12
13
  require 'sdr_client/deposit'
13
14
  require 'sdr_client/update'
14
15
  require 'sdr_client/credentials'
15
16
  require 'sdr_client/find'
16
17
  require 'sdr_client/login'
17
18
  require 'sdr_client/login_prompt'
18
- require 'sdr_client/cli'
19
19
  require 'sdr_client/connection'
20
20
  require 'sdr_client/background_job_results'
21
21
 
data/sdr-client.gemspec CHANGED
@@ -33,6 +33,8 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency 'faraday', '>= 0.16'
34
34
 
35
35
  spec.add_development_dependency 'bundler', '~> 2.0'
36
+ spec.add_development_dependency 'config'
37
+ spec.add_development_dependency 'launchy'
36
38
  spec.add_development_dependency 'rake', '~> 13.0'
37
39
  spec.add_development_dependency 'rspec', '~> 3.0'
38
40
  spec.add_development_dependency 'rubocop', '~> 1.24'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sdr-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.97.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-03 00:00:00.000000000 Z
11
+ date: 2022-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: config
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: launchy
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: rake
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -215,12 +243,14 @@ files:
215
243
  - Rakefile
216
244
  - bin/console
217
245
  - bin/setup
246
+ - config/settings.yml
218
247
  - exe/remove_w3cdtf_encoding_from_event_dates
219
248
  - exe/sdr
220
249
  - lib/sdr-client.rb
221
250
  - lib/sdr_client.rb
222
251
  - lib/sdr_client/background_job_results.rb
223
252
  - lib/sdr_client/cli.rb
253
+ - lib/sdr_client/cli/config.rb
224
254
  - lib/sdr_client/connection.rb
225
255
  - lib/sdr_client/credentials.rb
226
256
  - lib/sdr_client/deposit.rb
@@ -241,7 +271,6 @@ files:
241
271
  - lib/sdr_client/deposit/process.rb
242
272
  - lib/sdr_client/deposit/request.rb
243
273
  - lib/sdr_client/deposit/single_file_grouping_strategy.rb
244
- - lib/sdr_client/deposit/unexpected_response.rb
245
274
  - lib/sdr_client/deposit/update_dro_with_file_identifiers.rb
246
275
  - lib/sdr_client/deposit/update_resource.rb
247
276
  - lib/sdr_client/deposit/upload_files.rb
@@ -249,6 +278,7 @@ files:
249
278
  - lib/sdr_client/find.rb
250
279
  - lib/sdr_client/login.rb
251
280
  - lib/sdr_client/login_prompt.rb
281
+ - lib/sdr_client/unexpected_response.rb
252
282
  - lib/sdr_client/update.rb
253
283
  - lib/sdr_client/version.rb
254
284
  - sdr-client.gemspec
@@ -259,7 +289,7 @@ metadata:
259
289
  source_code_uri: https://github.com/sul-dlss/sdr-client
260
290
  changelog_uri: https://github.com/sul-dlss/sdr-client/releases
261
291
  rubygems_mfa_required: 'true'
262
- post_install_message:
292
+ post_install_message:
263
293
  rdoc_options: []
264
294
  require_paths:
265
295
  - lib
@@ -274,8 +304,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
274
304
  - !ruby/object:Gem::Version
275
305
  version: '0'
276
306
  requirements: []
277
- rubygems_version: 3.3.7
278
- signing_key:
307
+ rubygems_version: 3.2.32
308
+ signing_key:
279
309
  specification_version: 4
280
310
  summary: The CLI for https://github.com/sul-dlss/sdr-api
281
311
  test_files: []
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SdrClient
4
- module Deposit
5
- # Handles unexpected responses when manipulating resources
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. Perhaps they have expired?'
19
- else
20
- raise "unexpected response: #{response.status} #{response.body}"
21
- end
22
- end
23
- end
24
- end
25
- end