sdr-client 0.2.0 → 0.5.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: 3a995f725226eef2f793d8b0b45e487c37a6e34899b223a1f2c6864f32204325
4
- data.tar.gz: dd947253f76fe8dcfd20565537e89fa86c4b6b13a4e6c68b2ecf285d30406c08
3
+ metadata.gz: 565e7d34958ad818fcc048440816b0a360fa723ea6e62dc9437c6832ba8877c1
4
+ data.tar.gz: 14dfae8ecef2b6d05a256a8bdc7aebebfe801a278546c98bd3066169f7c98899
5
5
  SHA512:
6
- metadata.gz: f3f0c8d2c9994c4a0408438199105dfdcccb0901e4905220cbf99fd69b3d5e9a735752c3de638264c1397986985ecb2a9f992ab1f1fad2e3245025e78eed1dd0
7
- data.tar.gz: 300591272660ad09e74bc992742c3c26c14251bd107252b441a2e2856ed981749dfd5ff298704b13c360a0b3dab0cc89fceb2282a9f301fe24909eaac0b8de0d
6
+ metadata.gz: bc629b8837567ee95b01ce673a1f1b49596c3665810ab8d59573012b3b9b6a674db12dde7d15461a279f87a4ef88ed23d65d2457bbc7c0479330f45b3f2c1649
7
+ data.tar.gz: fe176665dadd92ae291b7dbf5b461bb40606600383ad81c9824a8a4bad83344db5e74b6568a10eced02f4fa14894188122605ac2b7ecd5176d7b25b241963da4
@@ -13,3 +13,5 @@ Layout/LineLength:
13
13
  Metrics/BlockLength:
14
14
  Exclude:
15
15
  - 'spec/**/*'
16
+ ExcludedMethods:
17
+ - 'OptionParser.new'
data/README.md CHANGED
@@ -8,6 +8,10 @@
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
+ ## Install
12
+
13
+ `gem install sdr-client`
14
+
11
15
  ## Usage
12
16
 
13
17
  Log in:
data/exe/sdr CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  $LOAD_PATH.unshift 'lib'
5
5
  require 'optparse'
6
+ require 'sdr_client'
6
7
 
7
8
  options = {}
8
9
  global = OptionParser.new do |opts|
@@ -60,6 +61,20 @@ subcommands = {
60
61
  options[:source_id] = source_id
61
62
  end
62
63
 
64
+ opts.on('--strategy STRATEGY',
65
+ 'The strategy to use for distributing files into filesets. Either "default" or "filename"') do |strategy|
66
+ strategy_class = case strategy
67
+ when 'filename'
68
+ SdrClient::Deposit::MatchingFileGroupingStrategy
69
+ when 'default'
70
+ SdrClient::Deposit::SingleFileGroupingStrategy
71
+ else
72
+ warn "Unknown strategy #{strategy}"
73
+ exit(1)
74
+ end
75
+ options[:grouping_strategy] = strategy_class
76
+ end
77
+
63
78
  opts.on('-h', '--help', 'Display this screen') do
64
79
  puts opts
65
80
  exit
@@ -75,6 +90,5 @@ end
75
90
 
76
91
  subcommands[command].order!
77
92
 
78
- require 'sdr_client'
79
93
  options[:files] = ARGV unless ARGV.empty?
80
94
  SdrClient::CLI.start(command, options)
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'dry/monads'
4
+ require 'faraday'
5
+
3
6
  require 'sdr_client/version'
4
7
  require 'sdr_client/deposit'
5
8
  require 'sdr_client/credentials'
6
9
  require 'sdr_client/login'
7
10
  require 'sdr_client/login_prompt'
8
11
  require 'sdr_client/cli'
9
- require 'faraday'
10
12
 
11
13
  module SdrClient
12
14
  class Error < StandardError; end
@@ -8,7 +8,8 @@ module SdrClient
8
8
  when 'deposit'
9
9
  SdrClient::Deposit.run(options)
10
10
  when 'login'
11
- SdrClient::Login.run(options)
11
+ status = SdrClient::Login.run(options)
12
+ puts status.value if status.failure?
12
13
  else
13
14
  raise "Unknown command #{command}"
14
15
  end
@@ -14,7 +14,7 @@ module SdrClient
14
14
  end
15
15
 
16
16
  def self.read
17
- return IO.readlines(credentials_file, chomp: true).first if File.exist?(credentials_file)
17
+ return IO.readlines(credentials_file, chomp: true).first if ::File.exist?(credentials_file)
18
18
 
19
19
  puts 'Log in first'
20
20
  exit(1)
@@ -3,13 +3,16 @@
3
3
  module SdrClient
4
4
  # The namespace for the "deposit" command
5
5
  module Deposit
6
+ # rubocop:disable Metrics/ParameterLists
6
7
  def self.run(label: nil,
7
8
  type: 'http://cocina.sul.stanford.edu/models/book.jsonld',
8
9
  apo:,
9
10
  collection:,
10
11
  catkey: nil,
11
12
  source_id:,
12
- url:, files: [])
13
+ url:,
14
+ files: [],
15
+ grouping_strategy: SingleFileGroupingStrategy)
13
16
  token = Credentials.read
