* tools/ejabberdctl: Added call to "exec" (thanks to Sergei

Golovan)

* src/msgs/ru.msg: Updated (thanks to Sergei Golovan)

* src/mod_vcard.erl: Support for searching of prefix substring and
limiting of result items (thanks to Sergei Golovan)

* src/mod_offline.erl: Support for message expiration (JEP-0023)
(thanks to Sergei Golovan)
* src/jlib.hrl: Added NS_EXPIRE macros (thanks to Sergei Golovan)

* src/ejabberd_logger_h.erl: Added reopen_log/0 (thanks to Sergei
Golovan)

* src/ejabberd_ctl.erl: Added return codes, updated "reopen-log"
command, added "delete-expired-messages" and "status" commands
(thanks to Sergei Golovan)

* doc/guide.tex: Updated (thanks to Sergei Golovan)

SVN Revision: 264
This commit is contained in:
Alexey Shchepin 2004-09-10 20:57:00 +00:00
parent 210a9a689b
commit e0ede61e0f
10 changed files with 290 additions and 82 deletions

View File

@ -1,3 +1,30 @@
2004-09-10 Alexey Shchepin <alexey@sevcom.net>
* tools/ejabberdctl: Added call to "exec" (thanks to Sergei
Golovan)
* src/msgs/ru.msg: Updated (thanks to Sergei Golovan)
* src/mod_vcard.erl: Support for searching of prefix substring and
limiting of result items (thanks to Sergei Golovan)
* src/mod_offline.erl: Support for message expiration (JEP-0023)
(thanks to Sergei Golovan)
* src/jlib.hrl: Added NS_EXPIRE macros (thanks to Sergei Golovan)
* src/ejabberd_logger_h.erl: Added reopen_log/0 (thanks to Sergei
Golovan)
* src/ejabberd_ctl.erl: Added return codes, updated "reopen-log"
command, added "delete-expired-messages" and "status" commands
(thanks to Sergei Golovan)
* doc/guide.tex: Updated (thanks to Sergei Golovan)
2004-09-04 Alexey Shchepin <alexey@sevcom.net>
* src/mod_roster.erl: Removed useless transactions
2004-08-28 Alexey Shchepin <alexey@sevcom.net>
* doc/guide.tex: Fix (thanks to Sander Devrieze)

View File

@ -1131,6 +1131,8 @@ Options:
\titem{search} Specifies wheather search is enabled (value is \term{true}, default) or
disabled (value is \term{false}) by the service. If \term{search} is set to \term{false},
option \term{host} is ignored and service does not appear in Jabber Discovery items.
\titem{matches} Limits the number of reported search results. If value is set to
\term{infinity} then all search results are reported. Default value is \term{30}.
\end{description}
Example:
@ -1138,7 +1140,7 @@ Example:
{modules,
[
...
{mod_vcard, [{search, false}]}
{mod_vcard, [{search, false}, {matches, 20}]}
...
]}.
\end{verbatim}

View File

