sdr-client 0.2.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c716a01e8ec4c6fc081a8faa3be231bcd39c3da6481efaa43f7f04248aa717c
4
- data.tar.gz: b69d8f2c2942abb198764eab8025250c0dbd018580e8a535ebafc4b2fc2f2634
3
+ metadata.gz: 04b34e821d56af3fb15c920c9955df147ac75ed028d111a37fac03612f9cc556
4
+ data.tar.gz: ed5c6b78582aa9c61b4fce2e191556508b9d467fbd3a3c615c09c5ac02cea43a
5
5
  SHA512:
6
- metadata.gz: 44884b0decb3af139e287b932110aa614bde3892f692d22528adceee0b02bdf4a966c30abbca6c2a81e755a32fc3189b66de89c8899bae77f64ea30b356cdb81
7
- data.tar.gz: f38600d5355545f23acac4e89ee546f46d90c80cedf30b7becb4b8eac2bc1e3ffead361d2aa343fb67fc01eb9845f1bbd0da9a58bbdee7afd3fb4c5aa8cf23ba
6
+ metadata.gz: 6112ce1e149109355702197254a126ca340d35f45de00574d765d455daae52ff9fdc7214cc1716cf46020315482c9a39e0bd82b18433e5b803e7275f81e4d965
7
+ data.tar.gz: 9b242e5439bcb5e55c7272d2238b41c0284b29f482fbfe53205e2c7b9f5daab0bffc23f515d7785d4d18c654c3bf949356c3a36968c22f2d56c4017fd16cdc68
@@ -13,3 +13,9 @@ Layout/LineLength:
13
13
  Metrics/BlockLength:
14
14
  Exclude:
15
15
  - 'spec/**/*'
16
+ ExcludedMethods:
17
+ - 'OptionParser.new'
18
+
19
+ Naming/FileName:
20
+ Exclude:
21
+ - 'lib/sdr-client.rb'
@@ -14,8 +14,3 @@ Metrics/AbcSize:
14
14
  # Configuration parameters: CountComments, ExcludedMethods.
15
15
  Metrics/MethodLength:
16
16
  Max: 13
17
-
18
- # Offense count: 2
19
- # Configuration parameters: CountKeywordArgs.
20
- Metrics/ParameterLists:
21
- Max: 8
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)
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sdr_client'
@@ -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,61 @@
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
+ # rubocop:disable Metrics/ParameterLists
8
+ def initialize(external_identifier:, label:, filename:,
9
+ access: 'dark', preserve: false, shelve: false,
10
+ mime_type: nil, md5: nil, sha1: nil)
11
+ @external_identifier = external_identifier
12
+ @label = label
13
+ @filename = filename
14
+ @access = access
15
+ @preserve = preserve
16
+ @shelve = shelve
17
+ @mime_type = mime_type
18
+ @md5 = md5
19
+ @sha1 = sha1
20
+ end
21
+ # rubocop:enable Metrics/ParameterLists
22
+
23
+ # rubocop:disable Metrics/MethodLength
24
+ def as_json
25
+ {
26
+ "type": 'http://cocina.sul.stanford.edu/models/file.jsonld',
27
+ label: @label,
28
+ filename: @filename,
29
+ externalIdentifier: @external_identifier,
30
+ access: {
31
+ access: @access
32
+ },
33
+ administrative: {
34
+ sdrPreserve: @preserve,
35
+ shelve: @shelve
36
+ }
37
+ }.tap do |json|
38
+ json['hasMessageDigests'] = message_digests unless message_digests.empty?
39
+ json['hasMimeType'] = @mime_type if @mime_type
40
+ end
41
+ end
42
+ # rubocop:enable Metrics/MethodLength
43
+
44
+ private
45
+
46
+ def message_digests
47
+ @message_digests ||= [].tap do |message_digests|
48
+ message_digests << create_message_digest('md5', @md5) unless @md5.nil?
49
+ message_digests << create_message_digest('sha1', @sha1) unless @sha1.nil?
50
+ end
51
+ end
52
+
53
+ def create_message_digest(algorithm, digest)
54
+ {
55
+ "type": algorithm,
56
+ digest: digest
57
+ }
58
+ end
59
+ end
60
+ end
61
+ 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
+ contains: 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,35 +9,44 @@ 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
+ # rubocop:disable Metrics/ParameterLists
20
+ def initialize(metadata:, grouping_strategy: SingleFileGroupingStrategy, url:,
21
+ token:, files: [], files_metadata: {}, logger: Logger.new(STDOUT))
17
22
  @files = files
18
23
  @url = url
19
24
  @token = token
20
25
  @metadata = metadata
21
26
  @logger = logger
27
+ @grouping_strategy = grouping_strategy
28
+ @files_metadata = files_metadata
22
29
  end
30
+ # rubocop:enable Metrics/ParameterLists
23
31
 
24
32
  def run
25
33
  check_files_exist
26
34
  file_metadata = collect_file_metadata
27
35
  upload_responses = upload_file_metadata(file_metadata)
28
36
  upload_files(upload_responses)
29
- request = metadata.with_uploads(upload_responses.values)
37
+ file_sets = build_filesets(uploads: upload_responses.values, files_metadata: files_metadata)
38
+ request = metadata.with_file_sets(file_sets)
30
39
  upload_metadata(request.as_json)
31
40
  end
