Skip to content

Instantly share code, notes, and snippets.

@dgvncsz0f
Last active September 13, 2016 04:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dgvncsz0f/9b42ff130ffecf155359d3ae73bdb255 to your computer and use it in GitHub Desktop.
Save dgvncsz0f/9b42ff130ffecf155359d3ae73bdb255 to your computer and use it in GitHub Desktop.
zipflow + erlcloud
defmodule ErlCloud do
require Record
import Record, only: [defrecord: 2, extract: 2]
defrecord :aws_config, extract(:aws_config, from_lib: "erlcloud/include/erlcloud_aws.hrl")
end
defmodule Yyy.YyyController do
use Yyy.Web, :controller
require ErlCloud
alias Zipflow
defp bucket, do: System.get_env("S3_BUCKET") |> String.to_char_list
defp aws_region, do: System.get_env("AWS_REGION") |> String.to_char_list
defp aws_access_key, do: System.get_env("AWS_ACCESS_KEY_ID") |> String.to_char_list
defp aws_secret_key, do: System.get_env("AWS_SECRET_ACCESS_KEY") |> String.to_char_list
defp parse_int!(n) do
case Integer.parse(n) do
{i, ""} -> i
end
end
defp s3cfg do
:erlcloud_s3.new(aws_access_key, aws_secret_key, aws_region)
|> ErlCloud.aws_config(timeout: 5000)
end
@chunk_size 1024 * 1024
defp download(path, acc, download_fn) do
size = :erlcloud_s3.get_object_metadata(bucket, path, [], s3cfg)
|> Keyword.fetch!(:content_length)
|> List.to_string
|> parse_int!
download(path, 0, size, acc, download_fn)
end
defp download(path, offset, size, acc, download_fn) do
if offset >= size do
download_fn.(acc, :done)
else
limit = offset + @chunk_size - 1
range = "bytes=#{offset}-#{limit}"
reply = :erlcloud_s3.get_object(bucket, path, [range: range], s3cfg) # will raise an except. on error
clength = reply[:content_length]
|> List.to_string
|> parse_int!
{:ok, acc1} = download_fn.(acc, {:more, reply[:content]})
download(path, offset + clength, size, acc1, download_fn)
end
end
defp s3_entry(printer, path) do
header = Zipflow.Spec.LFH.encode(printer, path) # path is the name of this entry [any path will do]
download(String.to_char_list(path), Zipflow.Spec.StoreEntry.init(printer), fn
ctx, :done -> {header, Zipflow.Spec.StoreEntry.term(ctx)} # or {:error, reason}
ctx, {:more, data} -> {:ok, Zipflow.Spec.StoreEntry.data(ctx, data)} # or {:error, reason}
end)
end
defp stream(zipflow, _, []), do: zipflow
defp stream(zipflow, printer, [path | paths]) do
zipflow
|> Zipflow.Stream.entry(s3_entry(printer, path))
|> stream(printer, paths)
end
# /yyy action
def yyy(conn, _params) do
conn = conn
|> put_resp_header("content-type", "application/zip")
|> put_resp_header("content-disposition", "attachment; filename=\"yyy.zip\"")
|> send_chunked(200)
printer = & chunk(conn, &1)
Zipflow.Stream.init
|> stream(printer, ["foobar", "foobaz"])
|> Zipflow.Stream.flush(printer)
conn
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment