xmpp.chapril.org-ejabberd/src/pubsub_migrate.erl

533 lines
16 KiB
Erlang

%%%----------------------------------------------------------------------
%%% File : pubsub_migrate.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Migration/Upgrade code put out of mod_pubsub
%%% Created : 26 Jul 2014 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2020 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.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(pubsub_migrate).
-dialyzer({no_return, report_and_stop/2}).
-include("pubsub.hrl").
-include("logger.hrl").
-export([update_node_database/2, update_state_database/2]).
-export([update_item_database/2, update_lastitem_database/2]).
update_item_database(_Host, _ServerHost) ->
convert_list_items().
update_node_database(Host, ServerHost) ->
mnesia:del_table_index(pubsub_node, type),
mnesia:del_table_index(pubsub_node, parentid),
case catch mnesia:table_info(pubsub_node, attributes) of
[host_node, host_parent, info] ->
?INFO_MSG("Upgrading pubsub nodes table...", []),
F = fun () ->
{Result, LastIdx} = lists:foldl(fun ({pubsub_node,
NodeId, ParentId,
{nodeinfo, Items,
Options,
Entities}},
{RecList,
NodeIdx}) ->
ItemsList =
lists:foldl(fun
({item,
IID,
Publisher,
Payload},
Acc) ->
C =
{unknown,
Publisher},
M =
{erlang:timestamp(),
Publisher},
mnesia:write(#pubsub_item{itemid
=
{IID,
NodeIdx},
creation
=
C,
modification
=
M,
payload
=
Payload}),
[{Publisher,
IID}
| Acc]
end,
[],
Items),
Owners =
dict:fold(fun
(JID,
{entity,
Aff,
Sub},
Acc) ->
UsrItems =
lists:foldl(fun
({P,
I},
IAcc) ->
case
P
of
JID ->
[I
| IAcc];
_ ->
IAcc
end
end,
[],
ItemsList),
mnesia:write({pubsub_state,
{JID,
NodeIdx},
UsrItems,
Aff,
Sub}),
case
Aff
of
owner ->
[JID
| Acc];
_ ->
Acc
end
end,
[],
Entities),
mnesia:delete({pubsub_node,
NodeId}),
{[#pubsub_node{nodeid
=
NodeId,
id
=
NodeIdx,
parents
=
[element(2,
ParentId)],
owners
=
Owners,
options
=
Options}
| RecList],
NodeIdx + 1}
end,
{[], 1},
mnesia:match_object({pubsub_node,
{Host,
'_'},
'_',
'_'})),
mnesia:write(#pubsub_index{index = node, last = LastIdx,
free = []}),
Result
end,
{atomic, NewRecords} = mnesia:transaction(F),
{atomic, ok} = mnesia:delete_table(pubsub_node),
{atomic, ok} = ejabberd_mnesia:create(?MODULE, pubsub_node,
[{disc_copies, [node()]},
{attributes,
record_info(fields,
pubsub_node)}]),
FNew = fun () ->
lists:foreach(fun (Record) -> mnesia:write(Record) end,
NewRecords)
end,
case mnesia:transaction(FNew) of
{atomic, Result} ->
?INFO_MSG("Pubsub nodes table upgraded: ~p",
[Result]);
{aborted, Reason} ->
?ERROR_MSG("Problem upgrading Pubsub nodes table:~n~p",
[Reason])
end;
[nodeid, parentid, type, owners, options] ->
F = fun ({pubsub_node, NodeId, {_, Parent}, Type,
Owners, Options}) ->
#pubsub_node{nodeid = NodeId, id = 0,
parents = [Parent], type = Type,
owners = Owners, options = Options}
end,
mnesia:transform_table(pubsub_node, F,
[nodeid, id, parents, type, owners, options]),
FNew = fun () ->
LastIdx = lists:foldl(fun (#pubsub_node{nodeid =
NodeId} =
PubsubNode,
NodeIdx) ->
mnesia:write(PubsubNode#pubsub_node{id
=
NodeIdx}),
lists:foreach(fun
(#pubsub_state{stateid
=
StateId} =
State) ->
{JID,
_} =
StateId,
mnesia:delete({pubsub_state,
StateId}),
mnesia:write(State#pubsub_state{stateid
=
{JID,
NodeIdx}})
end,
mnesia:match_object(#pubsub_state{stateid
=
{'_',
NodeId},
_
=
'_'})),
lists:foreach(fun
(#pubsub_item{itemid
=
ItemId} =
Item) ->
{IID,
_} =
ItemId,
{M1,
M2} =
Item#pubsub_item.modification,
{C1,
C2} =
Item#pubsub_item.creation,
mnesia:delete({pubsub_item,
ItemId}),
mnesia:write(Item#pubsub_item{itemid
=
{IID,
NodeIdx},
modification
=
{M2,
M1},
creation
=
{C2,
C1}})
end,
mnesia:match_object(#pubsub_item{itemid
=
{'_',
NodeId},
_
=
'_'})),
NodeIdx + 1
end,
1,
mnesia:match_object({pubsub_node,
{Host, '_'},
'_', '_',
'_', '_',
'_'})
++
mnesia:match_object({pubsub_node,
{{'_',
ServerHost,
'_'},
'_'},
'_', '_',
'_', '_',
'_'})),
mnesia:write(#pubsub_index{index = node,
last = LastIdx, free = []})
end,
case mnesia:transaction(FNew) of
{atomic, Result} ->
rename_default_nodeplugin(),
?INFO_MSG("Pubsub nodes table upgraded: ~p",
[Result]);
{aborted, Reason} ->
?ERROR_MSG("Problem upgrading Pubsub nodes table:~n~p",
[Reason])
end;
[nodeid, id, parent, type, owners, options] ->
F = fun ({pubsub_node, NodeId, Id, Parent, Type, Owners,
Options}) ->
#pubsub_node{nodeid = NodeId, id = Id,
parents = [Parent], type = Type,
owners = Owners, options = Options}
end,
mnesia:transform_table(pubsub_node, F,
[nodeid, id, parents, type, owners, options]),
rename_default_nodeplugin();
_ -> ok
end,
convert_list_nodes().
rename_default_nodeplugin() ->
lists:foreach(fun (Node) ->
mnesia:dirty_write(Node#pubsub_node{type =
<<"hometree">>})
end,
mnesia:dirty_match_object(#pubsub_node{type =
<<"default">>,
_ = '_'})).
update_state_database(_Host, _ServerHost) ->
% useless starting from ejabberd 17.04
% case catch mnesia:table_info(pubsub_state, attributes) of
% [stateid, nodeidx, items, affiliation, subscriptions] ->
% ?INFO_MSG("Upgrading pubsub states table...", []),
% F = fun ({pubsub_state, {{U,S,R}, NodeID}, _NodeIdx, Items, Aff, Sub}, Acc) ->
% JID = {U,S,R},
% Subs = case Sub of
% none ->
% [];
% [] ->
% [];
% _ ->
% SubID = pubsub_subscription:make_subid(),
% [{Sub, SubID}]
% end,
% NewState = #pubsub_state{stateid = {JID, NodeID},
% items = Items,
% affiliation = Aff,
% subscriptions = Subs},
% [NewState | Acc]
% end,
% {atomic, NewRecs} = mnesia:transaction(fun mnesia:foldl/3,
% [F, [], pubsub_state]),
% {atomic, ok} = mnesia:delete_table(pubsub_state),
% {atomic, ok} = ejabberd_mnesia:create(?MODULE, pubsub_state,
% [{disc_copies, [node()]},
% {attributes, record_info(fields, pubsub_state)}]),
% FNew = fun () ->
% lists:foreach(fun mnesia:write/1, NewRecs)
% end,
% case mnesia:transaction(FNew) of
% {atomic, Result} ->
% ?INFO_MSG("Pubsub states table upgraded: ~p",
% [Result]);
% {aborted, Reason} ->
% ?ERROR_MSG("Problem upgrading Pubsub states table:~n~p",
% [Reason])
% end;
% _ ->
% ok
% end,
convert_list_subscriptions(),
convert_list_states().
update_lastitem_database(_Host, _ServerHost) ->
convert_list_lasts().
%% binarization from old 2.1.x
convert_list_items() ->
convert_list_records(
pubsub_item,
record_info(fields, pubsub_item),
fun(#pubsub_item{itemid = {I, _}}) -> I end,
fun(#pubsub_item{itemid = {I, Nidx},
creation = {C, CKey},
modification = {M, MKey},
payload = Els} = R) ->
R#pubsub_item{itemid = {bin(I), Nidx},
creation = {C, binusr(CKey)},
modification = {M, binusr(MKey)},
payload = [fxml:to_xmlel(El) || El<-Els]}
end).
convert_list_states() ->
convert_list_records(
pubsub_state,
record_info(fields, pubsub_state),
fun(#pubsub_state{stateid = {{U,_,_}, _}}) -> U end,
fun(#pubsub_state{stateid = {U, Nidx},
items = Is,
affiliation = A,
subscriptions = Ss} = R) ->
R#pubsub_state{stateid = {binusr(U), Nidx},
items = [bin(I) || I<-Is],
affiliation = A,
subscriptions = [{S,bin(Sid)} || {S,Sid}<-Ss]}
end).
convert_list_nodes() ->
convert_list_records(
pubsub_node,
record_info(fields, pubsub_node),
fun(#pubsub_node{nodeid = {{U,_,_}, _}}) -> U;
(#pubsub_node{nodeid = {H, _}}) -> H end,
fun(#pubsub_node{nodeid = {H, N},
id = I,
parents = Ps,
type = T,
owners = Os,
options = Opts} = R) ->
R#pubsub_node{nodeid = {binhost(H), bin(N)},
id = I,
parents = [bin(P) || P<-Ps],
type = bin(T),
owners = [binusr(O) || O<-Os],
options = Opts}
end).
convert_list_subscriptions() ->
[convert_list_records(
pubsub_subscription,
record_info(fields, pubsub_subscription),
fun(#pubsub_subscription{subid = I}) -> I end,
fun(#pubsub_subscription{subid = I,
options = Opts} = R) ->
R#pubsub_subscription{subid = bin(I),
options = Opts}
end) || lists:member(pubsub_subscription, mnesia:system_info(tables))].
convert_list_lasts() ->
convert_list_records(
pubsub_last_item,
record_info(fields, pubsub_last_item),
fun(#pubsub_last_item{itemid = I}) -> I end,
fun(#pubsub_last_item{itemid = I,
nodeid = Nidx,
creation = {C, CKey},
payload = Payload} = R) ->
R#pubsub_last_item{itemid = bin(I),
nodeid = Nidx,
creation = {C, binusr(CKey)},
payload = fxml:to_xmlel(Payload)}
end).
%% internal tools
convert_list_records(Tab, Fields, DetectFun, ConvertFun) ->
case mnesia:table_info(Tab, attributes) of
Fields ->
convert_table_to_binary(
Tab, Fields, set, DetectFun, ConvertFun);
_ ->
?INFO_MSG("Recreating ~p table", [Tab]),
mnesia:transform_table(Tab, ignore, Fields),
convert_list_records(Tab, Fields, DetectFun, ConvertFun)
end.
binhost({U,S,R}) -> binusr({U,S,R});
binhost(L) -> bin(L).
binusr({U,S,R}) -> {bin(U), bin(S), bin(R)}.
bin(L) -> iolist_to_binary(L).
%% The code should be updated to support new ejabberd_mnesia
%% transform functions (i.e. need_transform/1 and transform/1)
convert_table_to_binary(Tab, Fields, Type, DetectFun, ConvertFun) ->
case is_table_still_list(Tab, DetectFun) of
true ->
?INFO_MSG("Converting '~ts' table from strings to binaries.", [Tab]),
TmpTab = list_to_atom(atom_to_list(Tab) ++ "_tmp_table"),
catch mnesia:delete_table(TmpTab),
case ejabberd_mnesia:create(?MODULE, TmpTab,
[{disc_only_copies, [node()]},
{type, Type},
{local_content, true},
{record_name, Tab},
{attributes, Fields}]) of
{atomic, ok} ->
mnesia:transform_table(Tab, ignore, Fields),
case mnesia:transaction(
fun() ->
mnesia:write_lock_table(TmpTab),
mnesia:foldl(
fun(R, _) ->
NewR = ConvertFun(R),
mnesia:dirty_write(TmpTab, NewR)
end, ok, Tab)
end) of
{atomic, ok} ->
mnesia:clear_table(Tab),
case mnesia:transaction(
fun() ->
mnesia:write_lock_table(Tab),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, TmpTab)
end) of
{atomic, ok} ->
mnesia:delete_table(TmpTab);
Err ->
report_and_stop(Tab, Err)
end;
Err ->
report_and_stop(Tab, Err)
end;
Err ->
report_and_stop(Tab, Err)
end;
false ->
ok
end.
is_table_still_list(Tab, DetectFun) ->
is_table_still_list(Tab, DetectFun, mnesia:dirty_first(Tab)).
is_table_still_list(_Tab, _DetectFun, '$end_of_table') ->
false;
is_table_still_list(Tab, DetectFun, Key) ->
Rs = mnesia:dirty_read(Tab, Key),
Res = lists:foldl(fun(_, true) ->
true;
(_, false) ->
false;
(R, _) ->
case DetectFun(R) of
'$next' ->
'$next';
El ->
is_list(El)
end
end, '$next', Rs),
case Res of
true ->
true;
false ->
false;
'$next' ->
is_table_still_list(Tab, DetectFun, mnesia:dirty_next(Tab, Key))
end.
report_and_stop(Tab, Err) ->
ErrTxt = lists:flatten(
io_lib:format(
"Failed to convert '~ts' table to binary: ~p",
[Tab, Err])),
?CRITICAL_MSG(ErrTxt, []),
ejabberd:halt().