-module(nova_file_controller). -export([ get_file/1, get_dir/1 ]). -include_lib("kernel/include/file.hrl"). get_file(#{extra_state := #{static := File, options := Options}}) -> Filepath = get_filepath(File), MimeType = case maps:get(mimetype, Options, undefined) of undefined -> {T, V, _} = cow_mimetypes:web(erlang:list_to_binary(Filepath)), <>; MType -> MType end, %% We can just build the full path #{size := Size} = file_info("", Filepath), {sendfile, 200, #{}, {0, Size, Filepath}, MimeType}; get_file(_Req) -> {status, 404}. get_dir(#{extra_state := #{pathinfo := Pathinfo, static := Dir, options := Options}} = Req) -> %% This case will be invoked if a directory was set with wildcard - pathinfo will then %% contain the segments of the wildcard value Filepath = get_filepath(Dir), Filepath0 = lists:foldl(fun(F, Acc) -> filename:join(Acc, binary_to_list(F)) end, Filepath, Pathinfo), case filelib:is_dir(Filepath0) of false -> %% Check if it's a directory case filelib:is_file(Filepath0) of true -> %% It's a file get_file(Req#{extra_state => #{static => {file, Filepath0}, options => Options}}); false -> {status, 404} end; true -> get_dir(Req#{extra_state => #{static => {dir, Filepath0}, options => Options}}) end; get_dir(#{path := Path, extra_state := #{static := Dir, options := Options}}) -> Filepath = get_filepath(Dir), {ok, Files} = file:list_dir(Filepath), case get_index_file(Files, maps:get(index_files, Options, ["index.html"])) of {ok, IndexFile} -> get_file(#{extra_state => #{static => {file, filename:join(Filepath, IndexFile)}, options => Options}}); false -> case maps:get(list_dir, Options, false) of false -> %% We will not show the directory listing {status, 403}; true -> {ok, Files} = file:list_dir(Filepath), FileInfos = [file_info(Filepath, F) || F <- Files], ParentDir = case re:replace(Path , "/[^/]+/?$", "") of [[]] -> undefined; Parent -> Parent end, {ok, #{date => calendar:local_time(), parent_dir => ParentDir, path => Path, files => FileInfos}} end end; get_dir(_Req) -> {status, 404}. %%%%%%%%%%%%%%%%%%%%%%%% %% Internal functions %% %%%%%%%%%%%%%%%%%%%%%%%% get_index_file([], _) -> false; get_index_file([File|Tl], IndexFiles) -> case lists:member(File, IndexFiles) of true -> {ok, File}; false -> get_index_file(Tl, IndexFiles) end. get_filepath({file, LocalFile}) -> LocalFile; get_filepath({priv_file, App, PrivFile}) -> filename:join(code:priv_dir(App), PrivFile); get_filepath({dir, LocalPath}) -> LocalPath; get_filepath({priv_dir, App, LocalPath}) -> filename:join(code:priv_dir(App), LocalPath). file_info(Filepath, Filename) -> case file:read_file_info(filename:join(Filepath, Filename)) of {ok, #file_info{type = Type, size = Size, mtime = LastModified}} -> #{type => Type, size => Size, last_modified => LastModified, filename => Filename}; _ -> undefined end.