defmodule Store.Project.Entry.Metadata do defstruct [:store_path, :source_file] @filename "metadata.json" @behaviour Store.Project.EntryFile @impl Store.Project.EntryFile def new(entry_path, source_file) do %__MODULE__{ store_path: Path.join(entry_path, @filename), source_file: source_file } end @impl Store.Project.EntryFile def exists?(file), do: file |> store_path() |> File.exists?() @impl Store.Project.EntryFile def store_path(file), do: file.store_path @impl Store.Project.EntryFile def read(file) do file.store_path |> File.read() |> case do {:ok, contents} -> SafeJson.decode(contents) error -> error end end @impl Store.Project.EntryFile def write(file, data \\ %{}) do data = if is_map(data), do: data, else: %{} rel_path = Map.get(data, :rel_path) || Map.get(data, "rel_path") # Callers that know the content hash upfront (git mode passes the blob # SHA straight through from ls-tree) should set `hash:` in `data`. # Otherwise we fall back to hashing the working-tree file so this # function remains usable from test / tooling call sites that bypass # the Source-aware save pipeline. hash = Map.get(data, :hash) || Map.get(data, "hash") || mkhash(file.source_file) %{ file: rel_path || file.source_file, timestamp: DateTime.utc_now(), hash: hash } |> SafeJson.encode() |> case do {:ok, json} -> File.write(file.store_path, json) error -> error end end defp mkhash(file) do :crypto.hash(:sha256, File.read!(file)) |> Base.encode16(case: :lower) end end