25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-30 16:36:29 +01:00

Support XEP-0227 Portable Import/Export (EJAB-993)

SVN Revision: 2421
This commit is contained in:
Badlop 2009-08-05 18:23:54 +00:00
parent 92ad67a814
commit 6aa3706bec
5 changed files with 740 additions and 14 deletions

View File

@ -3334,9 +3334,19 @@ Dump internal Mnesia database to a text file dump.
Restore immediately from a text file dump. Restore immediately from a text file dump.
This is not recommended for big databases, as it will consume much time, This is not recommended for big databases, as it will consume much time,
memory and processor. In that case it&#X2019;s preferable to use <TT>backup</TT> and <TT>install-fallback</TT>. memory and processor. In that case it&#X2019;s preferable to use <TT>backup</TT> and <TT>install-fallback</TT>.
</DD><DT CLASS="dt-description"><B><TT>import-piefxis, export-piefxis, export-piefxis-host</TT></B></DT><DD CLASS="dd-description">
These options can be used to migrate accounts
using <A HREF="http://www.xmpp.org/extensions/xep-0227.html">XEP-0227</A> formatted XML files
from/to other Jabber/XMPP servers
or move users of a vhost to another ejabberd installation.
See also
<A HREF="https://support.process-one.net/doc/display/P1/ejabberd+migration+kit">ejabberd migration kit</A>.
</DD><DT CLASS="dt-description"><B><TT>import-file, import-dir</TT></B></DT><DD CLASS="dd-description"> </DD><DT CLASS="dt-description"><B><TT>import-file, import-dir</TT></B></DT><DD CLASS="dd-description">
These options can be used to migrate from other Jabber/XMPP servers. There These options can be used to migrate accounts
exist tutorials to <A HREF="http://www.ejabberd.im/migrate-to-ejabberd">migrate from other software to ejabberd</A>. using jabberd1.4 formatted XML files.
from other Jabber/XMPP servers
There exist tutorials to
<A HREF="http://www.ejabberd.im/migrate-to-ejabberd">migrate from other software to ejabberd</A>.
</DD><DT CLASS="dt-description"><B><TT>delete-expired-messages</TT></B></DT><DD CLASS="dd-description"> This option can be used to delete old messages </DD><DT CLASS="dt-description"><B><TT>delete-expired-messages</TT></B></DT><DD CLASS="dd-description"> This option can be used to delete old messages
in offline storage. This might be useful when the number of offline messages in offline storage. This might be useful when the number of offline messages
is very high. is very high.

View File

