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 +4 -4
- data/README.md +0 -22
- data/lib/purl_fetcher/client/publish.rb +13 -4
- data/lib/purl_fetcher/client/unpublish.rb +9 -6
- data/lib/purl_fetcher/client/version.rb +1 -1
- data/lib/purl_fetcher/client.rb +3 -6
- metadata +3 -7
- data/lib/purl_fetcher/client/direct_upload_request.rb +0 -37
- data/lib/purl_fetcher/client/direct_upload_response.rb +0 -13
- data/lib/purl_fetcher/client/publish_shelve.rb +0 -66
- data/lib/purl_fetcher/client/upload_files.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a8e135cc1e6d731b5a6fbf1706b21764524f422fbe5935306d8c42355ac225a
|
4
|
+
data.tar.gz: 617533e69ecaabe0ea954d3052a74197bbe78c14eb2e58921aa1c0d13dfd18af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
13
|
-
|
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
|
-
|
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
|
data/lib/purl_fetcher/client.rb
CHANGED
@@ -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
|
-
|
57
|
-
|
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:
|
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-
|
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.
|
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
|