From ea6d651e12d3ace658f1ef473cbf82e974c6d488 Mon Sep 17 00:00:00 2001 From: Tres Wong-Godfrey Date: Sat, 2 Sep 2017 02:14:36 -0700 Subject: [PATCH] Mod: Allow Stored Filenames To Be Saved And Retrieved This update allows ArcEcto to store and retrieve saved file information. This can be useful for many cases, but is necessary when handling file format conversions that can cause many files to be generated from a single input file (i.e PDF => JPG or Animated GIF => PNG). This change goes along with the changes in https://github.com/stavro/arc/pull/215 +|+|+|+|+|+|+|+|+|+ |+|+|+|+|+|+|+|+|+| +|+|+|+|+|+|+|+|+|+ |+|+|+|+|+|+|+|+|+| [][][][][][][][][][ +|+|+|+|+|+|+|+|+|+ ][][][][][][][][][] |+|+|+|+|+|+|+|+|+| [][][][][][][][][][ +|+|+|+|+|+|+|+|+|+ ][][][][][][][][][] [][][][][][][][][][ PdPdPdPdPdPdPdPdPdP ][][][][][][][][][] dPdPdPdPdPdPdPdPdPd [][][][][][][][][][ PdPdPdPdPdPdPdPdPdP dPdPdPdPdPdPdPdPdPd PdPdPdPdPdPdPdPdPdP dPdPdPdPdPdPdPdPdPd PdPdPdPdPdPdPdPdPdP Yvonne Adams --- lib/arc_ecto/type.ex | 74 ++++++++++++++++++++++++++++++++------------ test/schema_test.exs | 2 +- test/type_test.exs | 12 +++++++ 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/lib/arc_ecto/type.ex b/lib/arc_ecto/type.ex index 98f7115..c729394 100644 --- a/lib/arc_ecto/type.ex +++ b/lib/arc_ecto/type.ex @@ -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 @@ -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 diff --git a/test/schema_test.exs b/test/schema_test.exs index 86f71a1..1e0aa1c 100644 --- a/test/schema_test.exs +++ b/test/schema_test.exs @@ -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? diff --git a/test/type_test.exs b/test/type_test.exs index 4f5a7ef..f1e96e1 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -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}