purl_fetcher-client 1.5.4 → 2.1.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: 297afb191a996a944ca1a0f40e1b4c2cb311efe96ac16e2fa419ad85fbab044a
4
- data.tar.gz: 1e09cba32a3f22b75f001641f56c430fabc2db993349dea9f6b6f516dbdd0440
3
+ metadata.gz: 8017e254342b99f9124349f201e4208be5f770266cea433a8dace94af780b880
4
+ data.tar.gz: 1ca31e0176e72973c32ceb02e0e44c29db4dab7a99b067eb7521a7e1ee6d70b1
5
5
  SHA512:
6
- metadata.gz: 4f1e38d68ab409f3f45f0f5b1dbff9de27f156541ba8efa5b7eade1c5b99ca474e3743dc22c3b093194f6c6ca3791bc02cc20016777db2e4a429129485353e8b
7
- data.tar.gz: 1476ef3bbc608f55888b7386ce91deb289e8495ac6044f88265769fa666a3e7f6b48acb0377f575f673018f565c8d0732078b480de7fdf87a6f96002136df73b
6
+ metadata.gz: 026716e6518e2240863859fc6e7142eb3110d5fed36d951ee5232b1579697e0202ad2413bc0e92b85b86b3ca44bc36773a6dba447159f0e4a73ead3f1d648f0a
7
+ data.tar.gz: c3a4d72a5dbd0052190a719507ef5ea80082b2719d5f6eb1dc66e9f4884744f3967be904ddc0d26d49b93efd5210b4acb9445b9bf2450609195e2e4c443832ca
data/README.md CHANGED
@@ -20,28 +20,6 @@ Or install it yourself as:
20
20
 
21
21
  $ gem install purl_fetcher-client
22
22
 
23
- ## Usage
24
-
25
- ### Uploading a file
26
-
27
- ```ruby
28
- PurlFetcher::Client.configure(url:'http://127.0.0.1:3000', token: 'abc123')
29
-
30
- PurlFetcher::Client::UploadFiles.upload(
31
- file_metadata: {
32
- 'file1.txt' => PurlFetcher::Client::DirectUploadRequest.new(
33
- checksum: '123',
34
- byte_size: 10_000,
35
- content_type: 'image/tiff',
36
- filename: 'image.tiff'
37
- )
38
- },
39
- filepath_map: {
40
- 'file1.txt' => File.expand_path('Gemfile.lock')
41
- }
42
- )
43
- ```
44
-
45
23
  ## Development
46
24
 
47
25
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -9,21 +9,27 @@ module PurlFetcher
9
9
  end
10
10
 
11
11
  # @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina the Cocina data object
12
- # @param [Hash<String,String>] file_uploads map of filenames to signed_ids
13
- def initialize(cocina:, file_uploads:)
12
+ # @param [Hash<String,String>] file_uploads map of cocina filenames to staging filenames (UUIDs)
13
+ # @param [String] version the version of the item
14
+ # @param [DateTime] version_date the version date
15
+ # @param [Boolean] must_version whether the item must be versioned
16
+ def initialize(cocina:, file_uploads:, version:, version_date:, must_version:)
14
17
  @cocina = cocina
15
18
  @file_uploads = file_uploads
19
+ @version = version
20
+ @version_date = version_date
21
+ @must_version = must_version
16
22
  end
17
23
 
18
24
  def publish
19
25
  logger.debug("Starting a publish request for: #{druid}")
20
- client.post(path:, body:)
26
+ client.put(path:, body:)
21
27
  logger.debug("Publish request complete")
22
28
  end
23
29
 
24
30
  private
25
31
 
26
- attr_reader :cocina, :file_uploads
32
+ attr_reader :cocina, :file_uploads, :version, :version_date, :must_version
27
33
 
28
34
  def druid
29
35
  cocina.externalIdentifier
@@ -32,7 +38,10 @@ module PurlFetcher
32
38
  def body
33
39
  {
34
40
  object: cocina.to_h,
35
- file_uploads: file_uploads
41
+ file_uploads: file_uploads,
42
+ version: version,
43
+ version_date: version_date.iso8601,
44
+ must_version: must_version
36
45
  }.to_json
37
46
  end
38
47
 
@@ -45,7 +54,7 @@ module PurlFetcher
45
54
  end
46
55
 
47
56
  def path
48
- "/v1/resources"
57
+ "/v1/purls/#{druid}"
49
58
  end
50
59
  end
51
60
  end
@@ -25,7 +25,7 @@ class PurlFetcher::Client::Reader
25
25
  # @raise [PurlFetcher::Client::NotFoundResponseError] if item is not found
26
26
  # @raise [PurlFetcher::Client::ResponseError] if the response is not successful
27
27
  def files_by_digest(druid)
