Home

Awesome

Shrine::Tus

Provides tools for integrating Shrine with tus-ruby-server.

Installation

gem "shrine-tus", "~> 2.0"

Usage

When the file is uploaded to the tus server, you'll probably want to attach it to a database record and move it to permanent storage. The storage that tus-ruby-server uses should be considered temporary, as some uploads might never be finished, and tus-ruby-server needs to clear expired uploads.

Shrine-tus provides three ways of moving the file uploaded into the tus server to permanent storage, which differ in performance and level of decoupling. But regardless of the approach you choose, the code for attaching the uploaded file works the same:

class VideoUploader < Shrine
  # ...
end
class Movie < Sequel::Model
  include VideoUploader::Attachment.new(:video)
end
file_data #=> {"id":"http://tus-server.org/68db42638388ae645ab747b36a837a79", "storage":"cache", "metadata":{...}}
Movie.create(video: file_data)

See shrine-tus-demo for an example application that uses shrine-tus.

Note that by default Shrine won't extract metadata from directly upload files, instead it will just copy metadata that was extracted on the client side. See this section for the rationale and instructions on how to opt in.

Approach A: Downloading through tus server

Conceptionally the simplest setup is to have Shrine download the uploaded file through your tus server app. This is also the most decoupled option, because your main app can remain completely oblivious to what storage the tus server app uses internally, or in which programming language is it written (e.g. you could swap it for tusd and everything should continue to work the same).

To use this approach, you need to assign Shrine::Storage::Tus as your temporary storage:

require "shrine/storage/tus"

Shrine.storages = {
  cache: Shrine::Storage::YourTemporaryStorage.new(...),
  store: Shrine::Storage::YourPermanentStorage.new(...),
  tus:   Shrine::Storage::Tus.new
}
class VideoUploader < Shrine
  storages[:cache] = storages[:tus] # set Shrine::Storage::Tus as temporary storage
end

Shrine::Storage::Tus is a subclass of Shrine::Storage::Url, which uses Down for downloading. By default, the Down::Http backend is used, which is implemented using HTTP.rb.

If you're experiencing a lot of network hiccups while downloading, you might want to consider switching to the Down::Wget backend, as wget automatically resumes the download in case of network hiccups.

Shrine::Storage::Tus.new(downloader: :wget)

Approach B: Downloading directly from storage

While the appoach A is decoupled from the tus server implementation, it might not be the most performant depending on the overhead between your main app and the tus server app, how well the web server that you use for the tus server app handles streaming downloads (whether it blocks the web worker, thus affecting the app's request throughput), and whether you have hard request timeout limits like on Heroku. Down will also temporarily cache downloaded content to disk while copying to the permanent storage, so that adds I/O and disk usage.

Instead of downloading through the tus server app, you can download directly from the underlying storage that it uses. This requires that you have the tus storage configured in your main app (which might already by the case if you're running tus server in the same process as your main app), and you can pass that storage to Shrine::Storage::Tus:

Shrine::Storage::Tus.new(tus_storage: Tus::Server.opts[:storage])

Approach C: Tus storage equals Shrine storage

Approach B internally utilizes the common interface of each tus storage object for streaming the file uploaded to that storage, via #each. However, certain Shrine storage classes have optimizations when copying/moving a file between two storages of the same kind.

So if you want to use the same kind of permanent storage as your tus server uses, you can reap those performance benefits. In order to do this, instead of using Shrine::Storage::Tus as your temporary Shrine storage as we did in approaches A and B, we will be using a regular Shrine storage which will match the storage that the tus server uses. In other words, your Shrine storage and your tus storage would reference the same files. So, if your tus server app is configured with either of the following storages:

Tus::Server.opts[:storage] = Tus::Storage::Filesystem.new("data")
Tus::Server.opts[:storage] = Tus::Storage::Gridfs.new(client: mongo, prefix: "tus")
Tus::Server.opts[:storage] = Tus::Storage::S3.new(prefix: "tus", **s3_options)

Then Shrine should be configured with the corresponding temporary storage:

Shrine.storages[:cache] = Shrine::Storage::FileSystem.new("data")
Shrine.storages[:cache] = Shrine::Storage::Gridfs.new(client: mongo, prefix: "tus")
Shrine.storages[:cache] = Shrine::Storage::S3.new(prefix: "tus", **s3_options)

In approaches A and B we didn't need to change the file data received from the client, because we were using a subclass of Shrine::Storage::Url, which accepts the id field as a URL. But with this approach the id field will need to be translated from the tus URL to the correct ID for your temporary Shrine storage, using the tus plugin that ships with shrine-tus.

Shrine.storages = {
  cache: Shrine::Storage::YourTemporaryStorage.new(...),
  store: Shrine::Storage::YourPermanentStorage.new(...),
}
class VideoUploader < Shrine
  plugin :tus
end

Note that it's not recommended to use the delete_promoted Shrine plugin with this this approach, because depending on the tus storage implementation it could cause HEAD requests to the tus server app to return a success for files that were deleted by Shrine.

These are the performance advantages for each of the official storages:

Filesystem

Shrine::Storage::FileSystem will have roughly the same performance as in option B, though it will allocate less memory. However, if you load the moving plugin, Shrine will execute a mv command between the tus storage and permanent storage, which executes instantaneously regardless of the filesize.

Shrine.plugin :moving

Mongo GridFS

Shrine::Storage::Gridfs will use more efficient copying, resulting in up to 2x speedup according to my benchmarks.

AWS S3

Shrine::Storage::S3 will issue a single S3 COPY request for files smaller than 100MB, while files 100MB or larger will be divided into multiple chunks which will be copied individually and in parallel using S3's multipart API.

Contributing

$ bundle exec rake test

License

MIT