14
17
 
15
18
  metadata = Request.new(label: label,
@@ -18,12 +21,17 @@ module SdrClient
18
21
  collection: collection,
19
22
  source_id: source_id,
20
23
  catkey: catkey)
21
- Process.new(metadata: metadata, url: url, token: token, files: files).run
24
+ Process.new(metadata: metadata, url: url, token: token, files: files, grouping_strategy: grouping_strategy).run
22
25
  end
26
+ # rubocop:enable Metrics/ParameterLists
23
27
  end
24
28
  end
25
29
  require 'json'
30
+ require 'sdr_client/deposit/single_file_grouping_strategy'
31
+ require 'sdr_client/deposit/matching_file_grouping_strategy'
26
32
  require 'sdr_client/deposit/files/direct_upload_request'
27
33
  require 'sdr_client/deposit/files/direct_upload_response'
34
+ require 'sdr_client/deposit/file'
35
+ require 'sdr_client/deposit/file_set'
28
36
  require 'sdr_client/deposit/request'
29
37
  require 'sdr_client/deposit/process'
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ module Deposit
5
+ # This represents the File metadata that we send to the server for doing a deposit
6
+ class File
7
+ def initialize(external_identifier:, label:, filename:, access: 'dark', preserve: false, shelve: false,
8
+ md5: nil, sha1: nil)
9
+ @external_identifier = external_identifier
10
+ @label = label
11
+ @filename = filename
12
+ @access = access
13
+ @preserve = preserve
14
+ @shelve = shelve
15
+ @md5 = md5
16
+ @sha1 = sha1
17
+ end
18
+
19
+ # rubocop:disable Metrics/MethodLength
20
+ def as_json
21
+ json = {
22
+ "type": 'http://cocina.sul.stanford.edu/models/file.jsonld',
23
+ label: @label,
24
+ filename: @filename,
25
+ externalIdentifier: @external_identifier,
26
+ access: {
27
+ access: @access
28
+ },
29
+ administrative: {
30
+ sdrPreserve: @preserve,
31
+ shelve: @shelve
32
+ }
33
+ }
34
+ json['hasMessageDigests'] = message_digests unless message_digests.empty?
35
+ json
36
+ end
37
+ # rubocop:enable Metrics/MethodLength
38
+
39
+ private
40
+
41
+ def message_digests
42
+ @message_digests ||= [].tap do |message_digests|
43
+ message_digests << create_message_digest('md5', @md5) unless @md5.nil?
44
+ message_digests << create_message_digest('sha1', @sha1) unless @sha1.nil?
45
+ end
46
+ end
47
+
48
+ def create_message_digest(algorithm, digest)
49
+ {
50
+ "type": algorithm,
51
+ digest: digest
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ module Deposit
5
+ # This represents the FileSet metadata that we send to the server for doing a deposit
6
+ class FileSet
7
+ def initialize(uploads: [], uploads_metadata: {}, files: [], label:)
8
+ @label = label
9
+ @files = if !uploads.empty?
10
+ uploads.map do |upload|
11
+ File.new(file_args(upload, uploads_metadata.fetch(upload.filename, {})))
12
+ end
13
+ else
14
+ files
15
+ end
16
+ end
17
+
18
+ def as_json
19
+ {
20
+ "type": 'http://cocina.sul.stanford.edu/models/fileset.jsonld',
21
+ "label": label,
22
+ structural: {
23
+ hasMember: files.map(&:as_json)
24
+ }
25
+ }
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :files, :label
31
+
32
+ def file_args(upload, upload_metadata)
33
+ args = {
34
+ external_identifier: upload.signed_id,
35
+ label: upload.filename,
36
+ filename: upload.filename
37
+ }
38
+ args.merge(upload_metadata)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -9,9 +9,9 @@ module SdrClient
9
9
  def self.from_file(filename)
10
10
  checksum = Digest::MD5.file(filename).base64digest
11
11
  new(checksum: checksum,
12
- byte_size: File.size(filename),
12
+ byte_size: ::File.size(filename),
13
13
  content_type: 'text/html',
14
- filename: File.basename(filename))
14
+ filename: ::File.basename(filename))
15
15
  end
