Skip to content

Commit 9ab1878

Browse files
committed
split the storage to support extensions
1 parent 9e7fa02 commit 9ab1878

12 files changed

+237
-166
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ spec/reports
1515
test/tmp
1616
test/version_tmp
1717
tmp
18+
tus_data

bin/rubytusd

+91
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,95 @@ Bundler.setup
1111
Bundler.require(:default)
1212

1313
require 'rubytus/api'
14+
require 'rubytus/error'
15+
require 'rubytus/storage'
16+
require 'rubytus/middlewares/tus_barrier'
17+
require 'rubytus/middlewares/storage_barrier'
18+
19+
class API < Rubytus::API
20+
include Rubytus::Constants
21+
22+
use Rubytus::Middlewares::TusBarrier
23+
use Rubytus::Middlewares::StorageBarrier
24+
25+
def on_headers(env, headers)
26+
super(env, headers)
27+
28+
request = Rubytus::Request.new(env)
29+
30+
begin
31+
32+
if env['api.action'] == :patch
33+
uid = env['api.uid']
34+
info = storage.read_info(uid)
35+
36+
validates_offset(request.offset, info.offset)
37+
validates_length(request.content_length, info.remaining_length)
38+
end
39+
40+
rescue Rubytus::PermissionError => e
41+
error!(STATUS_INTERNAL_ERROR, e.message)
42+
end
43+
end
44+
45+
def on_close(env)
46+
if env['api.action'] == :patch
47+
storage.patch_file(env['api.uid'], env['api.buffers'], env['api.offset'])
48+
end
49+
end
50+
51+
def options_parser(opts, options)
52+
options = init_options.merge(options)
53+
default_parser(opts, options)
54+
opts.on('-f', '--data-dir DATA_DIR', "Directory to store uploads, LOCAL storage only (default: #{options[:data_dir]})") do |value|
55+
options[:data_dir] = value
56+
end
57+
end
58+
59+
def init_options
60+
options = default_options
61+
options[:data_dir] = ENV[ENV_DATA_DIR] || DEFAULT_DATA_DIR
62+
options
63+
end
64+
65+
def setup
66+
begin
67+
default_setup
68+
@options[:data_dir] = validate_data_dir(@options[:data_dir])
69+
@options[:storage] = Rubytus::Storage.new(@options)
70+
rescue Rubytus::PermissionError, Rubytus::ConfigurationError => e
71+
puts '[ERROR] ' + e.message
72+
exit(1)
73+
end
74+
end
75+
76+
private
77+
def storage
78+
@options[:storage]
79+
end
80+
81+
def validate_data_dir(data_dir)
82+
if Pathname.new(data_dir).relative?
83+
data_dir = File.join(ENV['PWD'], data_dir)
84+
end
85+
86+
begin
87+
unless File.directory?(data_dir)
88+
Dir.mkdir(data_dir)
89+
end
90+
rescue SystemCallError => _
91+
raise Rubytus::PermissionError, "Couldn't create `data_dir` in #{data_dir}"
92+
end
93+
94+
unless File.world_writable?(data_dir)
95+
begin
96+
File.chmod(0777, data_dir)
97+
rescue Errno::EPERM
98+
raise Rubytus::PermissionError, "Couldn't make `data_dir` in #{data_dir} writable"
99+
end
100+
end
101+
102+
data_dir
103+
end
104+
end
14105

lib/rubytus.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
require 'rubytus/version'
22

3-
module Rubytus
4-
end
3+
module Rubytus; end

lib/rubytus/api.rb

+43-86
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
require 'goliath'
22
require 'goliath/constants'
3-
require 'rubytus/error'
4-
require 'rubytus/helpers'
5-
require 'rubytus/request'
6-
require 'rubytus/storage'
73
require 'rubytus/constants'
8-
require 'rubytus/rack/handler'
4+
require 'rubytus/request'
5+
require 'rubytus/helpers'
6+
require 'rubytus/error'
97
require 'stringio'
108

119
module Rubytus
@@ -14,109 +12,68 @@ class API < Goliath::API
1412
include Rubytus::Constants
1513
include Rubytus::Helpers
1614

