purl_fetcher-client 1.5.3 → 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: 26e9001f37819798fcdd911a7a98778a60dcbb6d67eb1d9bdafc2fd4e9eeb757
4
- data.tar.gz: f57db80c799b563e0b8e0863ea35b1ffcd54413373a3f645a4478ee6f8ee571b
3
+ metadata.gz: 3a8e135cc1e6d731b5a6fbf1706b21764524f422fbe5935306d8c42355ac225a
4
+ data.tar.gz: 617533e69ecaabe0ea954d3052a74197bbe78c14eb2e58921aa1c0d13dfd18af
5
5
  SHA512:
6
- metadata.gz: c177d00f8b223863246ed9641ddca70c53fe447d167b1e85ee32b13cff76ef8ee4ef91e21e21ebd6aea7b91544ecabbcab65f84232954e77c2294e0123f4f8e3
7
- data.tar.gz: d19fe944a5839c3177e57a84fcf0241fcf2c92a13cd6a86467db22981340e65dd8c5f04452db890a510e6a78eede456dd879e9d9f80309648548c2ecc9cfcf82
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.3"
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?
@@ -116,7 +113,7 @@ module PurlFetcher
116
113
  # To allow transfer of large files.
117
114
  {
118
115
  read_timeout: 900,
119
- timeout: 900
116
+ timeout: 36000
120
117
  }
121
118
  end
122
119
 
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.3
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-03 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:
@@ -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