82 lines
2.3 KiB
Elixir
Executable file
82 lines
2.3 KiB
Elixir
Executable file
#!/usr/bin/env elixir
|
|
|
|
defmodule TimestampRestorer do
|
|
@environment System.get_env("MIX_ENV", "dev")
|
|
@db_path "_build/#{@environment}/timestamp-database"
|
|
|
|
def sha_all(opts \\ []) do
|
|
timestamp_database = load_timestamp_database()
|
|
|
|
"**/*.{ex,exs,beam}"
|
|
|> Path.wildcard()
|
|
|> Enum.reduce(%{}, fn filename, acc ->
|
|
{sha, timestamp} = process(filename, timestamp_database, opts)
|
|
Map.put(acc, sha, timestamp)
|
|
end)
|
|
|> write_timestamp_database()
|
|
end
|
|
|
|
defp load_timestamp_database() do
|
|
if File.exists?(@db_path) do
|
|
@db_path
|
|
|> File.read!()
|
|
|> String.split("\n")
|
|
|> Enum.reduce(%{}, fn line, acc ->
|
|
[sha, timestamp_string] = String.split(line, ":")
|
|
{timestamp, ""} = Integer.parse(timestamp_string)
|
|
Map.put(acc, sha, timestamp)
|
|
end)
|
|
else
|
|
%{}
|
|
end
|
|
end
|
|
|
|
defp write_timestamp_database(database) do
|
|
File.mkdir_p!(Path.dirname(@db_path))
|
|
|
|
database
|
|
|> Enum.map(fn {key, value} -> "#{key}:#{value}" end)
|
|
|> Enum.join("\n")
|
|
|> (& File.write!(@db_path, &1)).()
|
|
end
|
|
|
|
defp process(filename, timestamp_database, opts) do
|
|
{verbose, _opts} = Keyword.pop(opts, :verbose, false)
|
|
sha = sha(filename)
|
|
{:ok, %{mtime: new_timestamp}} = File.lstat(filename, time: :posix)
|
|
|
|
case Map.get(timestamp_database, sha) do
|
|
nil ->
|
|
log("[NEW SHA ] #{filename}: #{new_timestamp}", verbose)
|
|
timestamp when timestamp < new_timestamp ->
|
|
log("[RESTORED ] #{filename}: #{timestamp}", verbose)
|
|
File.touch(filename, timestamp)
|
|
timestamp when timestamp >= new_timestamp ->
|
|
log("[UNCHANGED] #{filename}: #{timestamp}", verbose)
|
|
end
|
|
|
|
{sha, new_timestamp}
|
|
end
|
|
|
|
defp log(_message, false), do: :ok
|
|
defp log(message, true), do: :logger.debug(message)
|
|
|
|
defp sha(filename) do
|
|
hash_ref = :crypto.hash_init(:sha)
|
|
|
|
File.stream!(filename)
|
|
|> Enum.reduce(hash_ref, fn chunk, prev_ref->
|
|
new_ref = :crypto.hash_update(prev_ref, chunk)
|
|
new_ref
|
|
end)
|
|
|> :crypto.hash_final()
|
|
|> Base.encode16()
|
|
|> String.downcase()
|
|
end
|
|
end
|
|
|
|
{opts, _args, _errors} = OptionParser.parse(System.argv(), switches: [verbose: :boolean])
|
|
|
|
{time, _result} = :timer.tc(TimestampRestorer, :sha_all, [opts])
|
|
|
|
:logger.info("Restored timestamps in #{time / 1_000_000}s")
|