16
16
 
17
17
  def as_json
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ module Deposit
5
+ # This strategy is for building one file set per set of similarly prefixed uploaded files
6
+ class MatchingFileGroupingStrategy
7
+ # @param [Array<SdrClient::Deposit::Files::DirectUploadResponse>] uploads the uploaded files to attach.
8
+ # @return [Array<Array<SdrClient::Deposit::Files::DirectUploadResponse>>] uploads the grouped uploaded files.
9
+ def self.run(uploads: [])
10
+ uploads.group_by { |ul| ::File.basename(ul.filename, '.*') }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -9,16 +9,22 @@ module SdrClient
9
9
  BLOB_PATH = '/v1/direct_uploads'
10
10
  DRO_PATH = '/v1/resources'
11
11
  # @param [Request] metadata information about the object
12
+ # @param [Class] grouping_strategy class whose run method groups an array of uploads
12
13
  # @param [String] url the server to send to
13
14
  # @param [String] token the bearer auth token for the server
14
15
  # @param [Array<String>] files a list of file names to upload
16
+ # @param [Hash<String, Hash<String, String>>] files_metadata file name, hash of additional file metadata
17
+ # Additional metadata includes access, preserve, shelve, md5, sha1
15
18
  # @param [Logger] logger the logger to use
16
- def initialize(metadata:, url:, token:, files: [], logger: Logger.new(STDOUT))
19
+ def initialize(metadata:, grouping_strategy: SingleFileGroupingStrategy, url:,
20
+ token:, files: [], files_metadata: {}, logger: Logger.new(STDOUT))
17
21
  @files = files
18
22
  @url = url
19
23
  @token = token
20
24
  @metadata = metadata
21
25
  @logger = logger
26
+ @grouping_strategy = grouping_strategy
27
+ @files_metadata = files_metadata
22
28
  end
23
29
 
24
30
  def run
@@ -26,18 +32,19 @@ module SdrClient
26
32
  file_metadata = collect_file_metadata
27
33
  upload_responses = upload_file_metadata(file_metadata)
28
34
  upload_files(upload_responses)
29
- request = metadata.with_uploads(upload_responses.values)
35
+ file_sets = build_filesets(uploads: upload_responses.values, files_metadata: files_metadata)
36
+ request = metadata.with_file_sets(file_sets)
30
37
  upload_metadata(request.as_json)
31
38
  end
32
39
 
33
40
  private
34
41
 
35
- attr_reader :metadata, :files, :url, :token, :logger
42
+ attr_reader :metadata, :files, :url, :token, :logger, :grouping_strategy, :files_metadata
36
43
 
