farmbot_os/farmbot_core/lib/asset_storage/repo/snapshot.ex

103 lines
2.6 KiB
Elixir

defmodule Farmbot.Asset.Repo.Snapshot do
@moduledoc "Opaque data type. Hash of the entire Repo."
alias Farmbot.Asset.Repo.Snapshot
defmodule Diff do
@moduledoc false
defstruct [
additions: [],
deletions: [],
updates: [],
]
end
defstruct [data: [], hash: nil]
def diff(%Snapshot{} = old, %Snapshot{} = new) do
struct(Diff, [
additions: calculate_additions(old.data, new.data),
deletions: calculate_deletions(old.data, new.data),
updates: calculate_updates(old.data, new.data)
])
end
def diff(%Snapshot{} = data) do
struct(Diff, [
additions: calculate_additions([], data.data),
deletions: calculate_deletions([], data.data),
updates: []
])
end
defp calculate_additions(old, new) do
Enum.reduce(new, [], fn(new_object, acc) ->
maybe_old_object = Enum.find(old, fn(old_object) ->
is_correct_mod? = old_object.__struct__ == new_object.__struct__
is_correct_id? = old_object.id == new_object.id
is_correct_mod? && is_correct_id?
end)
if maybe_old_object do
acc
else
[new_object | acc]
end
end)
end
# We need all the items that are not in `new`, but were in `old`
defp calculate_deletions(old, new) do
Enum.reduce(old, [], fn(old_object, acc) ->
maybe_new_object = Enum.find(new, fn(new_object) ->
is_correct_mod? = old_object.__struct__ == new_object.__struct__
is_correct_id? = old_object.id == new_object.id
is_correct_mod? && is_correct_id?
end)
if maybe_new_object do
acc
else
[old_object | acc]
end
end)
end
# We need all items that weren't added, or deleted.
defp calculate_updates(old, new) do
index = fn(%{__struct__: mod, id: id} = data) ->
{{mod, id}, data}
end
old_index = Map.new(old, index)
new_index = Map.new(new, index)
a = Map.take(new_index, Map.keys(old_index))
Enum.reduce(a, [], fn({key, val}, acc) ->
if old_index[key] != val do
[val | acc]
else
acc
end
end)
end
def md5(%Snapshot{data: data} = snapshot) do
data
|> Enum.map(&:crypto.hash(:md5, inspect(&1)))
|> fn(data) ->
:crypto.hash(:md5, data) |> Base.encode16()
end.()
|> fn(hash) ->
%{snapshot | hash: hash}
end.()
end
defimpl Inspect, for: Snapshot do
def inspect(%Snapshot{data: []}, _) do
"#Snapshot<[NULL]>"
end
def inspect(%Snapshot{hash: hash}, _) when is_binary(hash) do
"#Snapshot<#{hash}>"
end
end
end