Skip to content

Commit 91d3628

Browse files
committed
Load files from the s3 API
1 parent 4629c23 commit 91d3628

File tree

17 files changed

+245
-75
lines changed

17 files changed

+245
-75
lines changed

.github/workflows/ruby.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,16 @@ jobs:
1919
with:
2020
ruby-version: ${{ matrix.ruby }}
2121
bundler-cache: true
22+
- name: Start MinIO
23+
run: docker compose up -d minio
24+
- name: Wait for MinIO to be ready
25+
run: |
26+
timeout 60 bash -c 'until curl -f http://localhost:9000/minio/health/ready; do sleep 2; done'
2227
- name: Run tests
2328
run: bin/rake test:prepare spec
29+
- name: Stop MinIO
30+
if: always()
31+
run: docker compose down
2432
lint:
2533
runs-on: ubuntu-latest
2634
steps:

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,5 @@ gem 'connection_pool'
8282
group :production do
8383
gem 'newrelic_rpm'
8484
end
85+
86+
gem "aws-sdk-s3", "~> 1.199"

Gemfile.lock

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ GEM
7777
airbrussh (1.5.3)
7878
sshkit (>= 1.6.1, != 1.7.0)
7979
ast (2.4.3)
80+
aws-eventstream (1.4.0)
81+
aws-partitions (1.1172.0)
82+
aws-sdk-core (3.233.0)
83+
aws-eventstream (~> 1, >= 1.3.0)
84+
aws-partitions (~> 1, >= 1.992.0)
85+
aws-sigv4 (~> 1.9)
86+
base64
87+
bigdecimal
88+
jmespath (~> 1, >= 1.6.1)
89+
logger
90+
aws-sdk-kms (1.113.0)
91+
aws-sdk-core (~> 3, >= 3.231.0)
92+
aws-sigv4 (~> 1.5)
93+
aws-sdk-s3 (1.199.1)
94+
aws-sdk-core (~> 3, >= 3.231.0)
95+
aws-sdk-kms (~> 1)
96+
aws-sigv4 (~> 1.5)
97+
aws-sigv4 (1.12.1)
98+
aws-eventstream (~> 1, >= 1.0.2)
8099
base64 (0.3.0)
81100
bcrypt_pbkdf (1.1.1)
82101
bcrypt_pbkdf (1.1.1-arm64-darwin)
@@ -186,6 +205,7 @@ GEM
186205
jbuilder (2.14.1)
187206
actionview (>= 7.0.0)
188207
activesupport (>= 7.0.0)
208+
jmespath (1.6.2)
189209
json (2.15.1)
190210
jwt (3.1.2)
191211
base64
@@ -422,6 +442,7 @@ PLATFORMS
422442
x86_64-linux
423443

424444
DEPENDENCIES
445+
aws-sdk-s3 (~> 1.199)
425446
bootsnap (>= 1.4.2)
426447
cancancan
427448
capistrano (~> 3.0)

app/controllers/file_controller.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
##
44
# API for delivering files from stacks
55
class FileController < ApplicationController
6+
include ActionController::Live
7+
68
rescue_from ActionController::MissingFile do
79
render plain: 'File not found', status: :not_found
810
end
@@ -24,7 +26,16 @@ def show
2426
ip: request.remote_ip
2527
)
2628

27-
send_file current_file.path, filename: current_file.file_name, disposition:
29+
response.headers['Content-Length'] = current_file.content_length
30+
send_stream(
31+
filename: current_file.file_name, # Sets the filename for the download
32+
type: current_file.content_type, # Sets the content type
33+
disposition:
34+
) do |stream|
35+
current_file.s3_object do |chunk|
36+
stream.write(chunk)
37+
end
38+
end
2839
end
2940
# rubocop:enable Metrics/AbcSize
3041

app/controllers/object_controller.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ def show
1919
zip_kit_stream(filename: "#{druid}.zip") do |zip|
2020
files.each do |stacks_file|
2121
zip.write_file(stacks_file.file_name, modification_time: stacks_file.mtime) do |sink|
22-
File.open(stacks_file.path, "rb") { |file_input| IO.copy_stream(file_input, sink) }
22+
stacks_file.s3_object do |chunk|
23+
sink.write(chunk)
24+
end
2325
end
2426
end
2527
end

app/models/s3_client_factory.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
3+
# Creates an S3 client instance.
4+
class S3ClientFactory
5+
def self.create_client
6+
Aws::S3::Client.new(
7+
region: 'us-east-1',
8+
endpoint: Settings.s3.endpoint,
9+
force_path_style: true, # Often required for custom endpoints
10+
access_key_id: Settings.s3.access_key_id,
11+
secret_access_key: Settings.s3.secret_access_key
12+
)
13+
end
14+
end

app/models/stacks_file.rb

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,30 @@ def id
2424