28
- retrieve_json("/purls/druid:#{druid.delete_prefix('druid:')}", {})
28
+ retrieve_json("/v1/purls/druid:#{druid.delete_prefix('druid:')}", {})
29
29
  .fetch("files_by_md5", [])
30
30
  end
31
31
 
@@ -43,7 +43,7 @@ module PurlFetcher
43
43
  end
44
44
 
45
45
  def path
46
- "/v1/released/#{druid}"
46
+ "/v1/purls/#{druid}/release_tags"
47
47
  end
48
48
  end
49
49
  end
@@ -5,26 +5,29 @@ module PurlFetcher
5
5
  # Delete an item from the purl-fetcher cache
6
6
  class Unpublish
7
7
  # @param [String] druid the identifier of the item
8
+ # @param [String] version the version of the item
8
9
  # @raise [Purl::Fetcher::Client::AlreadyDeletedResponseError] if the item is already deleted
9
- def self.unpublish(druid:)
10
- new(druid:).unpublish
10
+ def self.unpublish(druid:, version:)
11
+ new(druid:, version:).unpublish
11
12
  end
12
13
 
13
14
  # @param [String] druid the identifier of the item
14
- def initialize(druid:)
15
+ # @param [String] version the version of the item
16
+ def initialize(druid:, version:)
15
17
  @druid = druid
18
+ @version = version
16
19
  end
17
20
 
18
21
  def unpublish
19
- logger.debug("Starting a unpublish request for: #{druid}")
20
- response = client.delete(path:)
22
+ logger.debug("Starting a unpublish request for: #{druid} (#{version})")
23
+ response = client.delete(path:, params: { version: version })
21
24
  logger.debug("Unpublish request complete")
22
25
  response
23
26
  end
24
27
 
25
28
  private
26
29
 
27
- attr_reader :druid
30
+ attr_reader :druid, :version
28
31
 
29
32
  def logger
30
33
  Client.config.logger
@@ -35,7 +38,7 @@ module PurlFetcher
35
38
  end
36
39
 
37
40
  def path
38
- "/purls/#{druid}"
41
+ "/v1/purls/#{druid}"
39
42
  end
40
43
  end
41
44
  end
@@ -1,5 +1,5 @@
1
1
  module PurlFetcher
2
2
  class Client
3
- VERSION = "1.5.4"
3
+ VERSION = "2.1.0"
4
4
  end
5
5
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PurlFetcher
4
+ class Client
5
+ # Withdraw / restore a version of an item from the purl-fetcher cache
6
+ class Withdraw
7
+ # @param [String] druid the identifier of the item
8
+ # @param [String] version the version of the item
9
+ def self.withdraw(druid:, version:)
10
+ new(druid:, version:).withdraw
11
+ end
12
+
13
+ # @param [String] druid the identifier of the item
14
+ # @param [String] version the version of the item
15
+ def self.restore(druid:, version:)
16
+ new(druid:, version:).restore
17
+ end
18
+
19
+ # @param [String] druid the identifier of the item
20
+ # @param [String] version the version of the item
21
+ def initialize(druid:, version:)
22
+ @druid = druid
23
+ @version = version
24
+ end
25
+
26
+ def withdraw
27
+ logger.debug("Starting a withdraw request for: #{druid} (#{version})")
28
+ response = client.put(path: path(:withdraw))
29
+ logger.debug("Withdraw request complete")
30
+ response
31
+ end
32
+
33
+ def restore
34
+ logger.debug("Starting a restore request for: #{druid} (#{version})")
35
+ response = client.put(path: path(:restore))
36
+ logger.debug("Withdraw request complete")
37
+ response
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :druid, :version
43
+
44
+ def logger
45
+ Client.config.logger
46
+ end
47
+
48
+ def client
49
+ Client.instance
50
+ end
51
+
52
+ def path(action)
53
+ "/v1/purls/#{druid}/versions/#{version}/#{action}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -7,14 +7,10 @@ require "logger"
7
7
  require "purl_fetcher/client/version"
8
8
  require "purl_fetcher/client/reader"
9
9
  require "purl_fetcher/client/mods"
10
- require "purl_fetcher/client/upload_files"
11
- require "purl_fetcher/client/direct_upload_request"
12
- require "purl_fetcher/client/direct_upload_response"
13
- require "purl_fetcher/client/legacy_publish"
14
10
  require "purl_fetcher/client/publish"
15
11
  require "purl_fetcher/client/release_tags"
16
12
  require "purl_fetcher/client/unpublish"
17
- require "purl_fetcher/client/publish_shelve"
13
+ require "purl_fetcher/client/withdraw"
18
14
 
19
15
  module PurlFetcher
20
16
  class Client
@@ -53,8 +49,9 @@ module PurlFetcher
53
49
 
54
50
  # Send an DELETE request
