mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-02 16:37:52 +01:00
533 lines
16 KiB
Erlang
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-2019 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 '~s' 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 '~s' table to binary: ~p",
|
|
[Tab, Err])),
|
|
?CRITICAL_MSG(ErrTxt, []),
|
|
ejabberd:halt().
|