37
44
  def check_files_exist
38
45
  logger.info('checking to see if files exist')
39
46
  files.each do |file_name|
40
- raise Errno::ENOENT, file_name unless File.exist?(file_name)
47
+ raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
41
48
  end
42
49
  end
43
50
 
@@ -78,7 +85,7 @@ module SdrClient
78
85
  logger.info("Uploading `#{filename}' to #{url}")
79
86
 
80
87
  upload_response = connection.put(url) do |req|
81
- req.body = File.open(filename)
88
+ req.body = ::File.open(filename)
82
89
  req.headers['Content-Type'] = content_type
83
90
  req.headers['Content-Length'] = content_length.to_s
84
91
  end
@@ -111,6 +118,18 @@ module SdrClient
111
118
  conn.adapter :net_http
112
119
  end
113
120
  end
121
+
122
+ # @param [Array<SdrClient::Deposit::Files::DirectUploadResponse>] uploads the uploaded files to attach.
123
+ # @param [Hash<String,Hash<String, String>>] files_metadata filename, hash of additional file metadata.
124
+ # @return [Array<SdrClient::Deposit::FileSet>] the uploads transformed to filesets
125
+ def build_filesets(uploads:, files_metadata:)
126
+ grouped_uploads = grouping_strategy.run(uploads: uploads)
127
+ grouped_uploads.each_with_index.map do |upload_group, i|
128
+ metadata_group = {}
129
+ upload_group.each { |upload| metadata_group[upload.filename] = files_metadata.fetch(upload.filename, {}) }
130
+ FileSet.new(uploads: upload_group, uploads_metadata: metadata_group, label: "Object #{i + 1}")
131
+ end
132
+ end
114
133
  end
115
134
  end
116
135
  end
@@ -6,21 +6,21 @@ module SdrClient
6
6
  class Request
7
7
  # @param [String] label the required object label
8
8
  # @param [String] type (http://cocina.sul.stanford.edu/models/object.jsonld) the required object type.
9
- # @param [Array<SdrClient::Deposit::Files::DirectUploadResponse>] uploads the uploaded files to attach.
9
+ # @param [Array<FileSet>] file_sets the file sets to attach.
10
10
  def initialize(label: nil,
11
11
  apo:,
12
12
  collection:,
13
13
  source_id:,
14
14
  catkey: nil,
15
15
  type: 'http://cocina.sul.stanford.edu/models/object.jsonld',
16
- uploads: [])
16
+ file_sets: [])
17
17
  @label = label
18
18
  @type = type
19
19
  @source_id = source_id
20
20
  @collection = collection
21
21
  @catkey = catkey
22
22
  @apo = apo
23
- @uploads = uploads
23
+ @file_sets = file_sets
24
24
  end
25
25
 
26
26
  def as_json
@@ -35,20 +35,20 @@ module SdrClient
35
35
  end
36
36
  end
37
37
 
38
- # @return [Request] a clone of this request with the uploads added
39
- def with_uploads(uploads)
38
+ # @return [Request] a clone of this request with the file_sets added
39
+ def with_file_sets(file_sets)
40
40
  Request.new(label: label,
41
41
  apo: apo,
42
42
  collection: collection,
43
43
  source_id: source_id,
44
44
  catkey: catkey,
45
45
  type: type,
46
- uploads: uploads)
46
+ file_sets: file_sets)
47
47
  end
48
48
 
49
49
  private
50
50
 
51
- attr_reader :label, :uploads, :source_id, :catkey, :apo, :collection, :type
51
+ attr_reader :label, :file_sets, :source_id, :catkey, :apo, :collection, :type
52
52
 
53
53
  def administrative
