+2004-01-18 Alexey Shchepin <alexey@sevcom.net>
+
+ * src/ejabberd_ctl.erl: Added commands for backup processing
+
+ * src/ejabberd_c2s.erl: Added processing of xml:lang according to
+ latest XMPP-IM draft
+
+ * src/xml.erl: Added replace_tag_attr/3 function
+
+ * src/mod_roster.erl: Added auto-reply on incoming subscription
+ request according to latest XMPP-IM draft
+
+ * src/mod_offline.erl: Added pop_offline_messages/1 function
+ * src/ejabberd_c2s.erl: Updated sending of offline messages
+
2004-01-17 Alexey Shchepin <alexey@sevcom.net>
* src/mod_muc/mod_muc_room.erl: Bugfix, updated error codes
pres_last, pres_pri,
pres_timestamp,
pres_invis = false,
- privacy_list = none}).
+ privacy_list = none,
+ lang}).
%-define(DBGFSM, true).
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case xml:get_attr_s("xmlns:stream", Attrs) of
?NS_STREAM ->
+ Lang = xml:get_attr_s("xml:lang", Attrs),
case xml:get_attr_s("version", Attrs) of
"1.0" ->
Header = io_lib:format(?STREAM_HEADER,
[{"xmlns", ?NS_SASL}],
Mechs}]}),
{next_state, wait_for_sasl_auth,
- StateData#state{sasl_state = SASLState}};
+ StateData#state{sasl_state = SASLState,
+ lang = Lang}};
_ ->
case StateData#state.resource of
"" ->
{xmlelement, "stream:features", [],
[{xmlelement, "bind",
[{"xmlns", ?NS_BIND}], []}]}),
- {next_state, wait_for_bind, StateData};
+ {next_state, wait_for_bind,
+ StateData#state{lang = Lang}};
_ ->
send_element(
StateData,
{xmlelement, "stream:features", [], []}),
- {next_state, wait_for_session, StateData}
+ {next_state, wait_for_session,
+ StateData#state{lang = Lang}}
end
end;
_ ->
?STREAM_HEADER,
[StateData#state.streamid, ?MYNAME, ""]),
send_text(StateData, Header),
- {next_state, wait_for_auth, StateData}
+ {next_state, wait_for_auth, StateData#state{lang = Lang}}
end;
_ ->
Header = io_lib:format(
{xmlelement, Name, Attrs, _Els} = El,
User = StateData#state.user,
Server = StateData#state.server,
- %FromJID = {User,
- % Server,
- % StateData#state.resource},
+ % TODO: check 'from' attribute in stanza
FromJID = StateData#state.jid,
To = xml:get_attr_s("to", Attrs),
ToJID = case To of
_ ->
jlib:string_to_jid(To)
end,
+ NewEl = case xml:get_attr_s("xml:lang", Attrs) of
+ "" ->
+ case StateData#state.lang of
+ "" -> El;
+ Lang ->
+ xml:replace_tag_attr("xml:lang", Lang, El)
+ end;
+ _ ->
+ El
+ end,
NewState =
case ToJID of
error ->
"error" -> StateData;
"result" -> StateData;
_ ->
- Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED),
+ Err = jlib:make_error_reply(NewEl, ?ERR_JID_MALFORMED),
send_element(StateData, Err),
StateData
end;
server = Server,
resource = ""} ->
?DEBUG("presence_update(~p,~n\t~p,~n\t~p)",
- [FromJID, El, StateData]),
- presence_update(FromJID, El, StateData);
+ [FromJID, NewEl, StateData]),
+ presence_update(FromJID, NewEl, StateData);
_ ->
- presence_track(FromJID, ToJID, El, StateData)
+ presence_track(FromJID, ToJID, NewEl, StateData)
end;
"iq" ->
case StateData#state.privacy_list of
none ->
- ejabberd_router:route(FromJID, ToJID, El),
+ ejabberd_router:route(FromJID, ToJID, NewEl),
StateData;
_PrivList ->
- case jlib:iq_query_info(El) of
+ case jlib:iq_query_info(NewEl) of
#iq{xmlns = ?NS_PRIVACY} = IQ ->
process_privacy_iq(
FromJID, ToJID, IQ, StateData);
_ ->
ejabberd_router:route(
- FromJID, ToJID, El),
+ FromJID, ToJID, NewEl),
StateData
end
end;
"message" ->
- ejabberd_router:route(FromJID, ToJID, El),
+ ejabberd_router:route(FromJID, ToJID, NewEl),
StateData;
_ ->
StateData
FromUnavail ->
% TODO: watching ourself
- catch mod_offline:resend_offline_messages(
- StateData#state.user),
+ resend_offline_messages(StateData),
presence_broadcast_first(
From, StateData#state{pres_last = Packet,
pres_invis = false
NewStateData.
+resend_offline_messages(StateData) ->
+ case catch mod_offline:pop_offline_messages(StateData#state.user) of
+ {'EXIT', _Reason} ->
+ ok;
+ Rs when list(Rs) ->
+ lists:foreach(
+ fun({route, From, To, {xmlelement, Name, Attrs, Els}}) ->
+ Attrs2 = jlib:replace_from_to_attrs(
+ jlib:jid_to_string(From),
+ jlib:jid_to_string(To),
+ Attrs),
+ send_element(StateData, {xmlelement, Name, Attrs2, Els})
+ end, Rs)
+ end.
+
+
[User, Node, Reason])
end;
+process(Node, ["backup", Path]) ->
+ case rpc:call(Node, mnesia, backup, [Path]) of
+ {atomic, ok} ->
+ ok;
+ {error, Reason} ->
+ io:format("Can't store backup in ~p on node ~p: ~p~n",
+ [Path, Node, Reason]);
+ {badrpc, Reason} ->
+ io:format("Can't store backup in ~p on node ~p: ~p~n",
+ [Path, Node, Reason])
+ end;
+
+process(Node, ["restore", Path]) ->
+ case rpc:call(Node,
+ mnesia, restore, [Path, [{default_op, keep_tables}]]) of
+ {atomic, ok} ->
+ ok;
+ {error, Reason} ->
+ io:format("Can't restore backup from ~p on node ~p: ~p~n",
+ [Path, Node, Reason]);
+ {badrpc, Reason} ->
+ io:format("Can't restore backup from ~p on node ~p: ~p~n",
+ [Path, Node, Reason])
+ end;
+
+process(Node, ["install-fallback", Path]) ->
+ case rpc:call(Node, mnesia, install_fallback, [Path]) of
+ {atomic, ok} ->
+ ok;
+ {error, Reason} ->
+ io:format("Can't install fallback from ~p on node ~p: ~p~n",
+ [Path, Node, Reason]);
+ {badrpc, Reason} ->
+ io:format("Can't install fallback from ~p on node ~p: ~p~n",
+ [Path, Node, Reason])
+ end;
+
process(_Node, _Args) ->
print_usage().
"Available commands:~n"
" stop\t\t\t\tstop ejabberd~n"
" restart\t\t\trestart ejabberd~n"
- " register user password\tregister user~n"
- " unregister user\t\tunregister user~n"
+ " register user password\tregister a user~n"
+ " unregister user\t\tunregister a user~n"
+ " backup file\t\t\tstore a backup in file~n"
+ " restore file\t\t\trestore a backup from file~n"
+ " install-fallback file\t\tinstall a fallback from file~n"
"~n"
"Example:~n"
" ejabberdctl ejabberd@host restart~n"
stop/0,
store_packet/3,
resend_offline_messages/1,
+ pop_offline_messages/1,
remove_old_messages/1,
remove_user/1]).
resend_offline_messages(User) ->
LUser = jlib:nodeprep(User),
F = fun() ->
- Rs = mnesia:read({offline_msg, LUser}),
+ Rs = mnesia:wread({offline_msg, LUser}),
mnesia:delete({offline_msg, LUser}),
Rs
end,
ok
end.
+pop_offline_messages(User) ->
+ LUser = jlib:nodeprep(User),
+ F = fun() ->
+ Rs = mnesia:wread({offline_msg, LUser}),
+ mnesia:delete({offline_msg, LUser}),
+ Rs
+ 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,
+ lists:keysort(#offline_msg.timestamp, Rs));
+ _ ->
+ []
+ end.
+
remove_old_messages(Days) ->
{MegaSecs, Secs, _MicroSecs} = now(),
S = MegaSecs * 1000000 + Secs - 60 * 60 * 24 * Days,
Item#roster.ask,
Type)
end,
+ AutoReply = case Direction of
+ out ->
+ none;
+ in ->
+ in_auto_reply(Item#roster.subscription,
+ Item#roster.ask,
+ Type)
+ end,
case NewState of
none ->
- none;
+ {none, AutoReply};
{Subscription, Pending} ->
NewItem = Item#roster{subscription = Subscription,
ask = Pending},
mnesia:write(NewItem),
- {push, NewItem}
+ {{push, NewItem}, AutoReply}
end
end,
case mnesia:transaction(F) of
- {atomic, ok} ->
- false;
- {atomic, {push, Item}} ->
- push_item(User, {"", ?MYNAME, ""}, Item),
- true;
+ {atomic, {Push, AutoReply}} ->
+ case AutoReply of
+ none ->
+ ok;
+ _ ->
+ T = case AutoReply of
+ subscribed -> "subscribed";
+ unsubscribed -> "unsubscribed"
+ end,
+ ejabberd_router:route(
+ {User, ?MYNAME, ""}, JID1,
+ {xmlelement, "presence", [{"type", T}], []})
+ end,
+ case Push of
+ {push, Item} ->
+ push_item(User, {"", ?MYNAME, ""}, Item),
+ true;
+ none ->
+ false
+ end;
_ ->
false
end.
out_state_change(both, none, unsubscribe) -> {from, none};
out_state_change(both, none, unsubscribed) -> {to, none}.
+in_auto_reply(from, none, subscribe) -> subscribed;
+in_auto_reply(from, out, subscribe) -> subscribed;
+in_auto_reply(both, none, subscribe) -> subscribed;
+in_auto_reply(none, in, unsubscribe) -> unsubscribed;
+in_auto_reply(none, both, unsubscribe) -> unsubscribed;
+in_auto_reply(to, in, unsubscribe) -> unsubscribed;
+in_auto_reply(from, none, unsubscribe) -> unsubscribed;
+in_auto_reply(from, out, unsubscribe) -> unsubscribed;
+in_auto_reply(both, none, unsubscribe) -> unsubscribed;
+in_auto_reply(_, _, _) -> none.
+
remove_user(User) ->
LUser = jlib:nodeprep(User),
get_attr/2, get_attr_s/2,
get_tag_attr/2, get_tag_attr_s/2,
get_subtag/2,
- get_path_s/2]).
+ get_path_s/2,
+ replace_tag_attr/3]).
element_to_string(El) ->
case El of
get_path_s(El, [cdata]) ->
get_tag_cdata(El).
+
+replace_tag_attr(Attr, Value, {xmlelement, Name, Attrs, Els}) ->
+ Attrs1 = lists:keydelete(Attr, 1, Attrs),
+ Attrs2 = [{Attr, Value} | Attrs1],
+ {xmlelement, Name, Attrs2, Els}.
+
+