Packages

This package provides cache manifest file with digest and file or inline assets helper for .eex templates.

Current section

Files

Jump to
plug_offline lib plug plug_offline.ex
Raw

lib/plug/plug_offline.ex

defmodule Plug.PlugOffline do
import Plug.Conn
@spec init(map) :: map
def init(%{cache_digest: true} = options) do
put_in(options[:digest], cache_key(options[:cache], options[:base_path]))
end
def init(options) do
options
end
@spec call(map, map) :: map
def call(conn, options) do
if "/#{conn.path_info}" == options[:at] do
conn
|> put_resp_content_type("text/cache-manifest")
|> send_resp(200, cache_content(options))
|> halt
else
conn
end
end
@spec cache_content(map) :: binary
def cache_content(options) do
digest = options[:digest] || cache_key(options[:cache], options[:base_path])
["CACHE MANIFEST", digest]
|> cache(options)
|> network(options)
|> fallback(options)
|> Enum.join("\n")
end
# When inline option present do not generate cache manifest entry for the assets file, though
# the digest is still based on the content of all assets. Which make update possible when
# assets changes
@spec cache(list(binary), map) :: list(String.t)
defp cache(body, %{offline_asset: true, inline: true}) do
body
end
defp cache(body, opts) do
body ++ opts[:cache]
end
# https://bordeltabernacle.github.io/2016/01/04/notes-on-elixir-pattern-matching-maps.html
@spec network(list(String.t), map) :: list(String.t)
defp network(body, %{network: network_opts}) do
body ++ ["NETWORK:" | network_opts]
end
defp network(body, _opts) do
body
end
@spec fallback(list(String.t), map) :: list(String.t)
defp fallback(body, %{fallback: fallback_opts}) do
body ++ ["FALLBACK:" | fallback_opts]
end
defp fallback(body, _opts) do
body
end
@spec cache_key(nil, String.t) :: String.t
defp cache_key(nil, _path) do
''
end
@spec cache_key(list(String.t), String.t) :: String.t
defp cache_key(keys, base_path) do
keys
|> Enum.sort
|> Enum.map(&digest_file(&1, base_path))
|> Enum.join
|> magic_comment
end
@spec digest_file(String.t, nil) :: String.t
defp digest_file(file_name, nil) do
read_file(file_name)
end
@spec digest_file(String.t, String.t) :: String.t
defp digest_file(file_name, base_path) do
read_file(Path.join([base_path, file_name]))
end
@spec read_file(String.t) :: String.t
defp read_file(file) do
case File.read(file) do
{:ok, body} ->
:sha256 |> :crypto.hash(body) |> Base.encode16
{:error, _reason} -> ''
end
end
@spec magic_comment(String.t) :: String.t
defp magic_comment(text) do
"# #{:sha256 |> :crypto.hash(text) |> Base.encode16}"
end
end