55
51
  # @param path [String] the path for the API request
56
- def delete(path:)
57
- response = connection.delete(path)
52
+ # @param params [Hash] the query parameters for the DELETE request
53
+ def delete(path:, params: {})
54
+ response = connection.delete(path, params)
58
55
 
59
56
  raise AlreadyDeletedResponseError, response.body if response.status == 409
60
57
  raise "unexpected response: #{response.status} #{response.body}" unless response.success?
@@ -79,9 +76,9 @@ module PurlFetcher
79
76
  # @param path [String] the path for the API request
80
77
  # @param body [String] the body of the POST request
81
78
  # @param headers [Hash] extra headers to add to the SDR API request
82
- def put(path:, body:, headers: {})
79
+ def put(path:, body: nil, headers: {})
83
80
  response = connection.put(path) do |request|
84
- request.body = body
81
+ request.body = body if body
85
82
  request.headers = default_headers.merge(headers)
86
83
  end
87
84
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: purl_fetcher-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.4
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Beer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-10 00:00:00.000000000 Z
11
+ date: 2024-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -126,17 +126,13 @@ files:
126
126
  - bin/console
127
127
  - bin/setup
128
128
  - lib/purl_fetcher/client.rb
129
- - lib/purl_fetcher/client/direct_upload_request.rb
130
- - lib/purl_fetcher/client/direct_upload_response.rb
131
- - lib/purl_fetcher/client/legacy_publish.rb
132
129
  - lib/purl_fetcher/client/mods.rb
133
130
  - lib/purl_fetcher/client/publish.rb
134
- - lib/purl_fetcher/client/publish_shelve.rb
135
131
  - lib/purl_fetcher/client/reader.rb
136
132
  - lib/purl_fetcher/client/release_tags.rb
137
133
  - lib/purl_fetcher/client/unpublish.rb
138
- - lib/purl_fetcher/client/upload_files.rb
139
134
  - lib/purl_fetcher/client/version.rb
135
+ - lib/purl_fetcher/client/withdraw.rb
140
136
  - purl_fetcher-client.gemspec
141
137
  homepage:
142
138
  licenses: []
@@ -156,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
152
  - !ruby/object:Gem::Version
157
153
  version: '0'
158
154
  requirements: []
159
- rubygems_version: 3.5.10
155
+ rubygems_version: 3.4.19
160
156
  signing_key:
161
157
  specification_version: 4