2525
# Some files exist but have unreadable permissions, treat these as non-existent
2626
def readable?
27-
path && File.world_readable?(path)
27+
true
28+
# path && File.world_readable?(path)
29+
end
30+
31+
def s3_object(&)
32+
@s3_object ||= S3ClientFactory.create_client.get_object(bucket: Settings.s3.bucket, key: s3_key, &)
33+
rescue Aws::S3::Errors::NoSuchKey
34+
raise "Unable to find file at #{s3_key}"
2835
end
2936

3037
def mtime
31-
@mtime ||= File.mtime(path) if readable?
38+
@mtime ||= s3_object.last_modified
3239
end
3340

3441
def etag
3542
mtime&.to_i
3643
end
3744

3845
def content_length
39-
@content_length ||= File.size(path) if readable?
46+
cocina_file['size']
4047
end
4148

42-
def path
43-
@path ||= storage_root.absolute_path
49+
def content_type
50+
cocina_file['hasMimeType']
4451
end
4552

4653
# Used as the IIIF identifier for retrieving this file from the image server
@@ -61,10 +68,6 @@ def wowza_identifier
6168
"#{File.dirname(file_path)}/#{streaming_url_file_segment}"
6269
end
6370

64-
def storage_root
65-
@storage_root ||= StorageRoot.new(cocina:, file_name:)
66-
end
67-
6871
def stacks_rights
6972
@stacks_rights ||= StacksRights.new(cocina:, file_name:)
7073
end
@@ -76,4 +79,18 @@ def streamable?
7679
accepted_formats = [".mov", ".mp4", ".mpeg", ".m4a", ".mp3"]
7780
accepted_formats.include? File.extname(file_name)
7881
end
82+
83+
private
84+
85+
def cocina_file
86+
@cocina_file ||= cocina.find_file(file_name)
87+
end
88+
89+
def s3_key
90+
@s3_key ||= storage_root.relative_path
91+
end
92+
93+
def storage_root
94+
@storage_root ||= StorageRoot.new(cocina:, file_name:)
95+
end
7996
end

app/models/storage_root.rb

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,15 @@ def initialize(file_name:, cocina:)
1212
end
1313

1414
def relative_path
15-
@relative_path = File.join(treeified_id, druid, 'content', @md5)
16-
end
17-
18-
def absolute_path
19-
@absolute_path ||= File.join(Settings.stacks.storage_root, relative_path)
15+
File.join(treeified_id, druid, 'content', @md5)
2016
end
2117

2218
private
2319

2420
attr_reader :druid
2521

2622
def druid_parts
27-
@druid_parts ||= druid.match(DRUID_PARTS_PATTERN)
23+
druid.match(DRUID_PARTS_PATTERN)
2824
end
2925

3026
def treeified_id

compose.yaml

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1-
version: '3.6'
2-
31
services:
42
web:
53
build: .
64
ports:
75
- 3001:3000
86
environment:
9-
SECRET_KEY_BASE: 'e55fb68eb0f1f4f82fbb56eafeebf1c134518fd466d1c3d01b2bfd3d6b5cd9347b4c15eecb3bdc0e4c5bc226dba626a23bb7b9ddb14f709638571f910002f639'
10-
RAILS_LOG_TO_STDOUT: 'true'
7+
SECRET_KEY_BASE: "e55fb68eb0f1f4f82fbb56eafeebf1c134518fd466d1c3d01b2bfd3d6b5cd9347b4c15eecb3bdc0e4c5bc226dba626a23bb7b9ddb14f709638571f910002f639"
8+
RAILS_LOG_TO_STDOUT: "true"
119
REMOTE_USER: blalbrit@stanford.edu
12-
SETTINGS__CORS__ALLOW_ORIGIN_URL: 'http://localhost:3000'
13-
tty: true
10+
SETTINGS__CORS__ALLOW_ORIGIN_URL: "http://localhost:3000"
11+
tty: true
12+
minio:
13+
image: minio/minio
14+
environment:
15+
MINIO_ROOT_USER: minio-user
16+
MINIO_ROOT_PASSWORD: minio-password
17+
MINIO_UPDATE: off
18+
command: server /data --json --console-address ':9090'
19+
ports: ["9000:9000/tcp", "9090:9090/tcp"] # open http://127.0.0.1:9090 (9000 is the API port)
20+
volumes:
21+
- minio_data:/data
22+
volumes:
23+
minio_data:

config/settings.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ features:
44
stacks:
55
storage_root: /stacks
66

7+
s3:
8+
access_key_id: ~
9+
secret_access_key: ~
10+
bucket: stacks-test
11+
endpoint: https://sul-weka-s3.stanford.edu
12+
713
imageserver:
814
base_uri: "http://imageserver-prod.stanford.edu/iiif/2/"
915

0 commit comments

Comments
 (0)