% @doc non-servery functions for static file caching % % this spams the filesystem, so it's not "pure" code -module(fd_sfc_entry). -export_type([ encoding/0, entry/0 ]). -export([ %% constructor new/1, %% accessors fs_path/1, last_modified/1, mime_type/1, encoding/1, contents/1 ]). -include("$zx_include/zx_logger.hrl"). %% types % id = not compressed -type encoding() :: none | gzip. -record(e, {fs_path :: file:filename(), last_modified :: file:date_time(), mime_type :: string(), encoding :: encoding(), contents :: binary()}). -opaque entry() :: #e{}. %% accessors fs_path(#e{fs_path = X}) -> X. last_modified(#e{last_modified = X}) -> X. mime_type(#e{mime_type = X}) -> X. encoding(#e{encoding = X}) -> X. contents(#e{contents = X}) -> X. %% API -spec new(Path) -> Result when Path :: file:filename(), Result :: {found, entry()} | not_found. % @doc % absolute file path stored in resulting record % % returns not_found if ANY I/O error occurs during the process. will be logged new(Path) -> log(info, "~tp:new(~tp)", [?MODULE, Path]), case file:read_file(Path) of {ok, Binary} -> {found, new2(Path, Binary)}; Error -> tell("~tp:new(~tp): file read error: ~tp", [?MODULE, Path, Error]), not_found end. %% can assume file exists new2(FsPath, FileBytes) -> LastModified = filelib:last_modified(FsPath), {Encoding, MimeType} = mimetype_compress(FsPath), Contents = case Encoding of none -> FileBytes; gzip -> zlib:gzip(FileBytes) end, #e{fs_path = FsPath, last_modified = LastModified, mime_type = MimeType, encoding = Encoding, contents = Contents}. mimetype_compress(FsPath) -> case string:casefold(filename:extension(FsPath)) of %% only including the ones i anticipate encountering %% plaintext formats ".css" -> {gzip, "text/css"}; ".htm" -> {gzip, "text/html"}; ".html" -> {gzip, "text/html"}; ".js" -> {gzip, "text/javascript"}; ".json" -> {gzip, "application/json"}; ".map" -> {gzip, "application/json"}; ".md" -> {gzip, "text/markdown"}; ".ts" -> {gzip, "text/x-typescript"}; ".txt" -> {gzip, "text/plain"}; %% binary formats ".gif" -> {none, "image/gif"}; ".jpg" -> {none, "image/jpeg"}; ".jpeg" -> {none, "image/jpeg"}; ".mp4" -> {none, "video/mp4"}; ".png" -> {none, "image/png"}; ".webm" -> {none, "video/webm"}; ".webp" -> {none, "image/webp"}; _ -> {none, "application/octet-stream"} end.