@ -11,124 +11,166 @@
-export([start/0]).
-define(STATUS_SUCCESS, 0).
-define(STATUS_ERROR, 1).
-define(STATUS_USAGE, 2).
-define(STATUS_BADRPC, 3).
start() ->
case init:get_plain_arguments() of
[SNode | Args] ->
Node = list_to_atom(SNode),
process(Node, Args);
Status = process(Node, Args),
halt(Status);
_ ->
print_usage()
end,
halt().
print_usage(),
halt(?STATUS_USAGE)
end.
process(Node, ["status"]) ->
case rpc:call(Node, init, get_status, []) of
{badrpc, Reason} ->
io:format("Can't get node ~p status: ~p~n",
[Node, Reason]),
?STATUS_BADRPC;
{InternalStatus, ProvidedStatus} ->
io:format("Node ~p is ~p. Status: ~p~n",
[Node, InternalStatus, ProvidedStatus]),
?STATUS_SUCCESS
end;
process(Node, ["stop"]) ->
case rpc:call(Node, init, stop, []) of
{badrpc, Reason} ->
io:format("Can't stop node ~p: ~p~n",
[Node, Reason]);
[Node, Reason]),
?STATUS_BADRPC;
_ ->
ok
?STATUS_SUCCESS
end;
process(Node, ["restart"]) ->
case rpc:call(Node, init, restart, []) of
{badrpc, Reason} ->
io:format("Can't restart node ~p: ~p~n",
[Node, Reason]);
[Node, Reason]),
?STATUS_BADRPC;
_ ->
ok
?STATUS_SUCCESS
end;
process(Node, ["reopen-log"]) ->
{error_logger, Node} ! {emulator, noproc, reopen};
case rpc:call(Node, ejabberd_logger_h, reopen_log, []) of
{badrpc, Reason} ->
io:format("Can't reopen node ~p log: ~p~n",
[Node, Reason]),
?STATUS_BADRPC;
_ ->
?STATUS_SUCCESS
end;
process(Node, ["register", User, Password]) ->
case rpc:call(Node, ejabberd_auth, try_register, [User, Password]) of
{atomic, ok} ->
ok;
?STATUS_SUCCESS;
{atomic, exists} ->
io:format("User ~p already registered on node ~p~n",
[User, Node]);
[User, Node]),
?STATUS_ERROR;
{error, Reason} ->
io:format("Can't register user ~p on node ~p: ~p~n",
[User, Node, Reason]);
[User, Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't register user ~p on node ~p: ~p~n",
[User, Node, Reason])
[User, Node, Reason]),
?STATUS_BADRPC
end;
process(Node, ["unregister", User]) ->
case rpc:call(Node, ejabberd_auth, remove_user, [User]) of
{atomic, ok} ->
ok;
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't unregister user ~p on node ~p: ~p~n",
[User, Node, Reason]);
[User, Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't unregister user ~p on node ~p: ~p~n",
[User, Node, Reason])
[User, Node, Reason]),
?STATUS_BADRPC
end;
process(Node, ["backup", Path]) ->
case rpc:call(Node, mnesia, backup, [Path]) of
ok ->
ok;
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't store backup in ~p on node ~p: ~p~n",
[Path, Node, Reason]);
[Path, Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't store backup in ~p on node ~p: ~p~n",
[Path, Node, Reason])
[Path, Node, Reason]),
?STATUS_BADRPC
end;
process(Node, ["dump", Path]) ->
case rpc:call(Node, mnesia, dump_to_textfile, [Path]) of
ok ->
ok;
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't store dump in ~p on node ~p: ~p~n",
[Path, Node, Reason]);
[Path, Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't store dump in ~p on node ~p: ~p~n",
[Path, Node, Reason])
[Path, Node, Reason]),
?STATUS_BADRPC
end;
process(Node, ["load", Path]) ->
case rpc:call(Node, mnesia, load_textfile, [Path]) of
ok ->
ok;
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't load dump in ~p on node ~p: ~p~n",
[Path, Node, Reason]);
[Path, Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't load dump in ~p on node ~p: ~p~n",
[Path, Node, Reason])
[Path, Node, Reason]),
?STATUS_BADRPC
end;
process(Node, ["restore", Path]) ->
case rpc:call(Node,
mnesia, restore, [Path, [{default_op, keep_tables}]]) of
{atomic, ok} ->
ok;
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't restore backup from ~p on node ~p: ~p~n",
[Path, Node, Reason]);
[Path, Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't restore backup from ~p on node ~p: ~p~n",
[Path, Node, Reason])
[Path, Node, Reason]),
?STATUS_BADRPC
end;
process(Node, ["install-fallback", Path]) ->
case rpc:call(Node, mnesia, install_fallback, [Path]) of
{atomic, ok} ->
ok;
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't install fallback from ~p on node ~p: ~p~n",
[Path, Node, Reason]);
[Path, Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't install fallback from ~p on node ~p: ~p~n",
[Path, Node, Reason])
[Path, Node, Reason]),
?STATUS_BADRPC
end;
process(Node, ["registered-users"]) ->
@ -138,14 +180,30 @@ process(Node, ["registered-users"]) ->
SUsers = lists:sort(Users),
FUsers = lists:map(fun(U) -> [U, NewLine] end, SUsers),
io:format("~s", [FUsers]),
ok;
{ErrorTag, Reason} when (ErrorTag == error) or (ErrorTag == badrpc) ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't get list of registered users on node ~p: ~p~n",
[Node, Reason])
[Node, Reason]),
?STATUS_ERROR;
{badrpc, Reason} ->
io:format("Can't get list of registered users on node ~p: ~p~n",
[Node, Reason]),
?STATUS_BADRPC
end;
process(Node, ["delete-expired-messages"]) ->
case rpc:call(Node, mod_offline, remove_expired_messages, []) of
{badrpc, Reason} ->
io:format("Can't delete expired messages at node ~p: ~p~n",
[Node, Reason]),
?STATUS_BADRPC;
_ ->
?STATUS_SUCCESS
end;
process(_Node, _Args) ->
print_usage().
print_usage(),
?STATUS_USAGE.
@ -154,6 +212,7 @@ print_usage() ->
"Usage: ejabberdctl node command~n"
"~n"
"Available commands:~n"
" status\t\t\tget ejabberd status~n"
" stop\t\t\t\tstop ejabberd~n"
" restart\t\t\trestart ejabberd~n"
" reopen-log\t\t\treopen log file~n"
@ -165,6 +224,7 @@ print_usage() ->
" dump file\t\t\tdump a database in a text file~n"
" load file\t\t\trestore a database from a text file~n"
" registered-users\t\tlist all registered users~n"
" delete-expired-messages\tdelete expired offline messages from database~n"
"~n"
"Example:~n"
" ejabberdctl ejabberd@host restart~n"