54
54
  {
@@ -65,24 +65,9 @@ module SdrClient
65
65
  def structural
66
66
  {
67
67
  isMemberOf: collection,
68
- hasMember: file_sets_as_json
68
+ hasMember: file_sets.map(&:as_json)
69
69
  }
70
70
  end
71
-
72
- # In this case there is a 1-1 mapping between Files and FileSets,
73
- # but this doesn't always have to be the case. We could change this in the
74
- # future so that we have one FileSet that has an Image and its OCR file.
75
- def file_sets_as_json
76
- uploads.map do |upload|
77
- {
78
- "type": 'http://cocina.sul.stanford.edu/models/fileset.jsonld',
79
- label: upload.filename,
80
- structural: {
81
- hasMember: [upload.signed_id]
82
- }
83
- }
84
- end
85
- end
86
71
  end
87
72
  end
88
73
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ module Deposit
5
+ # This strategy is for building one file set per uploaded file
6
+ class SingleFileGroupingStrategy
7
+ # @param [Array<SdrClient::Deposit::Files::DirectUploadResponse>] uploads the uploaded files to attach.
8
+ # @return [Array<Array<SdrClient::Deposit::Files::DirectUploadResponse>>] uploads the grouped uploaded files.
9
+ def self.run(uploads: [])
10
+ uploads.map { |upload| [upload] }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -4,19 +4,22 @@ module SdrClient
4
4
  # The namespace for the "login" command
5
5
  module Login
6
6
  LOGIN_PATH = '/v1/auth/login'
7
+ extend Dry::Monads[:result]
8
+
9
+ # @return [Result] the status of the call
7
10
  def self.run(url:, login_service: LoginPrompt)
8
11
  request_json = JSON.generate(login_service.run)
9
12
  response = Faraday.post(url + LOGIN_PATH, request_json, 'Content-Type' => 'application/json')
10
13
  case response.status
11
14
  when 200
12
15
  Credentials.write(response.body)
16
+ Success()
13
17
  when 400
14
- puts 'Email address is not a valid email'
18
+ Failure('Email address is not a valid email')
15
19
  when 401
16
- puts 'Invalid username or password'
20
+ Failure('Invalid username or password')
17
21
  else
18
- puts "Status: #{response.status}"
19
- puts response.body
22
+ Failure("Status: #{response.status}\n#{response.body}")
20
23
  end
21
24
  end
22
25
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SdrClient
4
- VERSION = '0.2.0'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -27,7 +27,8 @@ 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 'faraday', '~> 0.17.0'
30
+ spec.add_dependency 'dry-monads'
31
+ spec.add_dependency 'faraday', '>= 0.16'
31
32
 
32
33
  spec.add_development_dependency 'bundler', '~> 2.0'
33
34
  spec.add_development_dependency 'rake', '~> 13.0'
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sdr-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.5.0
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-01-13 00:00:00.000000000 Z
11
+ date: 2020-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-monads
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: faraday
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
- - - "~>"
31
+ - - ">="
18
32
  - !ruby/object:Gem::Version
19
- version: 0.17.0
33
+ version: '0.16'
20
34
  type: :runtime
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
- - - "~>"
38
+ - - ">="
25
39
  - !ruby/object:Gem::Version
26
- version: 0.17.0
40
+ version: '0.16'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -148,10 +162,14 @@ files:
148
162
  - lib/sdr_client/cli.rb
149
163
  - lib/sdr_client/credentials.rb
150
164
  - lib/sdr_client/deposit.rb
165
+ - lib/sdr_client/deposit/file.rb
166
+ - lib/sdr_client/deposit/file_set.rb
151
167
  - lib/sdr_client/deposit/files/direct_upload_request.rb
152
168
  - lib/sdr_client/deposit/files/direct_upload_response.rb
169
+ - lib/sdr_client/deposit/matching_file_grouping_strategy.rb
153
170
  - lib/sdr_client/deposit/process.rb
154
171
  - lib/sdr_client/deposit/request.rb
172
+ - lib/sdr_client/deposit/single_file_grouping_strategy.rb
155
173
  - lib/sdr_client/login.rb
156
174
  - lib/sdr_client/login_prompt.rb
157
175
  - lib/sdr_client/version.rb