Merge branch 'master' of github.com:processone/ejabberd
This commit is contained in:
commit
8bff06bea9
19
Makefile.in
19
Makefile.in
|
@ -112,12 +112,19 @@ spec:
|
|||
$(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \
|
||||
'case xml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.'
|
||||
|
||||
TO_DEST=$(foreach path,$(1),$(if $(filter deps/%,$(path)),$(patsubst deps/%,$(LIBDIR)/%,$(path)),$(patsubst %,$(LIBDIR)/ejabberd/%,$(path))))
|
||||
JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1))
|
||||
|
||||
ELIXIR_TO_DEST=$(LIBDIR) $(wordlist 2,2,$(1)) $(wordlist 5,1000,$(1))
|
||||
DEPS_TO_DEST=$(LIBDIR) $(wordlist 2,1000,$(1))
|
||||
MAIN_TO_DEST=$(LIBDIR) ejabberd $(1)
|
||||
TO_DEST_SINGLE=$(if $(subst XdepsX,,X$(word 1,$(1))X),$(call MAIN_TO_DEST,$(1)),$(if $(subst XlibX,,X$(word 3,$(1))X),$(call DEPS_TO_DEST,$(1)),$(call ELIXIR_TO_DEST,$(1))))
|
||||
TO_DEST=$(foreach path,$(1),$(call JOIN_PATHS,$(call TO_DEST_SINGLE,$(subst /, ,$(path)))))
|
||||
|
||||
FILTER_DIRS=$(foreach path,$(1),$(if $(wildcard $(path)/*),,$(path)))
|
||||
FILES_WILDCARD=$(call FILTER_DIRS,$(foreach w,$(1),$(wildcard $(w))))
|
||||
|
||||
DEPS_FILES:=$(call FILES_WILDCARD,deps/*/ebin/*.beam deps/*/ebin/*.app deps/*/priv/* deps/*/priv/lib/* deps/*/priv/bin/* deps/*/include/*.hrl)
|
||||
DEPS_FILES_FILTERED:=$(filter-out %/epam,$(DEPS_FILES))
|
||||
DEPS_FILES:=$(call FILES_WILDCARD,deps/*/ebin/*.beam deps/*/ebin/*.app deps/*/priv/* deps/*/priv/lib/* deps/*/priv/bin/* deps/*/include/*.hrl deps/*/lib/*/ebin/*.beam deps/*/lib/*/ebin/*.app)
|
||||
DEPS_FILES_FILTERED:=$(filter-out %/epam deps/elixir/ebin/elixir.app,$(DEPS_FILES))
|
||||
DEPS_DIRS:=$(sort deps/ $(wildcard deps/*) $(dir $(DEPS_FILES)))
|
||||
|
||||
MAIN_FILES:=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl))
|
||||
|
@ -129,7 +136,7 @@ endef
|
|||
|
||||
$(foreach file,$(DEPS_FILES_FILTERED) $(MAIN_FILES),$(eval $(call COPY_template,$(file))))
|
||||
|
||||
$(call TO_DEST,$(MAIN_DIRS) $(DEPS_DIRS)):
|
||||
$(sort $(call TO_DEST,$(MAIN_DIRS) $(DEPS_DIRS))):
|
||||
$(INSTALL) -d $@
|
||||
|
||||
$(call TO_DEST,deps/p1_pam/priv/bin/epam): $(LIBDIR)/%: deps/% $(call TO_DEST,deps/p1_pam/priv/bin/)
|
||||
|
@ -172,10 +179,6 @@ install: all copy-files
|
|||
-e "s*@installuser@*$(INIT_USER)*" ejabberd.init.template \
|
||||
> ejabberd.init
|
||||
chmod 755 ejabberd.init
|
||||
# Install Elixir and Elixir dependancies
|
||||
-$(INSTALL) -m 644 deps/*/lib/*/ebin/*.app $(BEAMDIR)
|
||||
-$(INSTALL) -m 644 deps/*/lib/*/ebin/*.beam $(BEAMDIR)
|
||||
rm -f $(BEAMDIR)/configure.beam
|
||||
#
|
||||
# Binary C programs
|
||||
$(INSTALL) -d $(PBINDIR)
|
||||
|
|
|
@ -169,6 +169,18 @@
|
|||
#
|
||||
#CONTRIB_MODULES_PATH=/opt/ejabberd-modules
|
||||
|
||||
#.
|
||||
#' CONTRIB_MODULES_CONF_DIR: configuration directory for contributed modules
|
||||
#
|
||||
# Specify the full path to the configuration directory for contributed ejabberd
|
||||
# modules. In order to configure a module named mod_foo, a mod_foo.yml file can
|
||||
# be created in this directory. This file will then be used instead of the
|
||||
# default configuration file provided with the module.
|
||||
#
|
||||
# Default: $CONTRIB_MODULES_PATH/conf
|
||||
#
|
||||
#CONTRIB_MODULES_CONF_DIR=/etc/ejabberd/modules
|
||||
|
||||
#.
|
||||
#'
|
||||
# vim: foldmarker=#',#. foldmethod=marker:
|
||||
|
|
|
@ -156,6 +156,7 @@ export ERL_INETRC
|
|||
export ERL_MAX_PORTS
|
||||
export ERL_MAX_ETS_TABLES
|
||||
export CONTRIB_MODULES_PATH
|
||||
export CONTRIB_MODULES_CONF_DIR
|
||||
export ERL_LIBS
|
||||
|
||||
# start server
|
||||
|
@ -227,6 +228,21 @@ iexlive()
|
|||
--erl \"$ERLANG_OPTS\" --erl $ARGS --erl \"$@\""
|
||||
}
|
||||
|
||||
# start server in the foreground
|
||||
foreground()
|
||||
{
|
||||
check_start
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME $ERLANG_NODE \
|
||||
-noinput \
|
||||
-pa $EJABBERD_EBIN_PATH \
|
||||
$MNESIA_OPTS \
|
||||
$KERNEL_OPTS \
|
||||
$EJABBERD_OPTS \
|
||||
-s ejabberd \
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
}
|
||||
|
||||
debugwarning()
|
||||
{
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
|
@ -299,11 +315,12 @@ help()
|
|||
{
|
||||
echo ""
|
||||
echo "Commands to start an ejabberd node:"
|
||||
echo " start Start an ejabberd node in server mode"
|
||||
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
|
||||
echo " iexdebug Attach an interactive Elixir shell to a running ejabberd node"
|
||||
echo " live Start an ejabberd node in live (interactive) mode"
|
||||
echo " iexlive Start an ejabberd node in live (interactive) mode, within an Elixir shell"
|
||||
echo " start Start an ejabberd node in server mode"
|
||||
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
|
||||
echo " iexdebug Attach an interactive Elixir shell to a running ejabberd node"
|
||||
echo " live Start an ejabberd node in live (interactive) mode"
|
||||
echo " iexlive Start an ejabberd node in live (interactive) mode, within an Elixir shell"
|
||||
echo " foreground Start an ejabberd node in server mode (attached)"
|
||||
echo ""
|
||||
echo "Optional parameters when starting an ejabberd node:"
|
||||
echo " --config-dir dir Config ejabberd: $ETC_DIR"
|
||||
|
@ -467,6 +484,7 @@ case $ARGS in
|
|||
' iexdebug') iexdebug;;
|
||||
' live') live;;
|
||||
' iexlive') iexlive;;
|
||||
' foreground') foreground;;
|
||||
' ping'*) ping ${ARGS# ping};;
|
||||
' etop') etop;;
|
||||
' started') wait_for_status 0 30 2;; # wait 30x2s before timeout
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
-module(mod_http_upload).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
|
||||
-protocol({xep, 363, '0.1'}).
|
||||
|
||||
-define(GEN_SERVER, gen_server).
|
||||
-define(SERVICE_REQUEST_TIMEOUT, 5000). % 5 seconds.
|
||||
-define(SLOT_TIMEOUT, 18000000). % 5 hours.
|
||||
|
@ -719,6 +721,12 @@ iq_disco_info(Lang, Name) ->
|
|||
|
||||
%% HTTP request handling.
|
||||
|
||||
-spec store_file(file:filename_all(), binary(),
|
||||
integer() | undefined,
|
||||
integer() | undefined,
|
||||
binary(), binary(), boolean())
|
||||
-> ok | {ok, [{binary(), binary()}], binary()} | {error, term()}.
|
||||
|
||||
store_file(Path, Data, FileMode, DirMode, GetPrefix, LocalPath, Thumbnail) ->
|
||||
case do_store_file(Path, Data, FileMode, DirMode) of
|
||||
ok when Thumbnail ->
|
||||
|
@ -747,7 +755,9 @@ store_file(Path, Data, FileMode, DirMode, GetPrefix, LocalPath, Thumbnail) ->
|
|||
Err
|
||||
end.
|
||||
|
||||
-spec do_store_file(file:filename_all(), binary(), integer(), integer())
|
||||
-spec do_store_file(file:filename_all(), binary(),
|
||||
integer() | undefined,
|
||||
integer() | undefined)
|
||||
-> ok | {error, term()}.
|
||||
|
||||
do_store_file(Path, Data, FileMode, DirMode) ->
|
||||
|
@ -838,7 +848,7 @@ code_to_message(_Code) -> <<"">>.
|
|||
identify(Path) ->
|
||||
Cmd = lists:flatten(io_lib:fwrite("identify -format \"ok %m %h %w\" ~s",
|
||||
[Path])),
|
||||
Res = os:cmd(Cmd),
|
||||
Res = string:strip(os:cmd(Cmd), right, $\n),
|
||||
case string:tokens(Res, " ") of
|
||||
["ok", T, H, W] ->
|
||||
{ok, #media_info{
|
||||
|
@ -869,7 +879,7 @@ convert(Path, #media_info{type = T, width = W, height = H}) ->
|
|||
{ok, OutPath};
|
||||
Err ->
|
||||
?ERROR_MSG("failed to convert ~s to ~s: ~s",
|
||||
[Path, OutPath, Err]),
|
||||
[Path, OutPath, string:strip(Err, right, $\n)]),
|
||||
pass
|
||||
end;
|
||||
true ->
|
||||
|
@ -880,7 +890,7 @@ convert(Path, #media_info{type = T, width = W, height = H}) ->
|
|||
-spec thumb_el(string(), binary()) -> xmlel().
|
||||
|
||||
thumb_el(Path, URI) ->
|
||||
ContentType = guess_content_type(Path),
|
||||
ContentType = guess_content_type(list_to_binary(Path)),
|
||||
case identify(Path) of
|
||||
{ok, #media_info{height = H, width = W}} ->
|
||||
#xmlel{name = <<"thumbnail">>,
|
||||
|
|
|
@ -0,0 +1,345 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%%% File : mod_http_upload_quota.erl
|
||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%% Purpose : Quota management for HTTP File Upload (XEP-0363)
|
||||
%%% Created : 15 Oct 2015 by Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(mod_http_upload_quota).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
|
||||
-define(GEN_SERVER, gen_server).
|
||||
-define(PROCNAME, ?MODULE).
|
||||
-define(TIMEOUT, timer:hours(24)).
|
||||
-define(INITIAL_TIMEOUT, timer:minutes(10)).
|
||||
-define(FORMAT(Error), file:format_error(Error)).
|
||||
|
||||
-behaviour(?GEN_SERVER).
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% gen_mod/supervisor callbacks.
|
||||
-export([start_link/3,
|
||||
start/2,
|
||||
stop/1,
|
||||
mod_opt_type/1]).
|
||||
|
||||
%% gen_server callbacks.
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
terminate/2,
|
||||
code_change/3]).
|
||||
|
||||
%% ejabberd_hooks callback.
|
||||
-export([handle_slot_request/5]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("logger.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
-record(state,
|
||||
{server_host :: binary(),
|
||||
access_soft_quota :: atom(),
|
||||
access_hard_quota :: atom(),
|
||||
max_days :: pos_integer() | infinity,
|
||||
docroot :: binary(),
|
||||
disk_usage = dict:new() :: term(),
|
||||
timers :: [timer:tref()]}).
|
||||
|
||||
-type state() :: #state{}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_mod/supervisor callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec start_link(binary(), atom(), gen_mod:opts())
|
||||
-> {ok, pid()} | ignore | {error, _}.
|
||||
|
||||
start_link(ServerHost, Proc, Opts) ->
|
||||
?GEN_SERVER:start_link({local, Proc}, ?MODULE, {ServerHost, Opts}, []).
|
||||
|
||||
-spec start(binary(), gen_mod:opts()) -> {ok, _} | {ok, _, _} | {error, _}.
|
||||
|
||||
start(ServerHost, Opts) ->
|
||||
Proc = mod_http_upload:get_proc_name(ServerHost, ?PROCNAME),
|
||||
Spec = {Proc,
|
||||
{?MODULE, start_link, [ServerHost, Proc, Opts]},
|
||||
permanent,
|
||||
3000,
|
||||
worker,
|
||||
[?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, Spec).
|
||||
|
||||
-spec stop(binary()) -> ok.
|
||||
|
||||
stop(ServerHost) ->
|
||||
Proc = mod_http_upload:get_proc_name(ServerHost, ?PROCNAME),
|
||||
ok = supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
ok = supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
|
||||
mod_opt_type(access_soft_quota) ->
|
||||
fun(A) when is_atom(A) -> A end;
|
||||
mod_opt_type(access_hard_quota) ->
|
||||
fun(A) when is_atom(A) -> A end;
|
||||
mod_opt_type(max_days) ->
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
mod_opt_type(_) ->
|
||||
[access_soft_quota, access_hard_quota, max_days].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec init({binary(), gen_mod:opts()}) -> {ok, state()}.
|
||||
|
||||
init({ServerHost, Opts}) ->
|
||||
process_flag(trap_exit, true),
|
||||
AccessSoftQuota = gen_mod:get_opt(access_soft_quota, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
soft_upload_quota),
|
||||
AccessHardQuota = gen_mod:get_opt(access_hard_quota, Opts,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
hard_upload_quota),
|
||||
MaxDays = gen_mod:get_opt(max_days, Opts,
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
end,
|
||||
infinity),
|
||||
DocRoot1 = gen_mod:get_module_opt(ServerHost, mod_http_upload, docroot,
|
||||
fun iolist_to_binary/1,
|
||||
<<"@HOME@/upload">>),
|
||||
DocRoot2 = mod_http_upload:expand_home(str:strip(DocRoot1, right, $/)),
|
||||
Timers = if MaxDays == infinity -> [];
|
||||
true ->
|
||||
{ok, T1} = timer:send_after(?INITIAL_TIMEOUT, sweep),
|
||||
{ok, T2} = timer:send_interval(?TIMEOUT, sweep),
|
||||
[T1, T2]
|
||||
end,
|
||||
ejabberd_hooks:add(http_upload_slot_request, ServerHost, ?MODULE,
|
||||
handle_slot_request, 50),
|
||||
{ok, #state{server_host = ServerHost,
|
||||
access_soft_quota = AccessSoftQuota,
|
||||
access_hard_quota = AccessHardQuota,
|
||||
max_days = MaxDays,
|
||||
docroot = DocRoot2,
|
||||
timers = Timers}}.
|
||||
|
||||
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
|
||||
|
||||
handle_call(Request, From, State) ->
|
||||
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_cast(_, state()) -> {noreply, state()}.
|
||||
|
||||
handle_cast({handle_slot_request, #jid{user = U, server = S} = JID, Path, Size},
|
||||
#state{server_host = ServerHost,
|
||||
access_soft_quota = AccessSoftQuota,
|
||||
access_hard_quota = AccessHardQuota,
|
||||
disk_usage = DiskUsage} = State) ->
|
||||
HardQuota = case acl:match_rule(ServerHost, AccessHardQuota, JID) of
|
||||
Hard when is_integer(Hard), Hard > 0 ->
|
||||
Hard * 1024 * 1024;
|
||||
_ ->
|
||||
0
|
||||
end,
|
||||
SoftQuota = case acl:match_rule(ServerHost, AccessSoftQuota, JID) of
|
||||
Soft when is_integer(Soft), Soft > 0 ->
|
||||
Soft * 1024 * 1024;
|
||||
_ ->
|
||||
0
|
||||
end,
|
||||
OldSize = case dict:find({U, S}, DiskUsage) of
|
||||
{ok, Value} ->
|
||||
Value;
|
||||
error ->
|
||||
undefined
|
||||
end,
|
||||
NewSize = case {HardQuota, SoftQuota} of
|
||||
{0, 0} ->
|
||||
?DEBUG("No quota specified for ~s",
|
||||
[jlib:jid_to_string(JID)]),
|
||||
Size;
|
||||
{0, _} ->
|
||||
?WARNING_MSG("No hard quota specified for ~s",
|
||||
[jlib:jid_to_string(JID)]),
|
||||
enforce_quota(Path, Size, OldSize, SoftQuota, SoftQuota);
|
||||
{_, 0} ->
|
||||
?WARNING_MSG("No soft quota specified for ~s",
|
||||
[jlib:jid_to_string(JID)]),
|
||||
enforce_quota(Path, Size, OldSize, HardQuota, HardQuota);
|
||||
_ when SoftQuota > HardQuota ->
|
||||
?WARNING_MSG("Bad quota for ~s (soft: ~p, hard: ~p)",
|
||||
[jlib:jid_to_string(JID),
|
||||
SoftQuota, HardQuota]),
|
||||
enforce_quota(Path, Size, OldSize, SoftQuota, SoftQuota);
|
||||
_ ->
|
||||
?DEBUG("Enforcing quota for ~s",
|
||||
[jlib:jid_to_string(JID)]),
|
||||
enforce_quota(Path, Size, OldSize, SoftQuota, HardQuota)
|
||||
end,
|
||||
{noreply, State#state{disk_usage = dict:store({U, S}, NewSize, DiskUsage)}};
|
||||
handle_cast(Request, State) ->
|
||||
?ERROR_MSG("Got unexpected request: ~p", [Request]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_info(_, state()) -> {noreply, state()}.
|
||||
|
||||
handle_info(sweep, #state{server_host = ServerHost,
|
||||
docroot = DocRoot,
|
||||
max_days = MaxDays} = State)
|
||||
when is_integer(MaxDays), MaxDays > 0 ->
|
||||
?DEBUG("Got 'sweep' message for ~s", [ServerHost]),
|
||||
case file:list_dir(DocRoot) of
|
||||
{ok, Entries} ->
|
||||
BackThen = secs_since_epoch() - (MaxDays * 86400),
|
||||
DocRootS = binary_to_list(DocRoot),
|
||||
PathNames = lists:map(fun(Entry) -> DocRootS ++ "/" ++ Entry end,
|
||||
Entries),
|
||||
UserDirs = lists:filter(fun filelib:is_dir/1, PathNames),
|
||||
lists:foreach(fun(UserDir) -> delete_old_files(UserDir, BackThen) end,
|
||||
UserDirs);
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Cannot open document root ~s: ~s",
|
||||
[DocRoot, ?FORMAT(Error)])
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info(Info, State) ->
|
||||
?ERROR_MSG("Got unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
|
||||
|
||||
terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
|
||||
?DEBUG("Stopping upload quota process for ~s: ~p", [ServerHost, Reason]),
|
||||
ejabberd_hooks:delete(http_upload_slot_request, ServerHost, ?MODULE,
|
||||
handle_slot_request, 50),
|
||||
lists:foreach(fun(Timer) -> timer:cancel(Timer) end, Timers).
|
||||
|
||||
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
|
||||
|
||||
code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
|
||||
?DEBUG("Updating upload quota process for ~s", [ServerHost]),
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% ejabberd_hooks callback.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec handle_slot_request(term(), jid(), binary(), non_neg_integer(), binary())
|
||||
-> term().
|
||||
|
||||
handle_slot_request(allow, #jid{lserver = ServerHost} = JID, Path, Size,
|
||||
_Lang) ->
|
||||
Proc = mod_http_upload:get_proc_name(ServerHost, ?PROCNAME),
|
||||
?GEN_SERVER:cast(Proc, {handle_slot_request, JID, Path, Size}),
|
||||
allow;
|
||||
handle_slot_request(Acc, _JID, _Path, _Size, _Lang) -> Acc.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec enforce_quota(file:filename_all(), non_neg_integer(),
|
||||
non_neg_integer() | undefined, non_neg_integer(),
|
||||
non_neg_integer())
|
||||
-> non_neg_integer().
|
||||
|
||||
enforce_quota(_UserDir, SlotSize, OldSize, _MinSize, MaxSize)
|
||||
when is_integer(OldSize), OldSize + SlotSize =< MaxSize ->
|
||||
OldSize + SlotSize;
|
||||
enforce_quota(UserDir, SlotSize, _OldSize, MinSize, MaxSize) ->
|
||||
Files = lists:sort(fun({_PathA, _SizeA, TimeA}, {_PathB, _SizeB, TimeB}) ->
|
||||
TimeA > TimeB
|
||||
end, gather_file_info(UserDir)),
|
||||
{DelFiles, OldSize, NewSize} =
|
||||
lists:foldl(fun({_Path, Size, _Time}, {[], AccSize, AccSize})
|
||||
when AccSize + Size + SlotSize =< MinSize ->
|
||||
{[], AccSize + Size, AccSize + Size};
|
||||
({Path, Size, _Time}, {[], AccSize, AccSize}) ->
|
||||
{[Path], AccSize + Size, AccSize};
|
||||
({Path, Size, _Time}, {AccFiles, AccSize, NewSize}) ->
|
||||
{[Path | AccFiles], AccSize + Size, NewSize}
|
||||
end, {[], 0, 0}, Files),
|
||||
if OldSize + SlotSize > MaxSize ->
|
||||
lists:foreach(fun(File) -> del_file_and_dir(File) end, DelFiles),
|
||||
file:del_dir(UserDir), % In case it's empty, now.
|
||||
NewSize + SlotSize;
|
||||
true ->
|
||||
OldSize + SlotSize
|
||||
end.
|
||||
|
||||
-spec delete_old_files(file:filename_all(), integer()) -> ok.
|
||||
|
||||
delete_old_files(UserDir, Timestamp) ->
|
||||
FileInfo = gather_file_info(UserDir),
|
||||
case [Path || {Path, _Size, Time} <- FileInfo, Time < Timestamp] of
|
||||
[] ->
|
||||
ok;
|
||||
OldFiles ->
|
||||
lists:foreach(fun(File) -> del_file_and_dir(File) end, OldFiles),
|
||||
file:del_dir(UserDir) % In case it's empty, now.
|
||||
end.
|
||||
|
||||
-spec gather_file_info(file:filename_all())
|
||||
-> [{binary(), non_neg_integer(), non_neg_integer()}].
|
||||
|
||||
gather_file_info(Dir) when is_binary(Dir) ->
|
||||
gather_file_info(binary_to_list(Dir));
|
||||
gather_file_info(Dir) ->
|
||||
case file:list_dir(Dir) of
|
||||
{ok, Entries} ->
|
||||
lists:foldl(fun(Entry, Acc) ->
|
||||
Path = Dir ++ "/" ++ Entry,
|
||||
case file:read_file_info(Path, [{time, posix}]) of
|
||||
{ok, #file_info{type = directory}} ->
|
||||
gather_file_info(Path) ++ Acc;
|
||||
{ok, #file_info{type = regular,
|
||||
mtime = Time,
|
||||
size = Size}} ->
|
||||
[{Path, Size, Time} | Acc];
|
||||
{ok, _Info} ->
|
||||
?DEBUG("Won't stat(2) non-regular file ~s",
|
||||
[Path]),
|
||||
Acc;
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Cannot stat(2) ~s: ~s",
|
||||
[Path, ?FORMAT(Error)]),
|
||||
Acc
|
||||
end
|
||||
end, [], Entries);
|
||||
{error, enoent} ->
|
||||
?DEBUG("Directory ~s doesn't exist", [Dir]),
|
||||
[];
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Cannot open directory ~s: ~s", [Dir, ?FORMAT(Error)]),
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec del_file_and_dir(file:name_all()) -> ok.
|
||||
|
||||
del_file_and_dir(File) ->
|
||||
case file:delete(File) of
|
||||
ok ->
|
||||
?INFO_MSG("Removed ~s", [File]),
|
||||
Dir = filename:dirname(File),
|
||||
case file:del_dir(Dir) of
|
||||
ok ->
|
||||
?DEBUG("Removed ~s", [Dir]);
|
||||
{error, Error} ->
|
||||
?INFO_MSG("Cannot remove ~s: ~s", [Dir, ?FORMAT(Error)])
|
||||
end;
|
||||
{error, Error} ->
|
||||
?WARNING_MSG("Cannot remove ~s: ~s", [File, ?FORMAT(Error)])
|
||||
end.
|
||||
|
||||
-spec secs_since_epoch() -> non_neg_integer().
|
||||
|
||||
secs_since_epoch() ->
|
||||
{MegaSecs, Secs, _MicroSecs} = os:timestamp(),
|
||||
MegaSecs * 1000000 + Secs.
|
|
@ -25,7 +25,7 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
-module(mod_mam).
|
||||
|
||||
-protocol({xep, 313, '0.3'}).
|
||||
-protocol({xep, 313, '0.4'}).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
|
|
Loading…
Reference in New Issue