core/mix/asset_list.ex

# Copyright(c) 2015-2023 ACCESS CO., LTD. All rights reserved.

use Croma

defmodule AntikytheraCore.Mix.AssetList do
  alias Antikythera.{GearName, Time}

  @typep keyset :: MapSet.t(String.t())

  defun write!(gear_name :: v[GearName.t()], keys :: v[[String.t()]]) :: :ok do
    content = Enum.sort(keys) |> Enum.join("\n")
    version = Mix.Project.config()[:version]
    path = Path.join(dir(), "#{gear_name}-#{version}")
    File.write!(path, content)
    IO.puts("created an asset list file: #{path}")
  end

  defun load_all(gear_name :: v[GearName.t()], threshold_time :: v[Time.t()]) :: keyset do
    Path.wildcard(Path.join(dir(), "#{gear_name}-*"))
    |> filter_relevant_paths_and_discard_others(threshold_time)
    |> Enum.reduce(MapSet.new(), &load/2)
  end

  defp filter_relevant_paths_and_discard_others(paths, threshold_time) do
    {paths_before, paths_after} = split_paths(paths, threshold_time)

    case Enum.reverse(paths_before) do
      [] ->
        paths_after

      [path_latest | paths_irrelevant] ->
        Enum.each(paths_irrelevant, &File.rm!/1)
        [path_latest | paths_after]
    end
  end

  defp split_paths(paths, threshold_time) do
    {pairs_before, pairs_after} =
      Enum.map(paths, fn path -> {mtime(path), path} end)
      |> Enum.sort()
      |> Enum.split_while(fn {time, _} -> time < threshold_time end)

    {Enum.map(pairs_before, &elem(&1, 1)), Enum.map(pairs_after, &elem(&1, 1))}
  end

  defp mtime(path) do
    {date, time} = File.stat!(path).mtime
    {Time, date, time, 0}
  end

  defunp load(path :: Path.t(), set :: keyset) :: keyset do
    IO.puts("loading an asset list file: #{path}")

    File.read!(path)
    |> String.split("\n", trim: true)
    |> Enum.into(set)
  end

  defunp dir() :: Path.t() do
    Path.join(System.user_home!(), "antikythera_gear_asset_list")
  end
end