purl_fetcher-client 1.5.4 → 2.0.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: 297afb191a996a944ca1a0f40e1b4c2cb311efe96ac16e2fa419ad85fbab044a
4
- data.tar.gz: 1e09cba32a3f22b75f001641f56c430fabc2db993349dea9f6b6f516dbdd0440
3
+ metadata.gz: 3a8e135cc1e6d731b5a6fbf1706b21764524f422fbe5935306d8c42355ac225a
4
+ data.tar.gz: 617533e69ecaabe0ea954d3052a74197bbe78c14eb2e58921aa1c0d13dfd18af
5
5
  SHA512:
6
- metadata.gz: 4f1e38d68ab409f3f45f0f5b1dbff9de27f156541ba8efa5b7eade1c5b99ca474e3743dc22c3b093194f6c6ca3791bc02cc20016777db2e4a429129485353e8b
7
- data.tar.gz: 1476ef3bbc608f55888b7386ce91deb289e8495ac6044f88265769fa666a3e7f6b48acb0377f575f673018f565c8d0732078b480de7fdf87a6f96002136df73b
6
+ metadata.gz: ceed9ffc1a6480273b180966c208a02b0e63eff3c1d1725c0b82a968900d1cc98573b734eebdfc4578709e4782d9e90130b79fca718ff9c8e5056913cda6e48d
7
+ data.tar.gz: 0dc89a720e25f1e0c9f8330665ac3625463053e69717c27fa564876347f949aa9e95d9073c2131cb4eac039ca1d3dc91267edd232c7f3dfcacc02efafb02a042
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,10 +9,16 @@ 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
@@ -23,7 +29,7 @@ module PurlFetcher
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
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  module PurlFetcher
2
2
  class Client
3
- VERSION = "1.5.4"
3
+ VERSION = "2.0.0"
4
4
  end
5
5
  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
10
  require "purl_fetcher/client/legacy_publish"
14
11
  require "purl_fetcher/client/publish"
15
12
  require "purl_fetcher/client/release_tags"
16
13
  require "purl_fetcher/client/unpublish"
17
- require "purl_fetcher/client/publish_shelve"
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?
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.0.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-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -126,16 +126,12 @@ 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
129
  - lib/purl_fetcher/client/legacy_publish.rb
132
130
  - lib/purl_fetcher/client/mods.rb
133
131
  - lib/purl_fetcher/client/publish.rb
134
- - lib/purl_fetcher/client/publish_shelve.rb
135
132
  - lib/purl_fetcher/client/reader.rb
136
133
  - lib/purl_fetcher/client/release_tags.rb
137
134
  - lib/purl_fetcher/client/unpublish.rb
138
- - lib/purl_fetcher/client/upload_files.rb
139
135
  - lib/purl_fetcher/client/version.rb
140
136
  - purl_fetcher-client.gemspec
141
137
  homepage:
@@ -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,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