162
158
  summary: Traject-compatible reader implementation for streaming data from purl-fetcher
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PurlFetcher
4
- class Client
5
- # This models the JSON that we send to the server.
6
- DirectUploadRequest = Data.define(:checksum, :byte_size, :content_type, :filename) do
7
- def self.from_file(hexdigest:, byte_size:, file_name:, content_type:)
8
- new(checksum: hex_to_base64_digest(hexdigest),
9
- byte_size: byte_size,
10
- content_type: clean_content_type(content_type),
11
- filename: file_name)
12
- end
13
-
14
- def to_h
15
- {
16
- blob: { filename: filename, byte_size: byte_size, checksum: checksum,
17
- content_type: self.class.clean_content_type(content_type) }
18
- }
19
- end
20
-
21
- def to_json(*_args)
22
- JSON.generate(to_h)
23
- end
24
-
25
- def self.clean_content_type(content_type)
26
- return "application/octet-stream" if content_type.blank?
27
-
28
- # ActiveStorage is expecting "application/x-stata-dta" not "application/x-stata-dta;version=14"
29
- content_type.split(";").first
30
- end
31
-
32
- def self.hex_to_base64_digest(hexdigest)
33
- [ [ hexdigest ].pack("H*") ].pack("m0")
34
- end
35
- end
36
- end
37
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PurlFetcher
4
- class Client
5
- DirectUploadResponse = Data.define(:id, :key, :checksum, :byte_size, :content_type,
6
- :filename, :metadata, :created_at, :direct_upload,
7
- :signed_id, :service_name) do
8
- def with_filename(filename)
9
- self.class.new(**deconstruct_keys(nil).merge(filename:))
10
- end
11
- end
12
- end
13
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PurlFetcher
4
- class Client
5
- # Publish (metadata-only). This will be replaced with a single publish operation
6
- class LegacyPublish
7
- # @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina the Cocina data object
8
- def self.publish(cocina:)
9
- new(cocina:).publish
10
- end
11
-
12
- # @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina the Cocina data object
13
- def initialize(cocina:)
14
- @cocina = cocina
15
- end
16
-
17
- def publish
18
- logger.debug("Starting a legacy publish request for: #{druid}")
19
- response = client.post(path:, body:)
20
- logger.debug("Legacy publish request complete")
21
- end
22
-
23
- private
24
-
25
- attr_reader :cocina
26
-
27
- def druid
28
- cocina.externalIdentifier
29
- end
30
-
31
- def body
32
- cocina.to_json
33
- end
34
-
35
- def logger
36
- Client.config.logger
37
- end
38
-
39
- def client
40
- Client.instance
41
- end
42
-
43
- def path
44
- "/purls/#{druid}"
45
- end
46
- end
47
- end
48
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PurlFetcher
4
- class Client
5
- # High-level client for publishing and shelving.
6
- class PublishShelve
7
- def self.publish_and_shelve(...)
8
- new(...).publish_and_shelve
9
- end
10
-
11
- # @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina the Cocina data object
12
- # @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
13
- def initialize(cocina:, filepath_map:)
14
- @cocina = cocina
15
- @filepath_map = filepath_map
16
- end
17
-
18
- def publish_and_shelve
19
- logger.debug("Starting publish and shelve for: #{cocina.externalIdentifier}")
20
-
21
- direct_upload_responses = PurlFetcher::Client::UploadFiles.upload(file_metadata: file_metadata, filepath_map: filepath_map)
22
- file_uploads = direct_upload_responses.map { |response| [ response.filename, response.signed_id ] }.to_h
23
-
24
- PurlFetcher::Client::Publish.publish(cocina: cocina, file_uploads: file_uploads)
25
- logger.debug("Publish and shelve complete")
26
- end
27
-
28
- private
29
-
30
- attr_reader :cocina, :filepath_map
31
-
32
- def file_metadata
33
- return [] unless cocina.dro?
34
-
35
- cocina.structural.contains.flat_map do |fileset|
36
- fileset.structural.contains.select { |file| filepath_map.include?(file.filename) }.map do |file|
37
- direct_upload_request_for(file)
38
- end
39
- end
40
- end
41
-
42
- def direct_upload_request_for(cocina_file)
43
- PurlFetcher::Client::DirectUploadRequest.from_file(
44
- hexdigest: md5_for(cocina_file),
45
- byte_size: size_for(cocina_file),
46
- content_type: "application/octet-stream",
47
- file_name: cocina_file.filename
48
- )
49
- end
50
-
51
- def md5_for(cocina_file)
52
- cocina_file.hasMessageDigests.find { |digest| digest.type == "md5" }.digest
53
- end
54
-
55
- def size_for(cocina_file)
56
- return cocina_file.size if cocina_file.size.present? && cocina_file.size.positive?
57
-
58
- File.size(filepath_map[cocina_file.filename])
59
- end
60
-
61
- def logger
62
- Client.config.logger
63
- end
64
- end
65
- end
66
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PurlFetcher
4
- class Client
5
- # The file uploading part of a transfer
6
- class UploadFiles
7
- # @param [Hash<String,DirectUploadRequest>] file_metadata map of relative filepaths to file metadata
8
- # @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
9
- def self.upload(file_metadata:, filepath_map:)
10
- new(file_metadata: file_metadata, filepath_map: filepath_map).upload
11
- end
12
-
13
- # @param [Array<DirectUploadRequest>] file_metadata array of DirectUploadRequests for the files to be uploaded
14
- # @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
15
- def initialize(file_metadata:, filepath_map:)
16
- @file_metadata = file_metadata
17
- @filepath_map = filepath_map
18
- end
19
-
20
- # @return [Array<DirectUploadResponse>] the responses from the server for the uploads
21
- def upload
22
- file_metadata.map do |metadata|
23
- filepath = metadata.filename
24
- # ActiveStorage modifies the filename provided in response, so setting here with the relative filename
25
- direct_upload(metadata.to_json).with_filename(filepath).tap do |response|
26
- upload_file(response)
27
- logger.info("Upload of #{filepath} complete")
28
- end
29
- end
30
- end
31
-
32
- private
33
-
34
- attr_reader :file_metadata, :filepath_map
35
-
36
- def logger
37
- Client.config.logger
38
- end
39
-
40
- def client
41
- Client.instance
42
- end
43
-
44
- def path
45
- "/v1/direct_uploads"
46
- end
47
-
48
- def direct_upload(metadata_json)
49
- logger.info("Starting an upload request: #{metadata_json}")
50
- response = client.post(path: path, body: metadata_json)
51
-
52
- logger.info("Response from server: #{response}")
53
- DirectUploadResponse.new(**response.symbolize_keys)
54
- end
55
-
56
- def upload_file(response)
57
- logger.info("Uploading `#{response.filename}' to #{response.direct_upload.fetch('url')}")
58
-
59
- client.put(
60
- path: response.direct_upload.fetch("url"),
61
- body: ::File.open(filepath_map[response.filename]),
62
- headers: {
63
- "content-type" => response.content_type,
64
- "content-length" => response.byte_size.to_s
65
- }
66
- )
67
- end
68
- end
69
- end
70
- end