17-
use Rubytus::Rack::Handler
18-
1915
def on_headers(env, headers)
20-
request = Rubytus::Request.new(env)
21-
2216
env['api.options'] = @options
23-
env['api.headers'] = COMMON_HEADERS.merge({
24-
'Date' => Time.now.httpdate
25-
})
26-
27-
begin
28-
if request.collection? && request.post?
29-
uid = generate_uid
30-
31-
env['api.action'] = :create
32-
env['api.uid'] = uid
33-
env['api.final_length'] = request.final_length
34-
env['api.resource_url'] = request.resource_url(uid)
35-
end
36-
37-
if request.resource? && request.head?
38-
env['api.action'] = :head
39-
env['api.uid'] = request.resource_uid
40-
end
41-
42-
if request.resource? && request.patch?
43-
unless request.resumable_content_type?
44-
raise HeaderError, "Content-Type must be '#{RESUMABLE_CONTENT_TYPE}'"
45-
end
46-
47-
uid = request.resource_uid
48-
info = storage.read_info(uid)
49-
50-
if request.offset > info.offset
51-
raise UploadError, "Offset: #{request.offset} exceeds current offset: #{info.offset}"
52-
end
53-
54-
if request.content_length > info.remaining_length
55-
raise UploadError, "Content-Length: #{request.content_length} exceeded remaining length: #{info.remaining_length}"
56-
end
57-
58-
env['api.action'] = :patch
59-
env['api.uid'] = uid
60-
env['api.file'] = storage.open_file(uid, request.offset)
61-
end
62-
63-
if request.resource? && request.get?
64-
env['api.action'] = :get
65-
env['api.uid'] = request.resource_uid
66-
end
67-
68-
rescue HeaderError => e
69-
raise Goliath::Validation::Error.new(STATUS_BAD_REQUEST, e.message)
70-
71-
rescue UploadError => e
72-
raise Goliath::Validation::Error.new(STATUS_FORBIDDEN, e.message)
73-
end
17+
env['api.headers'] = COMMON_HEADERS.merge({ 'Date' => Time.now.httpdate })
18+
prepare_headers(env, headers)
7419
end
7520

7621
def on_body(env, data)
7722
if env['api.action'] == :patch
78-
storage.patch_file(env['api.file'], data)
23+
env['api.buffers'] << data
7924
else
8025
body = StringIO.new(data)
8126
env[RACK_INPUT] = body
8227
end
8328
end
8429

85-
def on_close(env)
86-
file = env['api.file']
87-
88-
if file
89-
size = file.size
90-
file.close unless file.closed?
91-
storage.update_info(env['api.uid'], size)
92-
end
93-
end
94-
9530
def response(env)
9631
status = STATUS_OK
9732
headers = env['api.headers']
98-
action = env['api.action']
9933
body = []
10034

101-
begin
102-
case action
103-
when :create
104-
status = STATUS_CREATED
105-
headers['Location'] = env['api.resource_url']
106-
storage.create_file(env['api.uid'], env['api.final_length'])
35+
[status, headers, body]
36+
end
37+
38+
def default_setup
39+
@options[:max_size] = validates_max_size(@options[:max_size])
40+
@options[:base_path] = validates_base_path(@options[:base_path])
41+
end
10742

108-
when :head
109-
info = storage.read_info(env['api.uid'])
110-
headers['Offset'] = info.offset.to_s
43+
def default_options
44+
{
45+
:base_path => ENV[ENV_BASE_PATH] || DEFAULT_BASE_PATH,
46+
:max_size => ENV[ENV_MAX_SIZE] || DEFAULT_MAX_SIZE
47+
}
48+
end
11149

112-
when :get
113-
body = storage.read_file(env['api.uid'])
50+
def default_parser(opts, options)
51+
opts.separator ""
52+
opts.separator "TUSD options:"
53+
54+
args = [
55+
{
56+
:name => :base_path,
57+
:short => '-b',
58+
:long => '--base-path BASE_PATH',
59+
:desc => "Url path used for handling uploads (default: #{options[:base_path]})"
60+
},
61+
{
62+
:name => :max_size,
63+
:short => '-m',
64+
:long => '--max-size MAX_SIZE',
65+
:desc => "Maximum bytes may be stored inside storage (default: #{options[:max_size]})"
66+
}
67+
]
68+
69+
args.each do |arg|
70+
opts.on(arg[:short], arg[:long], arg[:desc]) do |value|
71+
options[arg[:name]] = value
11472
end
115-
rescue PermissionError => e
116-
raise Goliath::Validation::Error.new(500, e.message)
11773
end
11874

119-
[status, headers, body]
75+
# save into global options
76+
@options = options
12077
end
12178
end
12279
end

lib/rubytus/common.rb

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module Rubytus
2+
module Common
3+
def generate_uid
4+
SecureRandom.hex(16)
5+
end
6+
7+
def error!(status, message)
8+
raise Goliath::Validation::Error.new(status, message)
9+
end
10+
end
11+
end

lib/rubytus/constants.rb

+2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ module Constants
55

66
RESUMABLE_CONTENT_TYPE = 'application/offset+octet-stream'
77

8+
ENV_STORAGE = 'TUSD_STORAGE'
89
ENV_DATA_DIR = 'TUSD_DATA_DIR'
910
ENV_BASE_PATH = 'TUSD_BASE_PATH'
1011
ENV_MAX_SIZE = 'TUSD_MAX_SIZE'
1112

13+
DEFAULT_STORAGE = 'local'
1214
DEFAULT_DATA_DIR = 'tus_data'
1315
DEFAULT_BASE_PATH = '/files/'
1416
DEFAULT_MAX_SIZE = 1073741824

lib/rubytus/error.rb

-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
module Rubytus
22
class ConfigurationError < StandardError; end
33
class PermissionError < StandardError; end
4-
class HeaderError < StandardError; end
5-
class UploadError < StandardError; end
64
end

0 commit comments

Comments
 (0)