View File

@ -15,7 +15,7 @@
%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2,
code_change/3]).
code_change/3, reopen_log/0]).
-record(state, {fd, file}).
@ -89,6 +89,9 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
reopen_log() ->
error_logger ! {emulator, noproc, reopen}.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------

View File

@ -21,6 +21,7 @@
-define(NS_XDATA, "jabber:x:data").
-define(NS_IQDATA, "jabber:iq:data").
-define(NS_DELAY, "jabber:x:delay").
-define(NS_EXPIRE, "jabber:x:expire").
-define(NS_EVENT, "jabber:x:event").
-define(NS_XCONFERENCE, "jabber:x:conference").
-define(NS_STATS, "http://jabber.org/protocol/stats").

View File

@ -17,12 +17,14 @@
store_packet/3,
resend_offline_messages/1,
pop_offline_messages/2,
remove_expired_messages/0,
remove_old_messages/1,
remove_user/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(offline_msg, {user, timestamp, from, to, packet}).
-record(offline_msg, {user, timestamp, expire, from, to, packet}).
-define(PROCNAME, ejabberd_offline).
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
@ -32,6 +34,7 @@ start(_) ->
[{disc_only_copies, [node()]},
{type, bag},
{attributes, record_info(fields, offline_msg)}]),
update_table(),
ejabberd_hooks:add(offline_message_hook,
?MODULE, store_packet, 50),
ejabberd_hooks:add(offline_subscription_hook,
@ -92,8 +95,11 @@ store_packet(From, To, Packet) ->
true ->
#jid{luser = LUser} = To,
TimeStamp = now(),
{xmlelement, _Name, _Attrs, Els} = Packet,
Expire = find_x_expire(TimeStamp, Els),
?PROCNAME ! #offline_msg{user = LUser,
timestamp = TimeStamp,
expire = Expire,
from = From,
to = To,
packet = Packet},
@ -150,6 +156,34 @@ find_x_event([El | Els]) ->
find_x_event(Els)
end.
find_x_expire(_, []) ->
never;
find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
find_x_expire(TimeStamp, Els);
find_x_expire(TimeStamp, [El | Els]) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EXPIRE ->
case xml:get_tag_attr_s("seconds", El) of
Val ->
case catch list_to_integer(Val) of
{'EXIT', _} ->
never;
Int when Int > 0 ->
{MegaSecs, Secs, MicroSecs} = TimeStamp,
S = MegaSecs * 1000000 + Secs + Int,
MegaSecs1 = S div 1000000,
Secs1 = S rem 1000000,
{MegaSecs1, Secs1, MicroSecs};
_ ->
never
end;
_ ->
never
end;
_ ->
find_x_expire(TimeStamp, Els)
end.
resend_offline_messages(User) ->
LUser = jlib:nodeprep(User),
@ -187,23 +221,54 @@ pop_offline_messages(Ls, User) ->
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
lists:map(
fun(R) ->
{xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
{route,
R#offline_msg.from,
R#offline_msg.to,
{xmlelement, Name, Attrs,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]}}
end,
Ls ++ lists:keysort(#offline_msg.timestamp, Rs));
TS = now(),
Ls ++ lists:map(
fun(R) ->
{xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
{route,
R#offline_msg.from,
R#offline_msg.to,
{xmlelement, Name, Attrs,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]}}
end,
lists:filter(
fun(R) ->
case R#offline_msg.expire of
never ->
true;
TimeStamp ->
TS < TimeStamp
end
end,
lists:keysort(#offline_msg.timestamp, Rs)));
_ ->
Ls
end.
remove_expired_messages() ->
TimeStamp = now(),
F = fun() ->
mnesia:write_lock_table(offline_msg),
mnesia:foldl(
fun(Rec, _Acc) ->
case Rec#offline_msg.expire of
never ->
ok;
TS ->
if
TS < TimeStamp ->
mnesia:delete_object(Rec);
true ->
ok
end
end
end, ok, offline_msg)
end,
mnesia:transaction(F).
remove_old_messages(Days) ->
{MegaSecs, Secs, _MicroSecs} = now(),
S = MegaSecs * 1000000 + Secs - 60 * 60 * 24 * Days,
@ -227,3 +292,29 @@ remove_user(User) ->
mnesia:delete({offline_msg, LUser})
end,
mnesia:transaction(F).
update_table() ->
Fields = record_info(fields, offline_msg),
case mnesia:table_info(offline_msg, attributes) of
Fields ->
ok;
[user, timestamp, from, to, packet] ->
?INFO_MSG("Converting offline_msg table from "
"{user, timestamp, from, to, packet} format", []),
mnesia:transform_table(
offline_msg,
fun({_, U, TS, F, T, P}) ->
{xmlelement, _Name, _Attrs, Els} = P,
Expire = find_x_expire(TS, Els),
#offline_msg{user = U,
timestamp = TS,
expire = Expire,
from = F,
to = T,
packet = P}
end, Fields);
_ ->
?INFO_MSG("Recreating offline_msg table", []),
mnesia:transform_table(last_activity, ignore, Fields)
end.

View File

@ -1,7 +1,7 @@
%%%----------------------------------------------------------------------
%%% File : mod_roster.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose :
%%% Purpose : Roster management
%%% Created : 11 Dec 2002 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
@ -90,11 +90,8 @@ process_local_iq(From, To, #iq{type = Type} = IQ) ->
process_iq_get(From, _To, #iq{sub_el = SubEl} = IQ) ->
#jid{luser = LUser} = From,
F = fun() ->
mnesia:index_read(roster, LUser, #roster.user)
end,
case mnesia:transaction(F) of
{atomic, Items} ->
case catch mnesia:dirty_index_read(roster, LUser, #roster.user) of
Items when is_list(Items) ->
XItems = lists:map(fun item_to_xml/1, Items),
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
@ -316,11 +313,8 @@ push_item(User, Resource, From, Item) ->
get_subscription_lists(User) ->
LUser = jlib:nodeprep(User),
F = fun() ->
mnesia:index_read(roster, LUser, #roster.user)
end,
case mnesia:transaction(F) of
{atomic, Items} ->
case mnesia:dirty_index_read(roster, LUser, #roster.user) of
Items when is_list(Items) ->
fill_subscription_lists(Items, [], []);
_ ->
{[], []}

View File

@ -22,7 +22,8 @@
-include("jlib.hrl").
%-define(JUD_ALLOW_RETURN_ALL, true)
%-define(JUD_ALLOW_RETURN_ALL, true).
-define(JUD_MATCHES, 30).
-record(vcard_search, {user, luser,
fn, lfn,
@ -234,8 +235,10 @@ set_vcard(User, VCARD) ->
[{xmlcdata, translate:translate(Lang, "Search users in ") ++
jlib:jid_to_string(JID)}]},
{xmlelement, "instructions", [],
[{xmlcdata, translate:translate(Lang, "Fill in fields to search "
"for any matching Jabber User")}]},
[{xmlcdata, translate:translate(Lang, "Fill in the form to search "
"for any matching Jabber User "
"(Add * to the end of field to "
"match substring)")}]},
?TLFIELD("text-single", "User", "user"),
?TLFIELD("text-single", "Full Name", "fn"),
?TLFIELD("text-single", "Name", "given"),
@ -452,7 +455,17 @@ search(Data) ->
?ERROR_MSG("~p", [Reason]),
[];
Rs ->
Rs
case gen_mod:get_module_opt(?MODULE, matches, 30) of
infinity ->
Rs;
Val when is_integer(Val) and Val > 0 ->
lists:sublist(Rs, Val);
Val ->
?ERROR_MSG("Illegal option value ~p. "
"Default value ~p substituted.",
[{matches, Val}, ?JUD_MATCHES]),
lists:sublist(Rs, ?JUD_MATCHES)
end
end.
-else.
@ -469,7 +482,17 @@ search(Data) ->
?ERROR_MSG("~p", [Reason]),
[];
Rs ->
Rs
case gen_mod:get_module_opt(?MODULE, matches, ?JUD_MATCHES) of
infinity ->
Rs;
Val when is_integer(Val) and (Val > 0) ->
lists:sublist(Rs, Val);
Val ->
?ERROR_MSG("Illegal option value ~p. "
"Default value ~p substituted.",
[{matches, Val}, ?JUD_MATCHES]),
lists:sublist(Rs, ?JUD_MATCHES)
end
end
end.
@ -499,24 +522,31 @@ filter_fields([{SVar, [Val]} | Ds], Match)
when is_list(Val) and (Val /= "") ->
LVal = stringprep:tolower(Val),
NewMatch = case SVar of
"user" -> Match#vcard_search{luser = LVal};
"fn" -> Match#vcard_search{lfn = LVal};
"family" -> Match#vcard_search{lfamily = LVal};
"given" -> Match#vcard_search{lgiven = LVal};
"middle" -> Match#vcard_search{lmiddle = LVal};
"nickname" -> Match#vcard_search{lnickname = LVal};
"bday" -> Match#vcard_search{lbday = LVal};
"ctry" -> Match#vcard_search{lctry = LVal};
"locality" -> Match#vcard_search{llocality = LVal};
"email" -> Match#vcard_search{lemail = LVal};
"orgname" -> Match#vcard_search{lorgname = LVal};
"orgunit" -> Match#vcard_search{lorgunit = LVal};
"user" -> Match#vcard_search{luser = make_val(LVal)};
"fn" -> Match#vcard_search{lfn = make_val(LVal)};
"family" -> Match#vcard_search{lfamily = make_val(LVal)};
"given" -> Match#vcard_search{lgiven = make_val(LVal)};
"middle" -> Match#vcard_search{lmiddle = make_val(LVal)};
"nickname" -> Match#vcard_search{lnickname = make_val(LVal)};
"bday" -> Match#vcard_search{lbday = make_val(LVal)};
"ctry" -> Match#vcard_search{lctry = make_val(LVal)};
"locality" -> Match#vcard_search{llocality = make_val(LVal)};
"email" -> Match#vcard_search{lemail = make_val(LVal)};
"orgname" -> Match#vcard_search{lorgname = make_val(LVal)};
"orgunit" -> Match#vcard_search{lorgunit = make_val(LVal)};
_ -> Match
end,
filter_fields(Ds, NewMatch);
filter_fields([_ | Ds], Match) ->
filter_fields(Ds, Match).
make_val(Val) ->
case lists:suffix("*", Val) of
true ->
lists:sublist(Val, length(Val) - 1) ++ '_';
_ ->
Val
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View File

@ -74,8 +74,8 @@
{"You need an x:data capable client to search",
"Чтобы воспользоваться поиском, требуется x:data-совместимый клиент"}.
{"Search users in ", "Поиск пользователей в "}.
{"Fill in fields to search for any matching Jabber User",
"Заполните поля для поиска пользователя Jabber"}.
{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)",
"Заполните форму для поиска пользователя Jabber (Если добавить * в конец поля, то происходит поиск подстроки)"}.
{"Results of search in ", "Результаты поиска в "}.
{"User", "Пользователь"}.

View File

@ -1,4 +1,4 @@
#!/bin/sh
erl -noinput -sname ejabberdctl -s ejabberd_ctl -extra $@
exec erl -noinput -sname ejabberdctl -s ejabberd_ctl -extra $@