Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Storing And Retrieving Output Files #82

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 54 additions & 20 deletions lib/arc_ecto/type.ex
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
defmodule Arc.Ecto.Type do
def type, do: :string

@filename_with_timestamp ~r{^(.*)\?(\d+)$}
@filename_with_timestamp ~r{^(.*)\?(\d+)(&|$)}

@saved_versions ~r/(files=)(.*)?&?/

# Support embeds_one/embeds_many
def cast(_definition, %{"file_name" => file, "updated_at" => updated_at}) do
{:ok, %{file_name: file, updated_at: updated_at}}
end

def cast(_definition, %{"file_name" => file, "updated_at" => updated_at, "saved_versions" => saved_versions}) do
{:ok, %{file_name: file, updated_at: updated_at, saved_versions: saved_versions}}
end

def cast(definition, args) do
case definition.store(args) do
{:ok, file} -> {:ok, %{file_name: file, updated_at: Ecto.DateTime.utc}}
{:ok, file, saved_versions} -> {:ok, %{file_name: file, updated_at: Ecto.DateTime.utc, saved_versions: saved_versions}}
_ -> :error
end
end

def load(_definition, value) do
{file_name, gsec} =
case Regex.match?(@filename_with_timestamp, value) do
true ->
[_, file_name, gsec] = Regex.run(@filename_with_timestamp, value)
{file_name, gsec}
_ -> {value, nil}
end

updated_at = case gsec do
gsec when is_binary(gsec) ->
gsec
|> String.to_integer()
|> :calendar.gregorian_seconds_to_datetime()
|> Ecto.DateTime.from_erl()
_ ->
nil
{file_name, gsec} = filename_from(value)
updated_at = updated_time_from(gsec)
saved_versions = saved_versions_from(value)
case is_nil(saved_versions) do
true -> {:ok, %{file_name: file_name, updated_at: updated_at}}
false -> {:ok, %{file_name: file_name, updated_at: updated_at, saved_versions: saved_versions}}
end

{:ok, %{file_name: file_name, updated_at: updated_at}}
end

def dump(_definition, %{file_name: file_name, updated_at: nil}) do
Expand All @@ -44,4 +39,43 @@ defmodule Arc.Ecto.Type do
gsec = :calendar.datetime_to_gregorian_seconds(Ecto.DateTime.to_erl(updated_at))
{:ok, "#{file_name}?#{gsec}"}
end

def dump(_definition, %{file_name: file_name, updated_at: updated_at, saved_versions: nil}) do
gsec = :calendar.datetime_to_gregorian_seconds(Ecto.DateTime.to_erl(updated_at))
{:ok, "#{file_name}?#{gsec}"}
end

def dump(_definition, %{file_name: file_name, updated_at: updated_at, saved_versions: versions}) do
gsec = :calendar.datetime_to_gregorian_seconds(Ecto.DateTime.to_erl(updated_at))
{:ok, "#{file_name}?#{gsec}&files=#{versions}"}
end

defp filename_from(value) do
case Regex.match?(@filename_with_timestamp, value) do
true ->
[_, file_name, gsec, _] = Regex.run(@filename_with_timestamp, value)
{file_name, gsec}
_ -> {value, nil}
end
end

defp saved_versions_from(value) do
case Regex.match?(@saved_versions, value) do
true ->
[_, _, val] = Regex.run(@saved_versions, value)
String.split(val, "::")
_ -> nil
end
end

defp updated_time_from(gsec) do
case gsec do
gsec when is_binary(gsec) ->
gsec
|> String.to_integer()
|> :calendar.gregorian_seconds_to_datetime()
|> Ecto.DateTime.from_erl()
_ -> nil
end
end
end
2 changes: 1 addition & 1 deletion test/schema_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ defmodule ArcTest.Ecto.Schema do
assert cs.errors == [avatar: {"can't be blank", []}]
end

test_with_mock "cascades storage success into a valid change", DummyDefinition, [store: fn({%{__struct__: Plug.Upload, path: "/path/to/my/file.png", file_name: "file.png"}, %TestUser{}}) -> {:ok, "file.png"} end] do
test_with_mock "cascades storage success into a valid change", DummyDefinition, [store: fn({%{__struct__: Plug.Upload, path: "/path/to/my/file.png", file_name: "file.png"}, %TestUser{}}) -> {:ok, "file.png", "file.png"} end] do
upload = build_upload("/path/to/my/file.png")
cs = TestUser.changeset(%TestUser{}, %{"avatar" => upload})
assert cs.valid?
Expand Down
12 changes: 12 additions & 0 deletions test/type_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ defmodule ArcTest.Ecto.Type do
assert value == %{file_name: "image.php", updated_at: timestamp}
end

test "loads filenames with an output file embedded" do
timestamp = Ecto.DateTime.cast!({{1970, 1, 1}, {0, 0, 0}})
{:ok, value} = DummyDefinition.Type.load("image.php?62167219200&files=image.png")
assert value == %{file_name: "image.php", updated_at: timestamp, saved_versions: ["image.png"]}
end

test "loads filenames with multiple output files embedded" do
timestamp = Ecto.DateTime.cast!({{1970, 1, 1}, {0, 0, 0}})
{:ok, value} = DummyDefinition.Type.load("image.php?62167219200&files=image.png::image2.png::image3.png")
assert value == %{file_name: "image.php", updated_at: timestamp, saved_versions: ["image.png", "image2.png", "image3.png"]}
end

test "loads pathological filenames without timestamps" do
{:ok, value} = DummyDefinition.Type.load("image^!?*!=@$.php")
assert value == %{file_name: "image^!?*!=@$.php", updated_at: nil}
Expand Down