@ -4264,9 +4264,19 @@ The more interesting ones are:
memory and processor. In that case it's preferable to use \term{backup} and \term{install-fallback}. memory and processor. In that case it's preferable to use \term{backup} and \term{install-fallback}.
%%More information about backuping can %%More information about backuping can
%% be found in section~\ref{backup}. %% be found in section~\ref{backup}.
\titem{import-piefxis, export-piefxis, export-piefxis-host} \ind{migrate between servers}
These options can be used to migrate accounts
using \xepref{0227} formatted XML files
from/to other \Jabber{}/XMPP servers
or move users of a vhost to another ejabberd installation.
See also
\footahref{https://support.process-one.net/doc/display/P1/ejabberd+migration+kit}{ejabberd migration kit}.
\titem{import-file, import-dir} \ind{migration from other software} \titem{import-file, import-dir} \ind{migration from other software}
These options can be used to migrate from other \Jabber{}/XMPP servers. There These options can be used to migrate accounts
exist tutorials to \footahref{http://www.ejabberd.im/migrate-to-ejabberd}{migrate from other software to ejabberd}. using jabberd1.4 formatted XML files.
from other \Jabber{}/XMPP servers
There exist tutorials to
\footahref{http://www.ejabberd.im/migrate-to-ejabberd}{migrate from other software to ejabberd}.
\titem{delete-expired-messages} This option can be used to delete old messages \titem{delete-expired-messages} This option can be used to delete old messages
in offline storage. This might be useful when the number of offline messages in offline storage. This might be useful when the number of offline messages
is very high. is very high.

View File

@ -33,7 +33,7 @@
%% Accounts %% Accounts
register/3, unregister/2, register/3, unregister/2,
registered_users/1, registered_users/1,
%% Migration %% Migration jabberd1.4
import_file/1, import_dir/1, import_file/1, import_dir/1,
%% Purge DB %% Purge DB
delete_expired_messages/0, delete_old_messages/1, delete_expired_messages/0, delete_old_messages/1,
@ -101,11 +101,24 @@ commands() ->
module = ?MODULE, function = import_file, module = ?MODULE, function = import_file,
args = [{file, string}], result = {res, restuple}}, args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = import_dir, tags = [mnesia], #ejabberd_commands{name = import_dir, tags = [mnesia],
desc = "Import user data from jabberd14 spool dir", desc = "Import users data from jabberd14 spool dir",
module = ?MODULE, function = import_dir, module = ?MODULE, function = import_dir,
args = [{file, string}], args = [{file, string}],
result = {res, restuple}}, result = {res, restuple}},
#ejabberd_commands{name = import_piefxis, tags = [mnesia],
desc = "Import users data from a PIEFXIS file (XEP-0227)",
module = ejabberd_piefxis, function = import_file,
args = [{file, string}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis, tags = [mnesia],
desc = "Export data of all users in the server to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_server,
args = [{dir, string}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis_host, tags = [mnesia],
desc = "Export data of users in a host to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_host,
args = [{dir, string}, {host, string}], result = {res, rescode}},
#ejabberd_commands{name = delete_expired_messages, tags = [purge], #ejabberd_commands{name = delete_expired_messages, tags = [purge],
desc = "Delete expired offline messages from database", desc = "Delete expired offline messages from database",
module = ?MODULE, function = delete_expired_messages, module = ?MODULE, function = delete_expired_messages,

640
src/ejabberd_piefxis.erl Normal file
View File

@ -0,0 +1,640 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_piefxis.erl
%%% Author : Pablo Polvorin, Vidal Santiago Martinez
%%% Purpose : XEP-0227: Portable Import/Export Format for XMPP-IM Servers
%%% Created : 17 Jul 2008 by Pablo Polvorin <pablo.polvorin@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
%%% Not implemented:
%%% - Export from mod_offline_odbc.erl
%%% - Export from mod_private_odbc.erl
%%% - XEP-227: 6. Security Considerations
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
%%% - If a host has many users, split that host in XML files with 50 users each.
%%%% Headers
-module(ejabberd_piefxis).
-export([import_file/1, export_server/1, export_host/2]).
-record(parsing_state, {parser, host, dir}).
-include("ejabberd.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-include_lib("exmpp/include/exmpp_client.hrl").
%% Copied from mod_private.erl
-record(private_storage, {usns, xml}).
%%-define(ERROR_MSG(M,Args),io:format(M,Args)).
%%-define(INFO_MSG(M,Args),ok).
-define(CHUNK_SIZE,1024*20). %20k
-define(BTL, binary_to_list).
-define(LTB, list_to_binary).
-define(NS_XINCLUDE, 'http://www.w3.org/2001/XInclude').
%%%==================================
%%%% Import file
import_file(FileName) ->
import_file(FileName, 2).
import_file(FileName, RootDepth) ->
try_start_exmpp(),
Dir = filename:dirname(FileName),
{ok, IO} = try_open_file(FileName),
Parser = exmpp_xml:start_parser([{max_size,infinity},
{root_depth, RootDepth},
{emit_endtag,true}]),
read_chunks(IO, #parsing_state{parser=Parser, dir=Dir}),
file:close(IO),
exmpp_xml:stop_parser(Parser).
try_start_exmpp() ->
try exmpp:start()
catch
error:{already_started, exmpp} -> ok;
error:undef -> throw({error, exmpp_not_installed})
end.
try_open_file(FileName) ->
case file:open(FileName,[read,binary]) of
{ok, IO} -> {ok, IO};
{error, enoent} -> throw({error, {file_not_found, FileName}})
end.
%%File could be large.. we read it in chunks
read_chunks(IO,State) ->
case file:read(IO,?CHUNK_SIZE) of
{ok,Chunk} ->
NewState = process_chunk(Chunk,State),
read_chunks(IO,NewState);
eof ->
ok
end.
process_chunk(Chunk,S =#parsing_state{parser=Parser}) ->
case exmpp_xml:parse(Parser,Chunk) of
continue ->
S;
XMLElements ->
process_elements(XMLElements,S)
end.
%%%==================================
%%%% Process Elements
process_elements(Elements,State) ->
lists:foldl(fun process_element/2,State,Elements).
%%%==================================
%%%% Process Element
process_element(El=#xmlel{name=user, ns=_XMLNS},
State=#parsing_state{host=Host}) ->
case add_user(El,Host) of
{error, _Other} -> error;
_ -> ok
end,
State;
process_element(H=#xmlel{name=host},State) ->
State#parsing_state{host=exmpp_xml:get_attribute(H,"jid",none)};
process_element(#xmlel{name='server-data'},State) ->
State;
process_element(El=#xmlel{name=include, ns=?NS_XINCLUDE}, State=#parsing_state{dir=Dir}) ->
case exmpp_xml:get_attribute(El, href, none) of
none ->
ok;
HrefB ->
Href = binary_to_list(HrefB),
%%?INFO_MSG("Parse also this file: ~n~p", [Href]),
FileName = filename:join([Dir, Href]),
import_file(FileName, 1),
Href
end,
State;
process_element(#xmlcdata{cdata = _CData},State) ->
State;
process_element(#xmlendtag{ns = _NS, name='server-data'},State) ->
State;
process_element(#xmlendtag{ns = _NS, name=_Name},State) ->
State;
process_element(El,State) ->
io:format("Warning!: unknown element found: ~p ~n",[El]),
State.
%%%==================================
%%%% Add user
add_user(El, Domain) ->
User = exmpp_xml:get_attribute(El,name,none),
Password = exmpp_xml:get_attribute(El,password,none),
add_user(El, Domain, User, Password).
%% @spec El = XML element
%% Domain = String with a domain name
%% User = String with an user name
%% Password = String with an user password
%% @ret ok | {atomic, exists} | {error, not_allowed}
%% @doc Add a new user to the database.
%% If user already exists, it will be only updated.
add_user(El, Domain, User, Password) ->
case create_user(User,Password,Domain) of
ok ->
ok = exmpp_xml:foreach(
fun(_,Child) ->
populate_user(User,Domain,Child)
end,
El),
ok;
{atomic, exists} ->
?INFO_MSG("User ~p@~p already exists, using stored profile...~n",
[User, Domain]),
io:format(""),
ok = exmpp_xml:foreach(
fun(_,Child) ->
populate_user(User,Domain,Child)
end,
El);
{error, Other} ->
?ERROR_MSG("Error adding user ~s@~s: ~p~n", [User, Domain, Other])
end.
%% @spec User = String with User name
%% Password = String with a Password value
%% Domain = Stirng with a Domain name
%% @ret ok | {atomic, exists} | {error, not_allowed}
%% @doc Create a new user
create_user(User,Password,Domain) ->
case ejabberd_auth:try_register(?BTL(User),?BTL(Domain),?BTL(Password)) of
{atomic,ok} -> ok;
{atomic, exists} -> {atomic, exists};
{error, not_allowed} -> {error, not_allowed};
Other -> {error, Other}
end.
%%%==================================
%%%% Populate user
%% @spec User = String
%% Domain = String
%% El = XML element
%% @ret ok | {error, not_found}
%%
%% @doc Add a new user from a XML file with a roster list.
%%
%% Example of a file:
%% <?xml version='1.0' encoding='UTF-8'?>
%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
%% <host jid='localhost'>
%% <user name='juliet' password='s3crEt'>
%% <query xmlns='jabber:iq:roster'>
%% <item jid='romeo@montague.net'
%% name='Romeo'
%% subscription='both'>
%% <group>Friends</group>
%% </item>
%% </query>
%% </user>
%% </host>
%% </server-data>
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
io:format("Trying to add/update roster list...",[]),
case loaded_module(Domain,[mod_roster_odbc,mod_roster]) of
{ok, M} ->
case M:set_items(User, Domain, El) of
{atomic, ok} ->
io:format(" DONE.~n",[]),
ok;
_ ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("Error trying to add a new user: ~s ~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
E -> io:format(" ERROR: ~p~n",[E]),
?ERROR_MSG("No modules loaded [mod_roster, mod_roster_odbc] ~s ~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
%% @spec User = String with the user name
%% Domain = String with a domain name
%% El = Sub XML element with vCard tags values
%% @ret ok | {error, not_found}
%% @doc Read vcards from the XML and send it to the server
%%
%% Example:
%% <?xml version='1.0' encoding='UTF-8'?>
%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
%% <host jid='localhost'>
%% <user name='admin' password='s3crEt'>
%% <vCard xmlns='vcard-temp'>
%% <FN>Admin</FN>
%% </vCard>
%% </user>
%% </host>
%% </server-data>
populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
io:format("Trying to add/update vCards...",[]),
case loaded_module(Domain,[mod_vcard,mod_vcard_odbc]) of
{ok, M} -> FullUser = exmpp_jid:make(User, Domain),
IQ = #iq{type = set, payload = El},
case M:process_sm_iq(FullUser, FullUser , IQ) of
{error,_Err} ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("Error processing vcard ~s : ~p ~n",
[exmpp_xml:document_to_list(El), _Err]);
_ ->
io:format(" DONE.~n",[]), ok
end;
_ ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("No modules loaded [mod_vcard, mod_vcard_odbc] ~s ~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
%% @spec User = String with the user name
%% Domain = String with a domain name
%% El = Sub XML element with offline messages values
%% @ret ok | {error, not_found}
%% @doc Read off-line message from the XML and send it to the server
populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
io:format("Trying to add/update offline-messages...",[]),
case loaded_module(Domain, [mod_offline, mod_offline_odbc]) of
{ok, M} ->
ok = exmpp_xml:foreach(
fun (_Element, {xmlcdata, _}) ->
ok;
(_Element, Child) ->
From = exmpp_xml:get_attribute(Child,from,none),
FullFrom = exmpp_jid:parse(From),
FullUser = exmpp_jid:make(User, Domain),
_R = M:store_packet(FullFrom, FullUser, Child)
end, El), io:format(" DONE.~n",[]);
_ ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("No modules loaded [mod_offline, mod_offline_odbc] ~s ~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
%% @spec User = String with the user name
%% Domain = String with a domain name
%% El = Sub XML element with private storage values
%% @ret ok | {error, not_found}
%% @doc Private storage parsing
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
io:format("Trying to add/update private storage...",[]),
case loaded_module(Domain,[mod_private_odbc,mod_private]) of
{ok, M} ->
FullUser = exmpp_jid:make(User, Domain),
IQ = #iq{type = set,
ns = 'jabber:iq:private',
kind = request,
iq_ns = 'jabberd:client',
payload = El},
case M:process_sm_iq(FullUser, FullUser, IQ ) of
{error, _Err} ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("Error processing private storage ~s : ~p ~n",
[exmpp_xml:document_to_list(El), _Err]);
_ -> io:format(" DONE.~n",[]), ok
end;
_ ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("No modules loaded [mod_private, mod_private_odbc] ~s~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
populate_user(_User, _Domain, #xmlcdata{cdata = _CData}) ->
ok;
populate_user(_User, _Domain, _El) ->
ok.
%%%==================================
%%%% Utilities
loaded_module(Domain,Options) when is_binary(Domain) ->
loaded_module(?BTL(Domain),Options);
loaded_module(Domain,Options) ->
LoadedModules = gen_mod:loaded_modules(Domain),
case lists:filter(fun(Module) ->
lists:member(Module, LoadedModules)
end, Options) of
[M|_] -> {ok, M};
[] -> {error,not_found}
end.
%%%==================================
%%%% Export server
%% @spec (Dir::string()) -> ok
export_server(Dir) ->
FnT = make_filename_template(),
DFn = make_main_basefilename(Dir, FnT),
{ok, Fd} = file_open(DFn),
print(Fd, make_piefxis_xml_head()),
print(Fd, make_piefxis_server_head()),
Hosts = ?MYHOSTS,
FilesAndHosts = [{make_host_filename(FnT, Host), Host} || Host <- Hosts],
[print(Fd, make_xinclude(FnH)) || {FnH, _Host} <- FilesAndHosts],
print(Fd, make_piefxis_server_tail()),
print(Fd, make_piefxis_xml_tail()),
file_close(Fd),
[export_host(Dir, FnH, Host) || {FnH, Host} <- FilesAndHosts],
ok.
%%%==================================
%%%% Export host
%% @spec (Dir::string(), Host::string()) -> ok
export_host(Dir, Host) ->
FnT = make_filename_template(),
FnH = make_host_filename(FnT, Host),
export_host(Dir, FnH, Host).
%% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
export_host(Dir, FnH, Host) ->
DFn = make_host_basefilename(Dir, FnH),
{ok, Fd} = file_open(DFn),
print(Fd, make_piefxis_xml_head()),
print(Fd, make_piefxis_host_head(Host)),
Users = ejabberd_auth:get_vh_registered_users(Host),
[export_user(Fd, Username, Host) || {Username, _Host} <- Users],
print(Fd, make_piefxis_host_tail()),
print(Fd, make_piefxis_xml_tail()),
file_close(Fd).
%%%==================================
%%%% PIEFXIS formatting
%% @spec () -> string()
make_piefxis_xml_head() ->
"<?xml version='1.0' encoding='UTF-8'?>".
%% @spec () -> string()
make_piefxis_xml_tail() ->
"".
%% @spec () -> string()
make_piefxis_server_head() ->
"<server-data"
" xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'"
" xmlns:xi='http://www.w3.org/2001/XInclude'>".
%% @spec () -> string()
make_piefxis_server_tail() ->
"</server-data>".
%% @spec (Host::string()) -> string()
make_piefxis_host_head(Host) ->
NSString =
" xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'"
" xmlns:xi='http://www.w3.org/2001/XInclude'",
io_lib:format("<host~s jid='~s'>", [NSString, Host]).
%% @spec () -> string()
make_piefxis_host_tail() ->
"</host>".
%% @spec (Fn::string()) -> string()
make_xinclude(Fn) ->
Base = filename:basename(Fn),
io_lib:format("<xi:include href='~s'/>", [Base]).
%%%==================================
%%%% Export user
%% @spec (Fd, Username::string(), Host::string()) -> ok
%% extraer su informacion e imprimirla
export_user(Fd, Username, Host) ->
UserString = extract_user(Username, Host),
print(Fd, UserString).
%% @spec (Username::string(), Host::string()) -> string()
extract_user(Username, Host) ->
Password = ejabberd_auth:get_password_s(Username, Host),
UserInfo = [extract_user_info(InfoName, Username, Host) || InfoName <- [roster, offline, private, vcard]],
UserInfoString = lists:flatten(UserInfo),
io_lib:format("<user name='~s' password='~s'>~s</user>", [Username, Password, UserInfoString]).
%% @spec (InfoName::atom(), Username::string(), Host::string()) -> string()
extract_user_info(roster, Username, Host) ->
case loaded_module(Host,[mod_roster_odbc,mod_roster]) of
{ok, M} ->
From = To = exmpp_jid:make(Username, Host, ""),
SubelGet = exmpp_xml:element(?NS_ROSTER, 'query', [], []),
IQGet = #iq{type=get, ns=?NS_ROSTER, payload=[SubelGet]},
Res = M:process_local_iq(From, To, IQGet),
case Res#iq.payload of
undefined -> "";
El -> exmpp_xml:document_to_list(El)
end;
_E ->
""
end;
extract_user_info(offline, Username, Host) ->
case loaded_module(Host,[mod_offline,mod_offline_odbc]) of
{ok, mod_offline} ->
Els = mnesia_pop_offline_messages([], Username, Host),
case Els of
[] -> "";
Els ->
OfEl = {xmlelement, "offline-messages", [], Els},
exmpp_xml:document_to_list(OfEl)
end;
{ok, mod_offline_odbc} ->
"";
_E ->
""
end;
extract_user_info(private, Username, Host) ->
case loaded_module(Host,[mod_private,mod_private_odbc]) of
{ok, mod_private} ->
get_user_private_mnesia(Username, Host);
{ok, mod_private_odbc} ->
"";
_E ->
""
end;
extract_user_info(vcard, Username, Host) ->
case loaded_module(Host,[mod_vcard, mod_vcard_odbc, mod_vcard_odbc]) of
{ok, M} ->
From = To = exmpp_jid:make(Username, Host, ""),
SubelGet = exmpp_xml:element(?NS_VCARD, 'vCard', [], []),
IQGet = #iq{type=get, ns=?NS_VCARD, payload=[SubelGet]},
Res = M:process_sm_iq(From, To, IQGet),
case Res#iq.payload of
undefined -> "";
El -> exmpp_xml:document_to_list(El)
end;
_E ->
""
end.
%%%==================================
%%%% Interface with ejabberd offline storage
%% Copied from mod_offline.erl and customized
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
mnesia_pop_offline_messages(Ls, User, Server) ->
try
LUser = User,
LServer = Server,
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
TS = now(),
Ls ++ lists:map(
fun(R) ->
Packet = R#offline_msg.packet,
FromString = exmpp_jid:prep_to_list(R#offline_msg.from),
Packet2 = exmpp_xml:set_attribute(Packet, "from", FromString),
Packet3 = Packet2#xmlel{ns = ?NS_JABBER_CLIENT},
exmpp_xml:append_children(
Packet3,
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp),
utc,
exmpp_jid:make("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
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
catch
_ ->
Ls
end.
%%%==================================
%%%% Interface with ejabberd private storage
get_user_private_mnesia(Username, Host) ->
ListNsEl = mnesia:dirty_select(private_storage,
[{#private_storage{usns={?LTB(Username), ?LTB(Host), '$1'}, xml = '$2'},
[], ['$$']}]),
Els = [exmpp_xml:document_to_list(El) || [_Ns, El] <- ListNsEl],
case lists:flatten(Els) of
"" -> "";
ElsString ->
io_lib:format("<query xmlns='jabber:iq:private'>~s</query>", [ElsString])
end.
%%%==================================
%%%% Disk file access
%% @spec () -> string()
make_filename_template() ->
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
lists:flatten(
io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
[Year, Month, Day, Hour, Minute, Second])).
%% @spec (Dir::string(), FnT::string()) -> string()
make_main_basefilename(Dir, FnT) ->
Filename2 = filename:flatten([FnT, ".xml"]),
filename:join([Dir, Filename2]).
%% @spec (FnT::string(), Host::string()) -> FnH::string()
%% FnH = FnT + _ + Host2 + Extension
%% Host2 = Host with any . replaced by _
%% Example: ("20080804-231550", "jabber.example.org") -> "20080804-231550_jabber_example_org.xml"
make_host_filename(FnT, Host) ->
Host2 = string:join(string:tokens(Host, "."), "_"),
filename:flatten([FnT, "_", Host2, ".xml"]).
make_host_basefilename(Dir, FnT) ->
filename:join([Dir, FnT]).
%% @spec (Fn::string()) -> {ok, Fd}
file_open(Fn) ->
file:open(Fn, [write]).
%% @spec (Fd) -> ok
file_close(Fd) ->
file:close(Fd).
%% @spec (Fd, String::string()) -> ok
print(Fd, String) ->
io:format(Fd, String, []).
%%%==================================
%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%%,%%%= foldmethod=marker:

View File

@ -1979,6 +1979,7 @@ get_node(global, Node, ["db"], Query, Lang) ->
end; end;
get_node(global, Node, ["backup"], Query, Lang) -> get_node(global, Node, ["backup"], Query, Lang) ->
HomeDir = re:replace(filename:nativename(os:cmd("echo $HOME")), "\n", "", [{return, list}]),
ResS = case node_backup_parse_query(Node, Query) of ResS = case node_backup_parse_query(Node, Query) of
nothing -> []; nothing -> [];
ok -> [?XREST("Submitted")]; ok -> [?XREST("Submitted")];
@ -1993,14 +1994,14 @@ get_node(global, Node, ["backup"], Query, Lang) ->
[?XE('tr', [?XE('tr',
[?XCT('td', "Store binary backup:"), [?XCT('td', "Store binary backup:"),
?XE('td', [?INPUT("text", "storepath", ?XE('td', [?INPUT("text", "storepath",
"ejabberd.backup")]), filename:join(HomeDir, "ejabberd.backup"))]),
?XE('td', [?INPUTT("submit", "store", ?XE('td', [?INPUTT("submit", "store",
"OK")]) "OK")])
]), ]),
?XE('tr', ?XE('tr',
[?XCT('td', "Restore binary backup immediately:"), [?XCT('td', "Restore binary backup immediately:"),
?XE('td', [?INPUT("text", "restorepath", ?XE('td', [?INPUT("text", "restorepath",
"ejabberd.backup")]), filename:join(HomeDir, "ejabberd.backup"))]),
?XE('td', [?INPUTT("submit", "restore", ?XE('td', [?INPUTT("submit", "restore",
"OK")]) "OK")])
]), ]),
@ -2008,23 +2009,59 @@ get_node(global, Node, ["backup"], Query, Lang) ->
[?XCT('td', [?XCT('td',
"Restore binary backup after next ejabberd restart (requires less memory):"), "Restore binary backup after next ejabberd restart (requires less memory):"),
?XE('td', [?INPUT("text", "fallbackpath", ?XE('td', [?INPUT("text", "fallbackpath",
"ejabberd.backup")]), filename:join(HomeDir, "ejabberd.backup"))]),
?XE('td', [?INPUTT("submit", "fallback", ?XE('td', [?INPUTT("submit", "fallback",
"OK")]) "OK")])
]), ]),
?XE('tr', ?XE('tr',
[?XCT('td', "Store plain text backup:"), [?XCT('td', "Store plain text backup:"),
?XE('td', [?INPUT("text", "dumppath", ?XE('td', [?INPUT("text", "dumppath",
"ejabberd.dump")]), filename:join(HomeDir, "ejabberd.dump"))]),
?XE('td', [?INPUTT("submit", "dump", ?XE('td', [?INPUTT("submit", "dump",
"OK")]) "OK")])
]), ]),
?XE('tr', ?XE('tr',
[?XCT('td', "Restore plain text backup immediately:"), [?XCT('td', "Restore plain text backup immediately:"),
?XE('td', [?INPUT("text", "loadpath", ?XE('td', [?INPUT("text", "loadpath",
"ejabberd.dump")]), filename:join(HomeDir, "ejabberd.dump"))]),
?XE('td', [?INPUTT("submit", "load", ?XE('td', [?INPUTT("submit", "load",
"OK")]) "OK")])
]),
?XE("tr",
[?XCT("td", "Import users data from a PIEFXIS file (XEP-0277):"),
?XE("td", [?INPUT("text", "import_piefxis_filepath",
filename:join(HomeDir, "users.xml"))]),
?XE("td", [?INPUTT("submit", "import_piefxis_file",
"OK")])
]),
?XE("tr",
[?XCT("td", "Export data of all users in the server to PIEFXIS files (XEP-0277):"),
?XE("td", [?INPUT("text", "export_piefxis_dirpath",
HomeDir)]),
?XE("td", [?INPUTT("submit", "export_piefxis_dir",
"OK")])
]),
?XE("tr",
[?XE("td", [?CT("Export data of users in a host to PIEFXIS files (XEP-0277):"),
?CT(" "),
?INPUT("text", "export_piefxis_host_dirhost", ?MYNAME)]),
?XE("td", [?INPUT("text", "export_piefxis_host_dirpath", HomeDir)]),
?XE("td", [?INPUTT("submit", "export_piefxis_host_dir",
"OK")])
]),
?XE("tr",
[?XCT("td", "Import user data from jabberd14 spool file:"),
?XE("td", [?INPUT("text", "import_filepath",
filename:join(HomeDir, "user1.xml"))]),
?XE("td", [?INPUTT("submit", "import_file",
"OK")])
]),
?XE("tr",
[?XCT("td", "Import users data from jabberd14 spool directory:"),
?XE("td", [?INPUT("text", "import_dirpath",
"/var/spool/jabber/")]),
?XE("td", [?INPUTT("submit", "import_dir",
"OK")])
]) ])
]) ])
])])]; ])])];
@ -2303,7 +2340,23 @@ node_backup_parse_query(Node, Query) ->
dump_to_textfile, [Path]); dump_to_textfile, [Path]);
"load" -> "load" ->
rpc:call(Node, mnesia, rpc:call(Node, mnesia,
load_textfile, [Path]) load_textfile, [Path]);
"import_piefxis_file" ->
rpc:call(Node, ejabberd_piefxis,
import_file, [Path]);
"export_piefxis_dir" ->
rpc:call(Node, ejabberd_piefxis,
export_server, [Path]);
"export_piefxis_host_dir" ->
{value, {_, Host}} = lists:keysearch(Action ++ "host", 1, Query),
rpc:call(Node, ejabberd_piefxis,
export_host, [Path, Host]);
"import_file" ->
rpc:call(Node, ejabberd_admin,
import_file, [Path]);
"import_dir" ->
rpc:call(Node, ejabberd_admin,
import_dir, [Path])
end, end,
case Res of case Res of
{error, Reason} -> {error, Reason} ->
@ -2321,8 +2374,8 @@ node_backup_parse_query(Node, Query) ->
end; end;
(_Action, Res) -> (_Action, Res) ->
Res Res
end, nothing, ["store", "restore", "fallback", "dump", "load"]). end, nothing, ["store", "restore", "fallback", "dump", "load", "import_file", "import_dir",
"import_piefxis_file", "export_piefxis_dir", "export_piefxis_host_dir"]).
node_ports_to_xhtml(Ports, Lang) -> node_ports_to_xhtml(Ports, Lang) ->
?XAE('table', [?XMLATTR('class', <<"withtextareas">>)], ?XAE('table', [?XMLATTR('class', <<"withtextareas">>)],