32
41
 
33
42
  private
34
43
 
35
- attr_reader :metadata, :files, :url, :token, :logger
44
+ attr_reader :metadata, :files, :url, :token, :logger, :grouping_strategy, :files_metadata
36
45
 
37
46
  def check_files_exist
38
47
  logger.info('checking to see if files exist')
39
48
  files.each do |file_name|
40
- raise Errno::ENOENT, file_name unless File.exist?(file_name)
49
+ raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
41
50
  end
42
51
  end
43
52
 
@@ -78,7 +87,7 @@ module SdrClient
78
87
  logger.info("Uploading `#{filename}' to #{url}")
79
88
 
80
89
  upload_response = connection.put(url) do |req|
81
- req.body = File.open(filename)
90
+ req.body = ::File.open(filename)
82
91
  req.headers['Content-Type'] = content_type
83
92
  req.headers['Content-Length'] = content_length.to_s
84
93
  end
@@ -111,6 +120,18 @@ module SdrClient
111
120
  conn.adapter :net_http
112
121
  end
113
122
  end
123
+
124
+ # @param [Array<SdrClient::Deposit::Files::DirectUploadResponse>] uploads the uploaded files to attach.
125
+ # @param [Hash<String,Hash<String, String>>] files_metadata filename, hash of additional file metadata.
126
+ # @return [Array<SdrClient::Deposit::FileSet>] the uploads transformed to filesets
127
+ def build_filesets(uploads:, files_metadata:)
128
+ grouped_uploads = grouping_strategy.run(uploads: uploads)
129
+ grouped_uploads.each_with_index.map do |upload_group, i|
130
+ metadata_group = {}
131
+ upload_group.each { |upload| metadata_group[upload.filename] = files_metadata.fetch(upload.filename, {}) }
132
+ FileSet.new(uploads: upload_group, uploads_metadata: metadata_group, label: "Object #{i + 1}")
133
+ end
134
+ end
114
135
  end
115
136
  end
116
137
  end
@@ -6,22 +6,24 @@ 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
+ # rubocop:disable Metrics/ParameterLists
10
11
  def initialize(label: nil,
11
12
  apo:,
12
13
  collection:,
13
14
  source_id:,
14
15
  catkey: nil,
15
16
  type: 'http://cocina.sul.stanford.edu/models/object.jsonld',
16
- uploads: [])
17
+ file_sets: [])
17
18
  @label = label
18
19
  @type = type
19
20
  @source_id = source_id
20
21
  @collection = collection
21
22
  @catkey = catkey
22
23
  @apo = apo
23
- @uploads = uploads
24
+ @file_sets = file_sets
24
25
  end
26
+ # rubocop:enable Metrics/ParameterLists
25
27
 
26
28
  def as_json
27
29
  {
@@ -35,20 +37,20 @@ module SdrClient
35
37
  end
36
38
  end
37
39
 
38
- # @return [Request] a clone of this request with the uploads added
39
- def with_uploads(uploads)
40
+ # @return [Request] a clone of this request with the file_sets added
41
+ def with_file_sets(file_sets)
40
42
  Request.new(label: label,
41
43
  apo: apo,
42
44
  collection: collection,
43
45
  source_id: source_id,
44
46
  catkey: catkey,
45
47
  type: type,
46
- uploads: uploads)
48
+ file_sets: file_sets)
47
49
  end
48
50
 
49
51
  private
50
52
 
51
- attr_reader :label, :uploads, :source_id, :catkey, :apo, :collection, :type
53
+ attr_reader :label, :file_sets, :source_id, :catkey, :apo, :collection, :type
52
54
 
53
55
  def administrative
54
56
  {
@@ -65,24 +67,9 @@ module SdrClient
65
67
  def structural
66
68
  {
67
69
  isMemberOf: collection,
68
- hasMember: file_sets_as_json
70
+ contains: file_sets.map(&:as_json)
69
71
  }
70
72
  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
73
  end
87
74
  end
88
75
  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.1'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -27,6 +27,7 @@ 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 'dry-monads'
30
31
  spec.add_dependency 'faraday', '>= 0.16'
31
32
 
32
33
  spec.add_development_dependency 'bundler', '~> 2.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.2.1
4
+ version: 0.6.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-29 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
@@ -144,14 +158,19 @@ files:
144
158
  - bin/console
145
159
  - bin/setup
146
160
  - exe/sdr
161
+ - lib/sdr-client.rb
147
162
  - lib/sdr_client.rb
148
163
  - lib/sdr_client/cli.rb
149
164
  - lib/sdr_client/credentials.rb
150
165
  - lib/sdr_client/deposit.rb
166
+ - lib/sdr_client/deposit/file.rb
167
+ - lib/sdr_client/deposit/file_set.rb
151
168
  - lib/sdr_client/deposit/files/direct_upload_request.rb
152
169
  - lib/sdr_client/deposit/files/direct_upload_response.rb
170
+ - lib/sdr_client/deposit/matching_file_grouping_strategy.rb
153
171
  - lib/sdr_client/deposit/process.rb
154
172
  - lib/sdr_client/deposit/request.rb
173
+ - lib/sdr_client/deposit/single_file_grouping_strategy.rb
155
174
  - lib/sdr_client/login.rb
156
175
  - lib/sdr_client/login_prompt.rb
157
176
  - lib/sdr_client/version.rb