emqx/apps/emqx_utils/src/emqx_utils_fs.erl

84 lines
2.9 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_utils_fs).
-include_lib("kernel/include/file.hrl").
-export([traverse_dir/3]).
-export([read_info/1]).
-export([canonicalize/1]).
-type fileinfo() :: #file_info{}.
-type foldfun(Acc) ::
fun((_Filepath :: file:name(), fileinfo() | {error, file:posix()}, Acc) -> Acc).
%% @doc Traverse a directory recursively and apply a fold function to each file.
%%
%% This is a safer version of `filelib:fold_files/5` which does not follow symlinks
%% and reports errors to the fold function, giving the user more control over the
%% traversal.
%% It's not an error if `Dirpath` is not a directory, in which case the fold function
%% will be called once with the file info of `Dirpath`.
-spec traverse_dir(foldfun(Acc), Acc, _Dirpath :: file:name()) ->
Acc.
traverse_dir(FoldFun, Acc, Dirpath) ->
traverse_dir(FoldFun, Acc, Dirpath, read_info(Dirpath)).
traverse_dir(FoldFun, AccIn, DirPath, {ok, #file_info{type = directory}}) ->
case file:list_dir(DirPath) of
{ok, Filenames} ->
lists:foldl(
fun(Filename, Acc) ->
AbsPath = filename:join(DirPath, Filename),
traverse_dir(FoldFun, Acc, AbsPath)
end,
AccIn,
Filenames
);
{error, Reason} ->
FoldFun(DirPath, {error, Reason}, AccIn)
end;
traverse_dir(FoldFun, Acc, AbsPath, {ok, Info}) ->
FoldFun(AbsPath, Info, Acc);
traverse_dir(FoldFun, Acc, AbsPath, {error, Reason}) ->
FoldFun(AbsPath, {error, Reason}, Acc).
-spec read_info(file:name()) ->
{ok, fileinfo()} | {error, file:posix() | badarg}.
read_info(AbsPath) ->
file:read_link_info(AbsPath, [{time, posix}, raw]).
%% @doc Canonicalize a file path.
%% Removes stray slashes and converts to a string.
-spec canonicalize(file:name()) ->
string().
canonicalize(Filename) ->
case filename:split(str(Filename)) of
Components = [_ | _] ->
filename:join(Components);
[] ->
""
end.
str(Value) ->
case unicode:characters_to_list(Value, unicode) of
Str when is_list(Str) ->
Str;
{error, _, _} ->
erlang:error(badarg, [Value])
end.