aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README4
-rw-r--r--src/ejabberd_admin.erl4
-rw-r--r--src/ejabberd_c2s.erl5
-rw-r--r--src/ejabberd_web_admin.erl75
-rw-r--r--src/ext_mod.erl77
-rw-r--r--src/mod_http_fileserver.erl13
-rw-r--r--src/mod_mam.erl317
-rw-r--r--src/mod_muc.erl3
-rw-r--r--src/mod_muc_admin.erl14
-rw-r--r--src/mod_muc_room.erl40
-rw-r--r--src/mod_pubsub.erl9
-rw-r--r--src/node_buddy.erl58
-rw-r--r--src/node_club.erl58
-rw-r--r--src/node_dispatch.erl4
-rw-r--r--src/node_flat.erl769
-rw-r--r--src/node_flat_odbc.erl989
-rw-r--r--src/node_hometree.erl808
-rw-r--r--src/node_hometree_odbc.erl1122
-rw-r--r--src/node_mb.erl1
-rw-r--r--src/node_pep.erl52
-rw-r--r--src/node_pep_odbc.erl80
-rw-r--r--src/node_private.erl58
-rw-r--r--src/node_public.erl58
-rw-r--r--src/nodetree_tree_odbc.erl16
-rw-r--r--test/ejabberd_SUITE.erl74
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml15
-rw-r--r--tools/xmpp_codec.erl85
-rw-r--r--tools/xmpp_codec.hrl6
-rw-r--r--tools/xmpp_codec.spec11
29 files changed, 2452 insertions, 2373 deletions
diff --git a/README b/README
index 2f78d1973..31a76778b 100644
--- a/README
+++ b/README
@@ -159,6 +159,10 @@ In order to assist in the development of ejabberd, and particularly the
execution of the test suite, a Vagrant environment is available at
https://github.com/processone/ejabberd-vagrant-dev.
+To start ejabberd in development mode from the repository directory, you can
+type a command like:
+
+ EJABBERD_CONFIG_PATH=ejabberd.yml erl -pa ebin -pa deps/*/ebin -pa test -s ejabberd
Links
-----
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index 89421d996..a0b32304f 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -167,6 +167,10 @@ commands() ->
desc = "Export all tables as SQL queries to a file",
module = ejd2odbc, function = export,
args = [{host, string}, {file, string}], result = {res, rescode}},
+ #ejabberd_commands{name = convert_to_scram, tags = [odbc],
+ desc = "Convert the passwords in 'users' ODBC table to SCRAM",
+ module = ejabberd_auth_odbc, function = convert_to_scram,
+ args = [{host, binary}], result = {res, rescode}},
#ejabberd_commands{name = convert_to_yaml, tags = [config],
desc = "Convert the input file from Erlang to YAML format",
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 3869bd54e..e6f55c489 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -308,7 +308,7 @@ init([{SockMod, Socket}, Opts]) ->
MaxAckQueue = case proplists:get_value(max_ack_queue, Opts) of
Limit when is_integer(Limit), Limit > 0 -> Limit;
infinity -> infinity;
- _ -> 500
+ _ -> 1000
end,
ResumeTimeout = case proplists:get_value(resume_timeout, Opts) of
Timeout when is_integer(Timeout), Timeout >= 0 -> Timeout;
@@ -2025,6 +2025,7 @@ get_conn_type(StateData) ->
p1_tls -> c2s_compressed_tls
end;
ejabberd_http_bind -> http_bind;
+ ejabberd_http_ws -> websocket;
_ -> unknown
end.
@@ -3010,7 +3011,7 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
end.
resume_session({Time, PID}) ->
- (?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 3000).
+ (?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 5000).
make_resume_id(StateData) ->
{Time, _} = StateData#state.sid,
diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl
index da166581c..75313de2b 100644
--- a/src/ejabberd_web_admin.erl
+++ b/src/ejabberd_web_admin.erl
@@ -167,15 +167,15 @@ process([<<"doc">>, LocalFile], _Request) ->
?DEBUG("Delivering content.", []),
{200, [{<<"Server">>, <<"ejabberd">>}], FileContents};
{error, Error} ->
- ?DEBUG("Delivering error: ~p", [Error]),
Help = <<" ", FileName/binary,
" - Try to specify the path to ejabberd "
"documentation with the environment variable "
"EJABBERD_DOC_PATH. Check the ejabberd "
"Guide for more information.">>,
+ ?INFO_MSG("Problem '~p' accessing the local Guide file ~s", [Error, Help]),
case Error of
eacces -> {403, [], <<"Forbidden", Help/binary>>};
- enoent -> {404, [], <<"Not found", Help/binary>>};
+ enoent -> {307, [{<<"Location">>, <<"http://docs.ejabberd.im/admin/guide/configuration/">>}], <<"Not found", Help/binary>>};
_Else ->
{404, [], <<(iolist_to_binary(atom_to_list(Error)))/binary, Help/binary>>}
end
@@ -296,7 +296,7 @@ make_xhtml(Els, Host, Node, Lang, JID) ->
#xmlel{name = <<"html">>,
attrs =
[{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
- {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}],
+ {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}]++direction(Lang),
children =
[#xmlel{name = <<"head">>, attrs = [],
children =
@@ -340,8 +340,15 @@ make_xhtml(Els, Host, Node, Lang, JID) ->
[{xmlcdata, <<"">>}])]),
?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}],
[?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}],
- [?XC(<<"p">>,
- <<"ejabberd (c) 2002-2015 ProcessOne">>)])])])]}}.
+ [?XE(<<"p">>,
+ [?AC(<<"https://www.ejabberd.im/">>, <<"ejabberd">>),
+ ?C(<<" (c) 2002-2015 ">>),
+ ?AC(<<"https://www.process-one.net/">>, <<"ProcessOne">>)]
+ )])])])]}}.
+
+direction(ltr) -> [{<<"dir">>, <<"ltr">>}];
+direction(<<"he">>) -> [{<<"dir">>, <<"rtl">>}];
+direction(_) -> [].
get_base_path(global, cluster) -> <<"/admin/">>;
get_base_path(Host, cluster) ->
@@ -510,7 +517,7 @@ css(Host) ->
"0px;\n}\n\nh3 {\n color: #000044;\n "
" font-family: Verdana, Arial, Helvetica, "
"sans-serif; \n font-size: 10pt;\n "
- "font-weight: bold;\n text-align: left;\n "
+ "font-weight: bold;\n "
" padding-top: 20px;\n padding-bottom: "
"2px;\n margin-top: 0px;\n margin-bottom: "
"0px;\n}\n\n#content a:link {\n color: "
@@ -590,7 +597,7 @@ logo_fill() ->
process_admin(global,
#request{path = [], auth = {_, _, AJID},
lang = Lang}) ->
- make_xhtml((?H1GL((?T(<<"Administration">>)), <<"toc">>,
+ make_xhtml((?H1GL((?T(<<"Administration">>)), <<"">>,
<<"Contents">>))
++
[?XE(<<"ul">>,
@@ -659,7 +666,7 @@ process_admin(Host,
[{{acl, '$1', '$2'}}]}])),
{NumLines, ACLsP} = term_to_paragraph(ACLs, 80),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
- <<"ACLDefinition">>, <<"ACL Definition">>))
+ <<"acl-definition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -668,7 +675,7 @@ process_admin(Host,
end
++
[?XAE(<<"form">>,
- [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"acls">>,
(iolist_to_binary(integer_to_list(lists:max([16,
NumLines])))),
@@ -695,7 +702,7 @@ process_admin(Host,
[{{acl, {'$1', Host}, '$2'}, [],
[{{acl, '$1', '$2'}}]}])),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
- <<"ACLDefinition">>, <<"ACL Definition">>))
+ <<"acl-definition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -703,9 +710,9 @@ process_admin(Host,
nothing -> []
end
++
- [?XE(<<"p">>, [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++
+ [?XAE(<<"p">>, direction(ltr), [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++
[?XAE(<<"form">>,
- [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[acls_to_xhtml(ACLs), ?BR,
?INPUTT(<<"submit">>, <<"delete">>,
<<"Delete Selected">>),
@@ -761,7 +768,7 @@ process_admin(Host,
[{{access, '$1', '$2'}}]}]),
{NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
- <<"AccessRights">>, <<"Access Rights">>))
+ <<"access-rights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -770,7 +777,7 @@ process_admin(Host,
end
++
[?XAE(<<"form">>,
- [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"access">>,
(iolist_to_binary(integer_to_list(lists:max([16,
NumLines])))),
@@ -794,7 +801,7 @@ process_admin(Host,
[{{access, {'$1', Host}, '$2'}, [],
[{{access, '$1', '$2'}}]}]),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
- <<"AccessRights">>, <<"Access Rights">>))
+ <<"access-rights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -802,10 +809,10 @@ process_admin(Host,
nothing -> []
end
++
- [?XE(<<"p">>, [?ACT(<<"../access-raw/">>, <<"Raw">>)])]
+ [?XAE(<<"p">>, direction(ltr), [?ACT(<<"../access-raw/">>, <<"Raw">>)])]
++
[?XAE(<<"form">>,
- [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[access_rules_to_xhtml(AccessRules, Lang), ?BR,
?INPUTT(<<"submit">>, <<"delete">>,
<<"Delete Selected">>)])],
@@ -853,7 +860,7 @@ process_admin(global,
lang = Lang}) ->
Res = list_vhosts(Lang, AJID),
make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)),
- <<"virtualhost">>, <<"Virtual Hosting">>))
+ <<"virtual-hosting">>, <<"Virtual Hosting">>))
++ Res,
global, Lang, AJID);
process_admin(Host,
@@ -1550,18 +1557,24 @@ user_info(User, Server, Query, Lang) ->
c2s_compressed_tls ->
<<"tls+zlib">>;
http_bind ->
- <<"http-bind">>
+ <<"http-bind">>;
+ websocket ->
+ <<"websocket">>;
+ _ ->
+ <<"unknown">>
end,
- <<" (", ConnS/binary,
+ <<ConnS/binary,
"://",
(jlib:ip_to_list(IP))/binary,
":",
(jlib:integer_to_binary(Port))/binary,
"#",
- (jlib:atom_to_binary(Node))/binary,
- ")">>
+ (jlib:atom_to_binary(Node))/binary>>
end,
- ?LI([?C((<<R/binary, FIP/binary>>))])
+ case direction(Lang) of
+ [{_, <<"rtl">>}] -> ?LI([?C((<<FIP/binary, " - ", R/binary>>))]);
+ _ -> ?LI([?C((<<R/binary, " - ", FIP/binary>>))])
+ end
end,
lists:sort(Resources))))]
end,
@@ -1872,9 +1885,7 @@ get_node(global, Node, [<<"backup">>], Query, Lang) ->
[?XRES(<<(?T(<<"Error">>))/binary, ": ",
(list_to_binary(io_lib:format("~p", [Error])))/binary>>)]
end,
- (?H1GL(list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])),
- <<"list-eja-commands">>,
- <<"List of ejabberd Commands">>))
+ [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])))]
++
ResS ++
[?XCT(<<"p">>,
@@ -2036,7 +2047,7 @@ get_node(global, Node, [<<"ports">>], Query, Lang) ->
[]])),
H1String = <<(?T(<<"Listened Ports at ">>))/binary,
(iolist_to_binary(atom_to_list(Node)))/binary>>,
- (?H1GL(H1String, <<"listened">>, <<"Listening Ports">>))
+ (?H1GL(H1String, <<"listening-ports">>, <<"Listening Ports">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -2064,7 +2075,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
NewModules = lists:sort(rpc:call(Node, gen_mod,
loaded_modules_with_opts, [Host])),
H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])),
- (?H1GL(H1String, <<"modoverview">>,
+ (?H1GL(H1String, <<"modules-overview">>,
<<"Modules Overview">>))
++
case Res of
@@ -2381,7 +2392,7 @@ node_ports_to_xhtml(Ports, Lang) ->
[?INPUTS(<<"text">>,
<<"module", SSPort/binary>>,
SModule, <<"15">>)]),
- ?XE(<<"td">>,
+ ?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SSPort/binary>>,
(iolist_to_binary(integer_to_list(NumLines))),
<<"35">>, SOptsClean)]),
@@ -2407,7 +2418,7 @@ node_ports_to_xhtml(Ports, Lang) ->
?XE(<<"td">>,
[?INPUTS(<<"text">>, <<"modulenew">>, <<"">>,
<<"15">>)]),
- ?XE(<<"td">>,
+ ?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"optsnew">>, <<"2">>, <<"35">>,
<<"[]">>)]),
?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
@@ -2524,7 +2535,7 @@ node_modules_to_xhtml(Modules, Lang) ->
40),
?XE(<<"tr">>,
[?XC(<<"td">>, SModule),
- ?XE(<<"td">>,
+ ?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SModule/binary>>,
(iolist_to_binary(integer_to_list(NumLines))),
<<"40">>, SOpts)]),
@@ -2543,7 +2554,7 @@ node_modules_to_xhtml(Modules, Lang) ->
[?XE(<<"tr">>,
[?XE(<<"td">>,
[?INPUT(<<"text">>, <<"modulenew">>, <<"">>)]),
- ?XE(<<"td">>,
+ ?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"optsnew">>, <<"2">>, <<"40">>,
<<"[]">>)]),
?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}],
diff --git a/src/ext_mod.erl b/src/ext_mod.erl
index bfc448dcf..070fdf9d3 100644
--- a/src/ext_mod.erl
+++ b/src/ext_mod.erl
@@ -445,9 +445,14 @@ compile_and_install(Module, Spec) ->
true ->
{ok, Dir} = file:get_cwd(),
file:set_cwd(SrcDir),
- Result = case compile(Module, Spec, LibDir) of
- ok -> install(Module, Spec, LibDir);
- Error -> Error
+ Result = case compile_deps(Module, Spec, LibDir) of
+ ok ->
+ case compile(Module, Spec, LibDir) of
+ ok -> install(Module, Spec, LibDir);
+ Error -> Error
+ end;
+ Error ->
+ Error
end,
file:set_cwd(Dir),
Result;
@@ -459,6 +464,35 @@ compile_and_install(Module, Spec) ->
end
end.
+compile_deps(_Module, _Spec, DestDir) ->
+ case filelib:is_dir("deps") of
+ true -> ok;
+ false -> fetch_rebar_deps()
+ end,
+ Ebin = filename:join(DestDir, "ebin"),
+ filelib:ensure_dir(filename:join(Ebin, ".")),
+ Result = lists:foldl(fun(Dep, Acc) ->
+ Inc = filename:join(Dep, "include"),
+ Src = filename:join(Dep, "src"),
+ Options = [{outdir, Ebin}, {i, Inc}],
+ [file:copy(App, Ebin) || App <- filelib:wildcard(Src++"/*.app")],
+ Acc++[case compile:file(File, Options) of
+ {ok, _} -> ok;
+ {ok, _, _} -> ok;
+ {ok, _, _, _} -> ok;
+ error -> {error, {compilation_failed, File}};
+ Error -> Error
+ end
+ || File <- filelib:wildcard(Src++"/*.erl")]
+ end, [], filelib:wildcard("deps/*")),
+ case lists:dropwhile(
+ fun(ok) -> true;
+ (_) -> false
+ end, Result) of
+ [] -> ok;
+ [Error|_] -> Error
+ end.
+
compile(_Module, _Spec, DestDir) ->
Ebin = filename:join(DestDir, "ebin"),
filelib:ensure_dir(filename:join(Ebin, ".")),
@@ -470,6 +504,7 @@ compile(_Module, _Spec, DestDir) ->
Options = [{outdir, Ebin}, {i, "include"}, {i, EjabInc},
verbose, report_errors, report_warnings]
++ Logger ++ ExtLib,
+ [file:copy(App, Ebin) || App <- filelib:wildcard("src/*.app")],
Result = [case compile:file(File, Options) of
{ok, _} -> ok;
{ok, _, _} -> ok;
@@ -504,6 +539,42 @@ install(Module, Spec, DestDir) ->
Error -> Error
end.
+%% -- minimalist rebar spec parser, only support git
+
+fetch_rebar_deps() ->
+ case rebar_deps("rebar.config")++rebar_deps("rebar.config.script") of
+ [] ->
+ ok;
+ Deps ->
+ filelib:ensure_dir(filename:join("deps", ".")),
+ lists:foreach(fun({_App, Cmd}) ->
+ os:cmd("cd deps; "++Cmd++"; cd ..")
+ end, Deps)
+ end.
+rebar_deps(Script) ->
+ case file:script(Script) of
+ {ok, Config} when is_list(Config) ->
+ [rebar_dep(Dep) || Dep <- proplists:get_value(deps, Config, [])];
+ {ok, {deps, Deps}} ->
+ [rebar_dep(Dep) || Dep <- Deps];
+ _ ->
+ []
+ end.
+rebar_dep({App, _, {git, Url}}) ->
+ {App, "git clone "++Url++" "++filename:basename(App)};
+rebar_dep({App, _, {git, Url, {branch, Ref}}}) ->
+ {App, "git clone -n "++Url++" "++filename:basename(App)++
+ "; (cd "++filename:basename(App)++
+ "; git checkout -q origin/"++Ref++")"};
+rebar_dep({App, _, {git, Url, {tag, Ref}}}) ->
+ {App, "git clone -n "++Url++" "++filename:basename(App)++
+ "; (cd "++filename:basename(App)++
+ "; git checkout -q "++Ref++")"};
+rebar_dep({App, _, {git, Url, Ref}}) ->
+ {App, "git clone -n "++Url++" "++filename:basename(App)++
+ "; (cd "++filename:basename(App)++
+ "; git checkout -q "++Ref++")"}.
+
%% -- YAML spec parser
consult(File) ->
diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl
index 68ebf8835..8b4d7337e 100644
--- a/src/mod_http_fileserver.erl
+++ b/src/mod_http_fileserver.erl
@@ -167,10 +167,17 @@ initialize(Host, Opts) ->
?DEFAULT_CONTENT_TYPE),
ContentTypes = build_list_content_types(
gen_mod:get_opt(content_types, Opts,
- fun(L) when is_list(L) -> L end,
- []),
+ fun(L) when is_list(L) ->
+ lists:map(
+ fun({K, V}) ->
+ {iolist_to_binary(K),
+ iolist_to_binary(V)}
+ end, L)
+ end, []),
?DEFAULT_CONTENT_TYPES),
- ?INFO_MSG("initialize: ~n ~p", [ContentTypes]),%+++
+ ?INFO_MSG("known content types: ~s",
+ [str:join([[$*, K, " -> ", V] || {K, V} <- ContentTypes],
+ <<", ">>)]),
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
CustomHeaders, DefaultContentType, ContentTypes}.
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index edbc4f91a..c3c8a2b92 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -34,7 +34,8 @@
-export([start/2, stop/1]).
-export([user_send_packet/4, user_receive_packet/5,
- process_iq/3, remove_user/2, mod_opt_type/1]).
+ process_iq_v0_2/3, process_iq_v0_3/3, remove_user/2,
+ mod_opt_type/1]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("jlib.hrl").
@@ -64,13 +65,13 @@ start(Host, Opts) ->
init_db(DBType, Host),
init_cache(DBType, Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_MAM_TMP, ?MODULE, process_iq, IQDisc),
+ ?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_MAM_TMP, ?MODULE, process_iq, IQDisc),
+ ?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_MAM_0, ?MODULE, process_iq, IQDisc),
+ ?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_MAM_0, ?MODULE, process_iq, IQDisc),
+ ?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
user_receive_packet, 500),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
@@ -197,13 +198,11 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
Pkt
end.
-process_iq(#jid{lserver = LServer} = From,
+% Query archive v0.2
+process_iq_v0_2(#jid{lserver = LServer} = From,
#jid{lserver = LServer} = To,
#iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
- NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl),
- Fs = case NS of
- ?NS_MAM_TMP ->
- lists:flatmap(
+ Fs = lists:flatmap(
fun(#xmlel{name = <<"start">>} = El) ->
[{<<"start">>, [xml:get_tag_cdata(El)]}];
(#xmlel{name = <<"end">>} = El) ->
@@ -218,9 +217,16 @@ process_iq(#jid{lserver = LServer} = From,
[{<<"set">>, SubEl}];
(_) ->
[]
- end, SubEl#xmlel.children);
- ?NS_MAM_0 ->
- case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
+ end, SubEl#xmlel.children),
+ process_iq(From, To, IQ, SubEl, Fs);
+process_iq_v0_2(From, To, IQ) ->
+ process_iq(From, To, IQ).
+
+% Query archive v0.3
+process_iq_v0_3(#jid{lserver = LServer} = From,
+ #jid{lserver = LServer} = To,
+ #iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
+ Fs = case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
xml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
{#xmlel{} = XData, false} ->
jlib:parse_xdata_submit(XData);
@@ -230,34 +236,16 @@ process_iq(#jid{lserver = LServer} = From,
[{<<"set">>, SubEl}];
{false, false} ->
[]
- end
end,
- case catch lists:foldl(
- fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
- {{_, _, _} = jlib:datetime_string_to_timestamp(Data),
- End, With, RSM};
- ({<<"end">>, [Data|_]}, {Start, _, With, RSM}) ->
- {Start,
- {_, _, _} = jlib:datetime_string_to_timestamp(Data),
- With, RSM};
- ({<<"with">>, [Data|_]}, {Start, End, _, RSM}) ->
- {Start, End, jlib:jid_tolower(jlib:string_to_jid(Data)), RSM};
- ({<<"withroom">>, [Data|_]}, {Start, End, _, RSM}) ->
- {Start, End,
- {room, jlib:jid_tolower(jlib:string_to_jid(Data))},
- RSM};
- ({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) ->
- {Start, End, {text, Data}, RSM};
- ({<<"set">>, El}, {Start, End, With, _}) ->
- {Start, End, With, jlib:rsm_decode(El)};
- (_, Acc) ->
- Acc
- end, {none, [], none, none}, Fs) of
- {'EXIT', _} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
- {Start, End, With, RSM} ->
- select_and_send(From, To, Start, End, With, RSM, IQ)
- end;
+ process_iq(From, To, IQ, SubEl, Fs);
+process_iq_v0_3(From, To, IQ) ->
+ process_iq(From, To, IQ).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+% Preference setting (both v0.2 & v0.3)
process_iq(#jid{luser = LUser, lserver = LServer},
#jid{lserver = LServer},
#iq{type = set, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
@@ -289,9 +277,34 @@ process_iq(#jid{luser = LUser, lserver = LServer},
process_iq(_, _, #iq{sub_el = SubEl} = IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
+process_iq(From, To, IQ, SubEl, Fs) ->
+ case catch lists:foldl(
+ fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
+ {{_, _, _} = jlib:datetime_string_to_timestamp(Data),
+ End, With, RSM};
+ ({<<"end">>, [Data|_]}, {Start, _, With, RSM}) ->
+ {Start,
+ {_, _, _} = jlib:datetime_string_to_timestamp(Data),
+ With, RSM};
+ ({<<"with">>, [Data|_]}, {Start, End, _, RSM}) ->
+ {Start, End, jlib:jid_tolower(jlib:string_to_jid(Data)), RSM};
+ ({<<"withroom">>, [Data|_]}, {Start, End, _, RSM}) ->
+ {Start, End,
+ {room, jlib:jid_tolower(jlib:string_to_jid(Data))},
+ RSM};
+ ({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) ->
+ {Start, End, {text, Data}, RSM};
+ ({<<"set">>, El}, {Start, End, With, _}) ->
+ {Start, End, With, jlib:rsm_decode(El)};
+ (_, Acc) ->
+ Acc
+ end, {none, [], none, none}, Fs) of
+ {'EXIT', _} ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ {Start, End, With, RSM} ->
+ select_and_send(From, To, Start, End, With, RSM, IQ)
+ end.
+
should_archive(#xmlel{name = <<"message">>} = Pkt) ->
case {xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs),
xml:get_subtag_cdata(Pkt, <<"body">>)} of
@@ -505,10 +518,10 @@ select_and_send(#jid{lserver = LServer} = From,
DBType).
select_and_send(From, To, Start, End, With, RSM, IQ, DBType) ->
- {Msgs, Count} = select_and_start(From, To, Start, End, With,
- RSM, DBType),
+ {Msgs, IsComplete, Count} = select_and_start(From, To, Start, End, With,
+ RSM, DBType),
SortedMsgs = lists:keysort(2, Msgs),
- send(From, To, SortedMsgs, RSM, Count, IQ).
+ send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
select_and_start(From, _To, StartUser, End, With, RSM, DB) ->
{JidRequestor, Start, With2} = case With of
@@ -525,21 +538,41 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
Start, End, With, RSM, mnesia) ->
MS = make_matchspec(LUser, LServer, Start, End, With),
Msgs = mnesia:dirty_select(archive_msg, MS),
- FilteredMsgs = filter_by_rsm(Msgs, RSM),
+ {FilteredMsgs, IsComplete} = filter_by_rsm(Msgs, RSM),
Count = length(Msgs),
{lists:map(
fun(Msg) ->
{Msg#archive_msg.id,
jlib:binary_to_integer(Msg#archive_msg.id),
msg_to_el(Msg, JidRequestor)}
- end, FilteredMsgs), Count};
+ end, FilteredMsgs), IsComplete, Count};
select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
Start, End, With, RSM, {odbc, Host}) ->
- {Query, CountQuery} = make_sql_query(LUser, LServer,
+ {Query, CountQuery} = make_sql_query(LUser, LServer,
Start, End, With, RSM),
+ % XXX TODO from XEP-0313:
+ % To conserve resources, a server MAY place a reasonable limit on
+ % how many stanzas may be pushed to a client in one request. If a
+ % query returns a number of stanzas greater than this limit and
+ % the client did not specify a limit using RSM then the server
+ % should return a policy-violation error to the client.
case {ejabberd_odbc:sql_query(Host, Query),
ejabberd_odbc:sql_query(Host, CountQuery)} of
{{selected, _, Res}, {selected, _, [[Count]]}} ->
+ {Max, Direction} = case RSM of
+ #rsm_in{max = M, direction = D} -> {M, D};
+ _ -> {undefined, undefined}
+ end,
+ {Res1, IsComplete} =
+ if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
+ if Direction == before ->
+ {lists:nthtail(1, Res), false};
+ true ->
+ {lists:sublist(Res, Max), false}
+ end;
+ true ->
+ {Res, true}
+ end,
{lists:map(
fun([TS, XML, PeerBin]) ->
#xmlel{} = El = xml_stream:parse_element(XML),
@@ -550,9 +583,9 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
packet = El,
peer = PeerJid},
JidRequestor)}
- end, Res), jlib:binary_to_integer(Count)};
- _ ->
- {[], 0}
+ end, Res1), IsComplete, jlib:binary_to_integer(Count)};
+ _ ->
+ {[], false, 0}
end.
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, peer = Peer},
@@ -580,14 +613,19 @@ maybe_update_from_to(Pkt, JidRequestor, Peer) ->
_ -> Pkt
end.
-send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) ->
+send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
QID = xml:get_tag_attr_s(<<"queryid">>, SubEl),
NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl),
QIDAttr = if QID /= <<>> ->
- [{<<"queryid">>, QID}];
- true ->
- []
- end,
+ [{<<"queryid">>, QID}];
+ true ->
+ []
+ end,
+ CompleteAttr = if NS == ?NS_MAM_TMP ->
+ [];
+ NS == ?NS_MAM_0 ->
+ [{<<"complete">>, jlib:atom_to_binary(IsComplete)}]
+ end,
Els = lists:map(
fun({ID, _IDInt, El}) ->
#xmlel{name = <<"message">>,
@@ -596,7 +634,7 @@ send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) ->
{<<"id">>, ID}|QIDAttr],
children = [El]}]}
end, Msgs),
- RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr, NS),
+ RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS),
case NS of
?NS_MAM_TMP ->
lists:foreach(
@@ -618,55 +656,57 @@ send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) ->
end.
-make_rsm_out(_Msgs, none, _Count, _QIDAttr, ?NS_MAM_TMP) ->
+make_rsm_out(_Msgs, none, _Count, _Attrs, ?NS_MAM_TMP) ->
[];
-make_rsm_out(_Msgs, none, _Count, QIDAttr, ?NS_MAM_0) ->
- [#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_MAM_0}|QIDAttr]}];
-make_rsm_out([], #rsm_in{}, Count, QIDAttr, NS) ->
+make_rsm_out(_Msgs, none, _Count, Attrs, ?NS_MAM_0) ->
+ [#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_MAM_0}|Attrs]}];
+make_rsm_out([], #rsm_in{}, Count, Attrs, NS) ->
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
end,
- [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr],
- children = jlib:rsm_encode(#rsm_out{count = Count})}];
-make_rsm_out([{FirstID, _, _}|_] = Msgs, #rsm_in{}, Count, QIDAttr, NS) ->
+ [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
+ children = jlib:rsm_encode(#rsm_out{count = Count})}];
+make_rsm_out([{FirstID, _, _}|_] = Msgs, #rsm_in{}, Count, Attrs, NS) ->
{LastID, _, _} = lists:last(Msgs),
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
end,
- [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr],
- children = jlib:rsm_encode(
- #rsm_out{first = FirstID, count = Count,
- last = LastID})}].
+ [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
+ children = jlib:rsm_encode(
+ #rsm_out{first = FirstID, count = Count,
+ last = LastID})}].
filter_by_rsm(Msgs, none) ->
- Msgs;
-filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max =< 0 ->
- [];
+ {Msgs, true};
+filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
+ {[], true};
filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
NewMsgs = case Direction of
- aft ->
- lists:filter(
- fun(#archive_msg{id = I}) ->
- I > ID
- end, Msgs);
- before ->
- lists:foldl(
- fun(#archive_msg{id = I} = Msg, Acc) when I < ID ->
- [Msg|Acc];
- (_, Acc) ->
- Acc
- end, [], Msgs);
- _ ->
- Msgs
- end,
+ aft when ID /= <<"">> ->
+ lists:filter(
+ fun(#archive_msg{id = I}) ->
+ I > ID
+ end, Msgs);
+ before when ID /= <<"">> ->
+ lists:foldl(
+ fun(#archive_msg{id = I} = Msg, Acc) when I < ID ->
+ [Msg|Acc];
+ (_, Acc) ->
+ Acc
+ end, [], Msgs);
+ before when ID == <<"">> ->
+ lists:reverse(Msgs);
+ _ ->
+ Msgs
+ end,
filter_by_max(NewMsgs, Max).
filter_by_max(Msgs, undefined) ->
- Msgs;
+ {Msgs, true};
filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
- lists:sublist(Msgs, Len);
+ {lists:sublist(Msgs, Len), length(Msgs) =< Len};
filter_by_max(_Msgs, _Junk) ->
- [].
+ {[], true}.
make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) ->
ets:fun2ms(
@@ -705,45 +745,43 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) ->
RSM#rsm_in.direction,
RSM#rsm_in.id};
none ->
- {none, none, none}
+ {none, none, <<>>}
end,
LimitClause = if is_integer(Max), Max >= 0 ->
- [<<" limit ">>, jlib:integer_to_binary(Max)];
- true ->
- []
- end,
+ [<<" limit ">>, jlib:integer_to_binary(Max+1)];
+ true ->
+ []
+ end,
WithClause = case With of
- {text, <<>>} ->
- [];
- {text, Txt} ->
- [<<" and match (txt) against ('">>,
- ejabberd_odbc:escape(Txt), <<"')">>];
- {_, _, <<>>} ->
- [<<" and bare_peer='">>,
- ejabberd_odbc:escape(jlib:jid_to_string(With)),
- <<"'">>];
- {_, _, _} ->
- [<<" and peer='">>,
- ejabberd_odbc:escape(jlib:jid_to_string(With)),
- <<"'">>];
- none ->
- []
- end,
- DirectionClause = case catch jlib:binary_to_integer(ID) of
- I when is_integer(I), I >= 0 ->
- case Direction of
- before ->
- [<<" and timestamp < ">>, ID,
- <<" order by timestamp desc">>];
- aft ->
- [<<" and timestamp > ">>, ID,
- <<" order by timestamp asc">>];
- _ ->
- []
- end;
- _ ->
- []
- end,
+ {text, <<>>} ->
+ [];
+ {text, Txt} ->
+ [<<" and match (txt) against ('">>,
+ ejabberd_odbc:escape(Txt), <<"')">>];
+ {_, _, <<>>} ->
+ [<<" and bare_peer='">>,
+ ejabberd_odbc:escape(jlib:jid_to_string(With)),
+ <<"'">>];
+ {_, _, _} ->
+ [<<" and peer='">>,
+ ejabberd_odbc:escape(jlib:jid_to_string(With)),
+ <<"'">>];
+ none ->
+ []
+ end,
+ PageClause = case catch jlib:binary_to_integer(ID) of
+ I when is_integer(I), I >= 0 ->
+ case Direction of
+ before ->
+ [<<" AND timestamp < ">>, ID];
+ aft ->
+ [<<" AND timestamp > ">>, ID];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end,
StartClause = case Start of
{_, _, _} ->
[<<" and timestamp >= ">>,
@@ -759,11 +797,28 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) ->
[]
end,
SUser = ejabberd_odbc:escape(LUser),
- {[<<"select timestamp, xml, peer from archive where username='">>,
- SUser, <<"'">>] ++ WithClause ++ StartClause ++ EndClause ++
- DirectionClause ++ LimitClause ++ [<<";">>],
- [<<"select count(*) from archive where username='">>,
- SUser, <<"'">>] ++ WithClause ++ StartClause ++ EndClause ++ [<<";">>]}.
+
+ Query = [<<"SELECT timestamp, xml, peer"
+ " FROM archive WHERE username='">>,
+ SUser, <<"'">>, WithClause, StartClause, EndClause,
+ PageClause],
+
+ QueryPage =
+ case Direction of
+ before ->
+ % ID can be empty because of
+ % XEP-0059: Result Set Management
+ % 2.5 Requesting the Last Page in a Result Set
+ [<<"SELECT timestamp, xml, peer FROM (">>, Query,
+ <<" ORDER BY timestamp DESC ">>,
+ LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
+ _ ->
+ [Query, <<" ORDER BY timestamp ASC ">>,
+ LimitClause, <<";">>]
+ end,
+ {QueryPage,
+ [<<"SELECT COUNT(*) FROM archive WHERE username='">>,
+ SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index 6226ca3b2..e798790c5 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -115,7 +115,8 @@ shutdown_rooms(Host) ->
Rooms = mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1',
pid = '$2'},
- [{'==', {element, 2, '$1'}, MyHost}],
+ [{'==', {element, 2, '$1'}, MyHost},
+ {'==', {node, '$2'}, node()}],
['$2']}]),
[Pid ! shutdown || Pid <- Rooms],
Rooms.
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index 1804ba7e5..6aa3482dc 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -241,8 +241,8 @@ web_menu_host(Acc, _Host, Lang) ->
])).
web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
- Res = [?XC(<<"h1">>, <<"Multi-User Chat">>),
- ?XC(<<"h3">>, <<"Statistics">>),
+ Res = [?XCT(<<"h1">>, <<"Multi-User Chat">>),
+ ?XCT(<<"h3">>, <<"Statistics">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, ets:info(muc_online_room, size)),
?TDTD(<<"Permanent rooms">>, mnesia:table_info(muc_room, size)),
@@ -301,22 +301,22 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
<<"Persistent">>,
<<"Logging">>,
<<"Just created">>,
- <<"Title">>],
+ <<"Room title">>],
{Titles_TR, _} =
lists:mapfoldl(
fun(Title, Num_column) ->
NCS = jlib:integer_to_binary(Num_column),
TD = ?XE(<<"td">>, [?CT(Title),
?C(<<" ">>),
- ?ACT(<<"?sort=", NCS/binary>>, <<"<">>),
+ ?AC(<<"?sort=", NCS/binary>>, <<"<">>),
?C(<<" ">>),
- ?ACT(<<"?sort=-", NCS/binary>>, <<">">>)]),
+ ?AC(<<"?sort=-", NCS/binary>>, <<">">>)]),
{TD, Num_column+1}
end,
1,
Titles),
- [?XC(<<"h1">>, <<"Multi-User Chat">>),
- ?XC(<<"h2">>, <<"Rooms">>),
+ [?XCT(<<"h1">>, <<"Multi-User Chat">>),
+ ?XCT(<<"h2">>, <<"Chatrooms">>),
?XE(<<"table">>,
[?XE(<<"thead">>,
[?XE(<<"tr">>, Titles_TR)]
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index 7b4cfbf6d..df06bce4f 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -947,20 +947,32 @@ process_groupchat_message(From,
end,
case IsAllowed of
true ->
- send_multiple(
- jlib:jid_replace_resource(StateData#state.jid, FromNick),
- StateData#state.server_host,
- StateData#state.users,
- Packet),
- NewStateData2 = case has_body_or_subject(Packet) of
- true ->
- add_message_to_history(FromNick, From,
- Packet,
- NewStateData1);
- false ->
- NewStateData1
- end,
- {next_state, normal_state, NewStateData2};
+ case
+ ejabberd_hooks:run_fold(muc_filter_packet,
+ StateData#state.server_host,
+ Packet,
+ [StateData,
+ StateData#state.jid,
+ From, FromNick])
+ of
+ drop ->
+ {next_state, normal_state, StateData};
+ NewPacket ->
+ send_multiple(jlib:jid_replace_resource(StateData#state.jid,
+ FromNick),
+ StateData#state.server_host,
+ StateData#state.users,
+ Packet),
+ NewStateData2 = case has_body_or_subject(Packet) of
+ true ->
+ add_message_to_history(FromNick, From,
+ Packet,
+ NewStateData1);
+ false ->
+ NewStateData1
+ end,
+ {next_state, normal_state, NewStateData2}
+ end;
_ ->
Err = case
(StateData#state.config)#config.allow_change_subj
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 218445cfb..b4e936020 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -314,7 +314,6 @@ init([ServerHost, Opts]) ->
pubsub_migrate:update_node_database(Host, ServerHost),
pubsub_migrate:update_state_database(Host, ServerHost),
pubsub_migrate:update_lastitem_database(Host, ServerHost),
- init_nodes(Host, ServerHost, NodeTree, Plugins),
{_, State} = init_send_loop(ServerHost),
{ok, State}.
@@ -388,14 +387,6 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
TreePlugin:terminate(Host, ServerHost),
ok.
-init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
- case lists:member(<<"hometree">>, Plugins) of
- true ->
- create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree">>),
- create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host), <<"hometree">>);
- false -> ok
- end.
-
send_loop(State) ->
receive
{presence, JID, Pid} ->
diff --git a/src/node_buddy.erl b/src/node_buddy.erl
index a6ff0d452..410fd0534 100644
--- a/src/node_buddy.erl
+++ b/src/node_buddy.erl
@@ -45,10 +45,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts).
+ node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+ node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -86,89 +86,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
- node_hometree:delete_node(Removed).
+ node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
diff --git a/src/node_club.erl b/src/node_club.erl
index 6917312f2..cf433b695 100644
--- a/src/node_club.erl
+++ b/src/node_club.erl
@@ -45,10 +45,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts).
+ node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+ node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -85,89 +85,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
- node_hometree:delete_node(Removed).
+ node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
diff --git a/src/node_dispatch.erl b/src/node_dispatch.erl
index 7a1231840..258405cb0 100644
--- a/src/node_dispatch.erl
+++ b/src/node_dispatch.erl
@@ -179,7 +179,7 @@ get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).
node_to_path(Node) ->
- node_flat:node_to_path(Node).
+ node_hometree:node_to_path(Node).
path_to_node(Path) ->
- node_flat:path_to_node(Path).
+ node_hometree:path_to_node(Path).
diff --git a/src/node_flat.erl b/src/node_flat.erl
index e26154380..736bbdfe1 100644
--- a/src/node_flat.erl
+++ b/src/node_flat.erl
@@ -24,6 +24,12 @@
%%% @end
%%% ====================================================================
+%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
+%%% <p>It is used as a default for all unknown PubSub node type. It can serve
+%%% as a developer basis and reference to build its own custom pubsub node
+%%% types.</p>
+%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
+
-module(node_flat).
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
@@ -40,9 +46,10 @@
get_entity_subscriptions/2, get_node_subscriptions/1,
get_subscriptions/2, set_subscriptions/4,
get_pending_nodes/2, get_states/1, get_state/2,
- set_state/1, get_items/7, get_items/3, get_item/7,
+ set_state/1, get_items/6, get_items/2,
+ get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
- path_to_node/1]).
+ path_to_node/1, can_fetch_item/2, is_subscribed/1]).
init(_Host, _ServerHost, _Opts) ->
pubsub_subscription:init(),
@@ -60,18 +67,55 @@ init(_Host, _ServerHost, _Opts) ->
end,
ok.
-terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+terminate(_Host, _ServerHost) ->
+ ok.
options() ->
- node_hometree:options().
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {purge_offline, false},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, publishers},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, on_sub_and_presence},
+ {deliver_notifications, true},
+ {presence_based_delivery, false}].
features() ->
- node_hometree:features().
-
-%% use same code as node_hometree, but do not limite node to
-%% the home/localhost/user/... hierarchy
-%% any node is allowed
+ [<<"create-nodes">>,
+ <<"auto-create">>,
+ <<"access-authorize">>,
+ <<"delete-nodes">>,
+ <<"delete-items">>,
+ <<"get-pending">>,
+ <<"instant-nodes">>,
+ <<"manage-subscriptions">>,
+ <<"modify-affiliations">>,
+ <<"multi-subscribe">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"publish-only-affiliation">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>,
+ <<"subscription-options">>].
+
+%% @doc Checks if the current user has the permission to create the requested node
+%% <p>In flat node, any unused node name is allowed. The access parameter is also
+%% checked. This parameter depends on the value of the
+%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
Allowed = case LOwner of
@@ -83,88 +127,684 @@ create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
{result, Allowed}.
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
-
-delete_node(Removed) ->
- node_hometree:delete_node(Removed).
-
+ OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ set_state(#pubsub_state{stateid = {OwnerKey, Nidx},
+ affiliation = owner}),
+ {result, {default, broadcast}}.
+
+delete_node(Nodes) ->
+ Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
+ lists:map(fun (S) -> {J, S} end, Ss)
+ end,
+ Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
+ {result, States} = get_states(Nidx),
+ lists:foreach(fun (#pubsub_state{stateid = {LJID, _}, items = Items}) ->
+ del_items(Nidx, Items),
+ del_state(Nidx, LJID)
+ end, States),
+ {PubsubNode, lists:flatmap(Tr, States)}
+ end, Nodes),
+ {result, {default, broadcast, Reply}}.
+
+%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
+%% <p>The mechanism works as follow:
+%% <ul>
+%% <li>The main PubSub module prepares the subscription and passes the
+%% result of the preparation as a record.</li>
+%% <li>This function gets the prepared record and several other parameters and
+%% can decide to:<ul>
+%% <li>reject the subscription;</li>
+%% <li>allow it as is, letting the main module perform the database
+%% persistance;</li>
+%% <li>allow it, modifying the record. The main module will store the
+%% modified record;</li>
+%% <li>allow it, but perform the needed persistance operations.</li></ul>
+%% </li></ul></p>
+%% <p>The selected behaviour depends on the return parameter:
+%% <ul>
+%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
+%% subscription will actually be performed.</li>
+%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
+%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
+%% parameter contains an error, no subscription will be performed.</li>
+%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
+%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
+%% passed in parameter <tt>SubscribeResult</tt>.</li>
+%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
+%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
+%% no further persistance operation will be performed. This case is used,
+%% when the plugin module is doing the persistance by itself or when it want
+%% to completly disable persistance.</li></ul>
+%% </p>
+%% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber,
- AccessModel, SendLast, PresenceSubscription,
- RosterGroup, Options).
+ SubKey = jlib:jid_tolower(Subscriber),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
+ GenState = get_state(Nidx, GenKey),
+ SubState = case SubKey of
+ GenKey -> GenState;
+ _ -> get_state(Nidx, SubKey)
+ end,
+ Affiliation = GenState#pubsub_state.affiliation,
+ Subscriptions = SubState#pubsub_state.subscriptions,
+ Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
+ PendingSubscription = lists:any(fun
+ ({pending, _}) -> true;
+ (_) -> false
+ end,
+ Subscriptions),
+ Owner = Affiliation == owner,
+ if not Authorized ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ PendingSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
+ (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ %%ForbiddenAnonymous ->
+ %% % Requesting entity is anonymous
+ %% {error, ?ERR_FORBIDDEN};
+ true ->
+ SubId = pubsub_subscription:add_subscription(Subscriber, Nidx, Options),
+ NewSub = case AccessModel of
+ authorize -> pending;
+ _ -> subscribed
+ end,
+ set_state(SubState#pubsub_state{subscriptions =
+ [{NewSub, SubId} | Subscriptions]}),
+ case {NewSub, SendLast} of
+ {subscribed, never} ->
+ {result, {default, subscribed, SubId}};
+ {subscribed, _} ->
+ {result, {default, subscribed, SubId, send_last}};
+ {_, _} ->
+ {result, {default, pending, SubId}}
+ end
+ end.
+%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ SubKey = jlib:jid_tolower(Subscriber),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
+ GenState = get_state(Nidx, GenKey),
+ SubState = case SubKey of
+ GenKey -> GenState;
+ _ -> get_state(Nidx, SubKey)
+ end,
+ Subscriptions = lists:filter(fun
+ ({_Sub, _SubId}) -> true;
+ (_SubId) -> false
+ end,
+ SubState#pubsub_state.subscriptions),
+ SubIdExists = case SubId of
+ <<>> -> false;
+ Binary when is_binary(Binary) -> true;
+ _ -> false
+ end,
+ if
+ %% Requesting entity is prohibited from unsubscribing entity
+ not Authorized ->
+ {error, ?ERR_FORBIDDEN};
+ %% Entity did not specify SubId
+ %%SubId == "", ?? ->
+ %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %% Invalid subscription identifier
+ %%InvalidSubId ->
+ %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %% Requesting entity is not a subscriber
+ Subscriptions == [] ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
+ %% Subid supplied, so use that.
+ SubIdExists ->
+ Sub = first_in_list(fun
+ ({_, S}) when S == SubId -> true;
+ (_) -> false
+ end,
+ SubState#pubsub_state.subscriptions),
+ case Sub of
+ {value, S} ->
+ delete_subscriptions(SubKey, Nidx, [S], SubState),
+ {result, default};
+ false ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
+ end;
+ %% Asking to remove all subscriptions to the given node
+ SubId == all ->
+ delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
+ {result, default};
+ %% No subid supplied, but there's only one matching subscription
+ length(Subscriptions) == 1 ->
+ delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
+ {result, default};
+ %% No subid and more than one possible subscription match.
+ true ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
+ end.
-publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) ->
+ NewSubs = lists:foldl(fun ({Subscription, SubId}, Acc) ->
+ pubsub_subscription:delete_subscription(SubKey, Nidx, SubId),
+ Acc -- [{Subscription, SubId}]
+ end, SubState#pubsub_state.subscriptions, Subscriptions),
+ case {SubState#pubsub_state.affiliation, NewSubs} of
+ {none, []} -> del_state(Nidx, SubKey);
+ _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ end.
-remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+%% @doc <p>Publishes the item passed as parameter.</p>
+%% <p>The mechanism works as follow:
+%% <ul>
+%% <li>The main PubSub module prepares the item to publish and passes the
+%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
+%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
+%% <li>reject the publication;</li>
+%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
+%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
+%% <li>allow it, but perform the needed persistance operations.</li></ul>
+%% </li></ul></p>
+%% <p>The selected behaviour depends on the return parameter:
+%% <ul>
+%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
+%% publication is actually performed.</li>
+%% <li><tt>true</tt>: Publication operation is allowed, based on the
+%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
+%% parameter contains an error, no subscription will actually be
+%% performed.</li>
+%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
+%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
+%% in parameter <tt>Item</tt>. The persistance will be performed by the main
+%% module.</li>
+%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
+%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
+%% no further persistance operation will be performed. This case is used,
+%% when the plugin module is doing the persistance by itself or when it want
+%% to completly disable persistance.</li></ul>
+%% </p>
+%% <p>In the default plugin module, the record is unchanged.</p>
+publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
+ SubKey = jlib:jid_tolower(Publisher),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ SubState = case SubKey of
+ GenKey -> GenState;
+ _ -> get_state(Nidx, SubKey)
+ end,
+ Affiliation = GenState#pubsub_state.affiliation,
+ Subscribed = case PublishModel of
+ subscribers -> is_subscribed(GenState#pubsub_state.subscriptions) orelse
+ is_subscribed(SubState#pubsub_state.subscriptions);
+ _ -> undefined
+ end,
+ if not ((PublishModel == open) or
+ (PublishModel == publishers) and
+ ((Affiliation == owner)
+ or (Affiliation == publisher)
+ or (Affiliation == publish_only))
+ or (Subscribed == true)) ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ if MaxItems > 0 ->
+ Now = now(),
+ PubId = {Now, SubKey},
+ Item = case get_item(Nidx, ItemId) of
+ {result, OldItem} ->
+ OldItem#pubsub_item{modification = PubId,
+ payload = Payload};
+ _ ->
+ #pubsub_item{itemid = {ItemId, Nidx},
+ creation = {Now, GenKey},
+ modification = PubId,
+ payload = Payload}
+ end,
+ Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
+ {result, {NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
+ set_item(Item),
+ set_state(GenState#pubsub_state{items = NI}),
+ {result, {default, broadcast, OI}};
+ true ->
+ {result, {default, broadcast, []}}
+ end
+ end.
+%% @doc <p>This function is used to remove extra items, most notably when the
+%% maximum number of items has been reached.</p>
+%% <p>This function is used internally by the core PubSub module, as no
+%% permission check is performed.</p>
+%% <p>In the default plugin module, the oldest items are removed, but other
+%% rules can be used.</p>
+%% <p>If another PubSub plugin wants to delegate the item removal (and if the
+%% plugin is using the default pubsub storage), it can implements this function like this:
+%% ```remove_extra_items(Nidx, MaxItems, ItemIds) ->
+%% node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p>
+remove_extra_items(_Nidx, unlimited, ItemIds) ->
+ {result, {ItemIds, []}};
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ NewItems = lists:sublist(ItemIds, MaxItems),
+ OldItems = lists:nthtail(length(NewItems), ItemIds),
+ del_items(Nidx, OldItems),
+ {result, {NewItems, OldItems}}.
+
+%% @doc <p>Triggers item deletion.</p>
+%% <p>Default plugin: The user performing the deletion must be the node owner
+%% or a publisher, or PublishModel being open.</p>
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ SubKey = jlib:jid_tolower(Publisher),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ #pubsub_state{affiliation = Affiliation, items = Items} = GenState,
+ Allowed = Affiliation == publisher orelse
+ Affiliation == owner orelse
+ PublishModel == open orelse
+ case get_item(Nidx, ItemId) of
+ {result, #pubsub_item{creation = {_, GenKey}}} -> true;
+ _ -> false
+ end,
+ if not Allowed ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ case lists:member(ItemId, Items) of
+ true ->
+ del_item(Nidx, ItemId),
+ set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}),
+ {result, {default, broadcast}};
+ false ->
+ case Affiliation of
+ owner ->
+ {result, States} = get_states(Nidx),
+ lists:foldl(fun
+ (#pubsub_state{items = PI} = S, Res) ->
+ case lists:member(ItemId, PI) of
+ true ->
+ Nitems = lists:delete(ItemId, PI),
+ del_item(Nidx, ItemId),
+ set_state(S#pubsub_state{items = Nitems}),
+ {result, {default, broadcast}};
+ false ->
+ Res
+ end;
+ (_, Res) ->
+ Res
+ end,
+ {error, ?ERR_ITEM_NOT_FOUND}, States);
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end
+ end
+ end.
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ case GenState of
+ #pubsub_state{affiliation = owner} ->
+ {result, States} = get_states(Nidx),
+ lists:foreach(fun
+ (#pubsub_state{items = []}) ->
+ ok;
+ (#pubsub_state{items = Items} = S) ->
+ del_items(Nidx, Items),
+ set_state(S#pubsub_state{items = []})
+ end,
+ States),
+ {result, {default, broadcast}};
+ _ ->
+ {error, ?ERR_FORBIDDEN}
+ end.
+%% @doc <p>Return the current affiliations for the given user</p>
+%% <p>The default module reads affiliations in the main Mnesia
+%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
+%% table, it should return an empty list, as the affiliation will be read by
+%% the default PubSub module. Otherwise, it should return its own affiliation,
+%% that will be added to the affiliation stored in the main
+%% <tt>pubsub_state</tt> table.</p>
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
+ NodeTree = mod_pubsub:tree(Host),
+ Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
+ _ -> Acc
+ end
+ end,
+ [], States),
+ {result, Reply}.
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ {result, States} = get_states(Nidx),
+ Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
+ {result, lists:map(Tr, States)}.
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ #pubsub_state{affiliation = Affiliation} = get_state(Nidx, GenKey),
+ {result, Affiliation}.
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ case {Affiliation, GenState#pubsub_state.subscriptions} of
+ {none, []} -> del_state(Nidx, GenKey);
+ _ -> set_state(GenState#pubsub_state{affiliation = Affiliation})
+ end.
+%% @doc <p>Return the current subscriptions for the given user</p>
+%% <p>The default module reads subscriptions in the main Mnesia
+%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
+%% table, it should return an empty list, as the affiliation will be read by
+%% the default PubSub module. Otherwise, it should return its own affiliation,
+%% that will be added to the affiliation stored in the main
+%% <tt>pubsub_state</tt> table.</p>
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ {U, D, _} = SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ States = case SubKey of
+ GenKey ->
+ mnesia:match_object(#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
+ _ ->
+ mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'})
+ ++
+ mnesia:match_object(#pubsub_state{stateid = {SubKey, '_'}, _ = '_'})
+ end,
+ NodeTree = mod_pubsub:tree(Host),
+ Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {Host, _}} = Node ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Node, Sub, SubId, J} | Acc2]
+ end,
+ Acc, Ss);
+ _ ->
+ Acc
+ end
+ end,
+ [], States),
+ {result, Reply}.
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ {result, States} = get_states(Nidx),
+ Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) ->
+ case Subscriptions of
+ [_ | _] ->
+ lists:foldl(fun ({S, SubId}, Acc) ->
+ [{J, S, SubId} | Acc]
+ end,
+ [], Subscriptions);
+ [] ->
+ [];
+ _ ->
+ [{J, none}]
+ end
+ end,
+ {result, lists:flatmap(Tr, States)}.
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ SubState = get_state(Nidx, SubKey),
+ {result, SubState#pubsub_state.subscriptions}.
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ SubKey = jlib:jid_tolower(Owner),
+ SubState = get_state(Nidx, SubKey),
+ case {SubId, SubState#pubsub_state.subscriptions} of
+ {_, []} ->
+ case Subscription of
+ none ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
+ _ ->
+ new_subscription(Nidx, Owner, Subscription, SubState)
+ end;
+ {<<>>, [{_, SID}]} ->
+ case Subscription of
+ none -> unsub_with_subid(Nidx, SID, SubState);
+ _ -> replace_subscription({Subscription, SID}, SubState)
+ end;
+ {<<>>, [_ | _]} ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
+ _ ->
+ case Subscription of
+ none -> unsub_with_subid(Nidx, SubId, SubState);
+ _ -> replace_subscription({Subscription, SubId}, SubState)
+ end
+ end.
+replace_subscription(NewSub, SubState) ->
+ NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
+ set_state(SubState#pubsub_state{subscriptions = NewSubs}).
+
+replace_subscription(_, [], Acc) -> Acc;
+replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
+ replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
+
+new_subscription(Nidx, Owner, Sub, SubState) ->
+ SubId = pubsub_subscription:add_subscription(Owner, Nidx, []),
+ Subs = SubState#pubsub_state.subscriptions,
+ set_state(SubState#pubsub_state{subscriptions = [{Sub, SubId} | Subs]}),
+ {Sub, SubId}.
+
+unsub_with_subid(Nidx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) ->
+ pubsub_subscription:delete_subscription(SubState#pubsub_state.stateid, Nidx, SubId),
+ NewSubs = [{S, Sid}
+ || {S, Sid} <- SubState#pubsub_state.subscriptions,
+ SubId =/= Sid],
+ case {NewSubs, SubState#pubsub_state.affiliation} of
+ {[], none} -> del_state(Nidx, Entity);
+ _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ end.
+
+%% @doc <p>Returns a list of Owner's nodes on Host with pending
+%% subscriptions.</p>
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
+ States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
+ affiliation = owner,
+ _ = '_'}),
+ NodeIdxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
+ NodeTree = mod_pubsub:tree(Host),
+ Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
+ case lists:member(Nidx, NodeIdxs) of
+ true ->
+ case get_nodes_helper(NodeTree, S) of
+ {value, Node} -> [Node | Acc];
+ false -> Acc
+ end;
+ false ->
+ Acc
+ end
+ end,
+ [], pubsub_state),
+ {result, Reply}.
+
+get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
+ HasPending = fun
+ ({pending, _}) -> true;
+ (pending) -> true;
+ (_) -> false
+ end,
+ case lists:any(HasPending, Subs) of
+ true ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {_, Node}} -> {value, Node};
+ _ -> false
+ end;
+ false ->
+ false
+ end.
+%% @doc Returns the list of stored states for a given node.
+%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
+%% <p>We can consider that the pubsub_state table have been created by the main
+%% mod_pubsub module.</p>
+%% <p>PubSub plugins can store the states where they wants (for example in a
+%% relational database).</p>
+%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
+%% they can implement this function like this:
+%% ```get_states(Nidx) ->
+%% node_default:get_states(Nidx).'''</p>
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ States = case catch mnesia:match_object(
+ #pubsub_state{stateid = {'_', Nidx}, _ = '_'}) of
+ List when is_list(List) -> List;
+ _ -> []
+ end,
+ {result, States}.
+
+%% @doc <p>Returns a state (one state list), given its reference.</p>
+get_state(Nidx, Key) ->
+ StateId = {Key, Nidx},
+ case catch mnesia:read({pubsub_state, StateId}) of
+ [State] when is_record(State, pubsub_state) -> State;
+ _ -> #pubsub_state{stateid = StateId}
+ end.
-get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+%% @doc <p>Write a state into database.</p>
+set_state(State) when is_record(State, pubsub_state) ->
+ mnesia:write(State).
+%set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
+
+%% @doc <p>Delete a state from database.</p>
+del_state(Nidx, Key) ->
+ mnesia:delete({pubsub_state, {Key, Nidx}}).
+
+%% @doc Returns the list of stored items for a given node.
+%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
+%% <p>We can consider that the pubsub_item table have been created by the main
+%% mod_pubsub module.</p>
+%% <p>PubSub plugins can store the items where they wants (for example in a
+%% relational database), or they can even decide not to persist any items.</p>
+%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
+%% they can implement this function like this:
+%% ```get_items(Nidx, From) ->
+%% node_default:get_items(Nidx, From).'''</p>
+get_items(Nidx, From) ->
+ get_items(Nidx, From, none).
+get_items(Nidx, _From, _RSM) ->
+ Items = mnesia:match_object(#pubsub_item{itemid = {'_', Nidx}, _ = '_'}),
+ {result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), none}}.
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, _RSM) ->
+ SubKey = jlib:jid_tolower(JID),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ SubState = get_state(Nidx, SubKey),
+ Affiliation = GenState#pubsub_state.affiliation,
+ BareSubscriptions = GenState#pubsub_state.subscriptions,
+ FullSubscriptions = SubState#pubsub_state.subscriptions,
+ Whitelisted = can_fetch_item(Affiliation, BareSubscriptions) orelse
+ can_fetch_item(Affiliation, FullSubscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true ->
+ get_items(Nidx, JID)
+ end.
-set_state(State) ->
- node_hometree:set_state(State).
+%% @doc <p>Returns an item (one item list), given its reference.</p>
-get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+get_item(Nidx, ItemId) ->
+ case mnesia:read({pubsub_item, {ItemId, Nidx}}) of
+ [Item] when is_record(Item, pubsub_item) -> {result, Item};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ end.
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
- PresenceSubscription, RosterGroup, SubId, RSM).
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
+ SubKey = jlib:jid_tolower(JID),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ Affiliation = GenState#pubsub_state.affiliation,
+ Subscriptions = GenState#pubsub_state.subscriptions,
+ Whitelisted = can_fetch_item(Affiliation, Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true ->
+ get_item(Nidx, ItemId)
+ end.
-get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+%% @doc <p>Write an item into database.</p>
+set_item(Item) when is_record(Item, pubsub_item) ->
+ mnesia:write(Item).
+%set_item(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
-get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
- PresenceSubscription, RosterGroup, SubId).
+%% @doc <p>Delete an item from database.</p>
+del_item(Nidx, ItemId) ->
+ mnesia:delete({pubsub_item, {ItemId, Nidx}}).
-set_item(Item) ->
- node_hometree:set_item(Item).
+del_items(Nidx, ItemIds) ->
+ lists:foreach(fun (ItemId) -> del_item(Nidx, ItemId)
+ end,
+ ItemIds).
-get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+get_item_name(_Host, _Node, Id) ->
+ Id.
+%% @doc <p>Return the path of the node. In flat it's just node id.</p>
node_to_path(Node) ->
[(Node)].
@@ -178,3 +818,26 @@ path_to_node(Path) ->
% default case (used by PEP for example)
_ -> iolist_to_binary(Path)
end.
+
+can_fetch_item(owner, _) -> true;
+can_fetch_item(member, _) -> true;
+can_fetch_item(publisher, _) -> true;
+can_fetch_item(publish_only, _) -> false;
+can_fetch_item(outcast, _) -> false;
+can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
+%can_fetch_item(_Affiliation, _Subscription) -> false.
+
+is_subscribed(Subscriptions) ->
+ lists:any(fun
+ ({subscribed, _SubId}) -> true;
+ (_) -> false
+ end,
+ Subscriptions).
+
+first_in_list(_Pred, []) ->
+ false;
+first_in_list(Pred, [H | T]) ->
+ case Pred(H) of
+ true -> {value, H};
+ _ -> first_in_list(Pred, T)
+ end.
diff --git a/src/node_flat_odbc.erl b/src/node_flat_odbc.erl
index 9ac2643f8..c3f9c7e26 100644
--- a/src/node_flat_odbc.erl
+++ b/src/node_flat_odbc.erl
@@ -24,6 +24,12 @@
%%% @end
%%% ====================================================================
+%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
+%%% <p>It is used as a default for all unknown PubSub node type. It can serve
+%%% as a developer basis and reference to build its own custom pubsub node
+%%% types.</p>
+%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
+
-module(node_flat_odbc).
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
@@ -40,16 +46,23 @@
get_entity_subscriptions/2, get_node_subscriptions/1,
get_subscriptions/2, set_subscriptions/4,
get_pending_nodes/2, get_states/1, get_state/2,
- set_state/1, get_items/7, get_items/3, get_item/7,
+ set_state/1, get_items/6, get_items/2,
+ get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
path_to_node/1,
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
-init(Host, ServerHost, Opts) ->
- node_hometree_odbc:init(Host, ServerHost, Opts).
+-export([decode_jid/1, encode_jid/1,
+ decode_affiliation/1, decode_subscriptions/1,
+ encode_affiliation/1, encode_subscriptions/1,
+ encode_host/1]).
+
+init(_Host, _ServerHost, _Opts) ->
+ pubsub_subscription_odbc:init(),
+ ok.
-terminate(Host, ServerHost) ->
- node_hometree_odbc:terminate(Host, ServerHost).
+terminate(_Host, _ServerHost) ->
+ ok.
options() ->
[{odbc, true}, {rsm, true} | node_flat:options()].
@@ -57,110 +70,976 @@ options() ->
features() ->
[<<"rsm">> | node_flat:features()].
-%% use same code as node_hometree_odbc, but do not limite node to
-%% the home/localhost/user/... hierarchy
-%% any node is allowed
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree_odbc:create_node(Nidx, Owner).
+ {_U, _S, _R} = OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner},
+ catch ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ "values(">>, state_to_raw(Nidx, State), <<");">>]),
+ {result, {default, broadcast}}.
-delete_node(Removed) ->
- node_hometree_odbc:delete_node(Removed).
+delete_node(Nodes) ->
+ Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
+ Subscriptions = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, subscriptions "
+ "from pubsub_state where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
+ [{decode_jid(SJID), decode_subscriptions(Subs)} || [SJID, Subs] <- RItems];
+ _ ->
+ []
+ end,
+ {PubsubNode, Subscriptions}
+ end, Nodes),
+ {result, {default, broadcast, Reply}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
- PresenceSubscription, RosterGroup, Options).
+ SubKey = jlib:jid_tolower(Subscriber),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
+ Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
+ PendingSubscription = lists:any(fun
+ ({pending, _}) -> true;
+ (_) -> false
+ end,
+ Subscriptions),
+ Owner = Affiliation == owner,
+ if not Authorized ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ PendingSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
+ (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ %%ForbiddenAnonymous ->
+ %% % Requesting entity is anonymous
+ %% {error, ?ERR_FORBIDDEN};
+ true ->
+ {result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, Nidx, Options),
+ NewSub = case AccessModel of
+ authorize -> pending;
+ _ -> subscribed
+ end,
+ update_subscription(Nidx, SubKey, [{NewSub, SubId} | Subscriptions]),
+ case {NewSub, SendLast} of
+ {subscribed, never} -> {result, {default, subscribed, SubId}};
+ {subscribed, _} -> {result, {default, subscribed, SubId, send_last}};
+ {_, _} -> {result, {default, pending, SubId}}
+ end
+ end.
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ SubKey = jlib:jid_tolower(Subscriber),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, SubKey),
+ SubIdExists = case SubId of
+ <<>> -> false;
+ Binary when is_binary(Binary) -> true;
+ _ -> false
+ end,
+ if
+ %% Requesting entity is prohibited from unsubscribing entity
+ not Authorized ->
+ {error, ?ERR_FORBIDDEN};
+ %% Entity did not specify SubId
+ %%SubId == "", ?? ->
+ %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %% Invalid subscription identifier
+ %%InvalidSubId ->
+ %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %% Requesting entity is not a subscriber
+ Subscriptions == [] ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
+ %% Subid supplied, so use that.
+ SubIdExists ->
+ Sub = first_in_list(fun
+ ({_, S}) when S == SubId -> true;
+ (_) -> false
+ end,
+ Subscriptions),
+ case Sub of
+ {value, S} ->
+ delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions),
+ {result, default};
+ false ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
+ end;
+ %% Asking to remove all subscriptions to the given node
+ SubId == all ->
+ [delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions)
+ || S <- Subscriptions],
+ {result, default};
+ %% No subid supplied, but there's only one matching subscription
+ length(Subscriptions) == 1 ->
+ delete_subscription(SubKey, Nidx, hd(Subscriptions), Affiliation, Subscriptions),
+ {result, default};
+ %% No subid and more than one possible subscription match.
+ true ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
+ end.
-publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) ->
+ NewSubs = Subscriptions -- [{Subscription, SubId}],
+ pubsub_subscription_odbc:unsubscribe_node(SubKey, Nidx, SubId),
+ case {Affiliation, NewSubs} of
+ {none, []} -> del_state(Nidx, SubKey);
+ _ -> update_subscription(Nidx, SubKey, NewSubs)
+ end.
+
+publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
+ SubKey = jlib:jid_tolower(Publisher),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
+ Subscribed = case PublishModel of
+ subscribers -> node_flat:is_subscribed(Subscriptions);
+ _ -> undefined
+ end,
+ if not ((PublishModel == open) or
+ (PublishModel == publishers) and
+ ((Affiliation == owner)
+ or (Affiliation == publisher)
+ or (Affiliation == publish_only))
+ or (Subscribed == true)) ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ if MaxItems > 0 ->
+ PubId = {now(), SubKey},
+ set_item(#pubsub_item{itemid = {ItemId, Nidx},
+ creation = {now(), GenKey},
+ modification = PubId,
+ payload = Payload}),
+ Items = [ItemId | itemids(Nidx, GenKey) -- [ItemId]],
+ {result, {_, OI}} = remove_extra_items(Nidx, MaxItems, Items),
+ {result, {default, broadcast, OI}};
+ true ->
+ {result, {default, broadcast, []}}
+ end
+ end.
+remove_extra_items(_Nidx, unlimited, ItemIds) ->
+ {result, {ItemIds, []}};
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
+ NewItems = lists:sublist(ItemIds, MaxItems),
+ OldItems = lists:nthtail(length(NewItems), ItemIds),
+ del_items(Nidx, OldItems),
+ {result, {NewItems, OldItems}}.
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ SubKey = jlib:jid_tolower(Publisher),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ {result, Affiliation} = get_affiliation(Nidx, GenKey),
+ Allowed = Affiliation == publisher orelse
+ Affiliation == owner orelse
+ PublishModel == open orelse
+ case get_item(Nidx, ItemId) of
+ {result, #pubsub_item{creation = {_, GenKey}}} -> true;
+ _ -> false
+ end,
+ if not Allowed ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ case del_item(Nidx, ItemId) of
+ {updated, 1} -> {result, {default, broadcast}};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ end
+ end.
purge_node(Nidx, Owner) ->
- node_hometree_odbc:purge_node(Nidx, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ GenState = get_state(Nidx, GenKey),
+ case GenState of
+ #pubsub_state{affiliation = owner} ->
+ {result, States} = get_states(Nidx),
+ lists:foreach(fun
+ (#pubsub_state{items = []}) -> ok;
+ (#pubsub_state{items = Items}) -> del_items(Nidx, Items)
+ end,
+ States),
+ {result, {default, broadcast}};
+ _ ->
+ {error, ?ERR_FORBIDDEN}
+ end.
get_entity_affiliations(Host, Owner) ->
- node_hometree_odbc:get_entity_affiliations(Host, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ H = encode_host(Host),
+ J = encode_jid(GenKey),
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation "
+ "from pubsub_state i, pubsub_node n where "
+ "i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>])
+ of
+ {selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} ->
+ [{nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
+ || [N, T, I, A] <- RItems];
+ _ ->
+ []
+ end,
+ {result, Reply}.
get_node_affiliations(Nidx) ->
- node_hometree_odbc:get_node_affiliations(Nidx).
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state "
+ "where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"affiliation">>], RItems} ->
+ [{decode_jid(J), decode_affiliation(A)} || [J, A] <- RItems];
+ _ ->
+ []
+ end,
+ {result, Reply}.
get_affiliation(Nidx, Owner) ->
- node_hometree_odbc:get_affiliation(Nidx, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ J = encode_jid(GenKey),
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state "
+ "where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {selected, [<<"affiliation">>], [[A]]} ->
+ decode_affiliation(A);
+ _ ->
+ none
+ end,
+ {result, Reply}.
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree_odbc:set_affiliation(Nidx, Owner, Affiliation).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ {_, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey),
+ case {Affiliation, Subscriptions} of
+ {none, []} -> del_state(Nidx, GenKey);
+ _ -> update_affiliation(Nidx, GenKey, Affiliation)
+ end.
get_entity_subscriptions(Host, Owner) ->
- node_hometree_odbc:get_entity_subscriptions(Host, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ H = encode_host(Host),
+ SJ = encode_jid(SubKey),
+ GJ = encode_jid(GenKey),
+ Query = case SubKey of
+ GenKey ->
+ [<<"select node, type, i.nodeid, jid, subscriptions "
+ "from pubsub_state i, pubsub_node n "
+ "where i.nodeid = n.nodeid and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
+ _ ->
+ [<<"select node, type, i.nodeid, jid, subscriptions "
+ "from pubsub_state i, pubsub_node n "
+ "where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
+ end,
+ Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
+ lists:foldl(fun ([N, T, I, J, S], Acc) ->
+ Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] ->
+ [{Node, none, Jid} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Node, Sub, SubId, Jid} | Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ ->
+ []
+ end,
+ {result, Reply}.
+-spec(get_entity_subscriptions_for_send_last/2 ::
+ (
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result,
+ [{mod_pubsub:pubsubNode(),
+ mod_pubsub:subscription(),
+ mod_pubsub:subId(),
+ ljid()}]
+ }
+ ).
get_entity_subscriptions_for_send_last(Host, Owner) ->
- node_hometree_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ H = encode_host(Host),
+ SJ = encode_jid(SubKey),
+ GJ = encode_jid(GenKey),
+ Query = case SubKey of
+ GenKey ->
+ [<<"select node, type, i.nodeid, jid, subscriptions "
+ "from pubsub_state i, pubsub_node n, pubsub_node_option o "
+ "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
+ "and val='on_sub_and_presence' and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
+ _ ->
+ [<<"select node, type, i.nodeid, jid, subscriptions "
+ "from pubsub_state i, pubsub_node n, pubsub_node_option o "
+ "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
+ "and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
+ end,
+ Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
+ lists:foldl(fun ([N, T, I, J, S], Acc) ->
+ Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] ->
+ [{Node, none, Jid} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Node, Sub, SubId, Jid}| Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ ->
+ []
+ end,
+ {result, Reply}.
get_node_subscriptions(Nidx) ->
- node_hometree_odbc:get_node_subscriptions(Nidx).
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
+ "where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
+ lists:foldl(fun ([J, S], Acc) ->
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] ->
+ [{Jid, none} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Jid, Sub, SubId} | Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ ->
+ []
+ end,
+ {result, Reply}.
get_subscriptions(Nidx, Owner) ->
- node_hometree_odbc:get_subscriptions(Nidx, Owner).
+ SubKey = jlib:jid_tolower(Owner),
+ J = encode_jid(SubKey),
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state where "
+ "nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {selected, [<<"subscriptions">>], [[S]]} ->
+ decode_subscriptions(S);
+ _ ->
+ []
+ end,
+ {result, Reply}.
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ SubKey = jlib:jid_tolower(Owner),
+ SubState = get_state_without_itemids(Nidx, SubKey),
+ case {SubId, SubState#pubsub_state.subscriptions} of
+ {_, []} ->
+ case Subscription of
+ none ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
+ _ ->
+ new_subscription(Nidx, Owner, Subscription, SubState)
+ end;
+ {<<>>, [{_, SID}]} ->
+ case Subscription of
+ none -> unsub_with_subid(Nidx, SID, SubState);
+ _ -> replace_subscription({Subscription, SID}, SubState)
+ end;
+ {<<>>, [_ | _]} ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
+ _ ->
+ case Subscription of
+ none -> unsub_with_subid(Nidx, SubId, SubState);
+ _ -> replace_subscription({Subscription, SubId}, SubState)
+ end
+ end.
+
+replace_subscription(NewSub, SubState) ->
+ NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
+ set_state(SubState#pubsub_state{subscriptions = NewSubs}).
+
+replace_subscription(_, [], Acc) -> Acc;
+replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
+ replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
+
+new_subscription(Nidx, Owner, Subscription, SubState) ->
+ {result, SubId} = pubsub_subscription_odbc:subscribe_node(Owner, Nidx, []),
+ Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions],
+ set_state(SubState#pubsub_state{subscriptions = Subscriptions}),
+ {Subscription, SubId}.
+
+unsub_with_subid(Nidx, SubId, SubState) ->
+ pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
+ NewSubs = [{S, Sid}
+ || {S, Sid} <- SubState#pubsub_state.subscriptions,
+ SubId =/= Sid],
+ case {NewSubs, SubState#pubsub_state.affiliation} of
+ {[], none} -> del_state(Nidx, element(1, SubState#pubsub_state.stateid));
+ _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ end.
get_pending_nodes(Host, Owner) ->
- node_hometree_odbc:get_pending_nodes(Host, Owner).
+ GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
+ States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
+ affiliation = owner, _ = '_'}),
+ Nidxxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
+ NodeTree = mod_pubsub:tree(Host),
+ Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
+ case lists:member(Nidx, Nidxxs) of
+ true ->
+ case get_nodes_helper(NodeTree, S) of
+ {value, Node} -> [Node | Acc];
+ false -> Acc
+ end;
+ false ->
+ Acc
+ end
+ end,
+ [], pubsub_state),
+ {result, Reply}.
+
+get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
+ HasPending = fun
+ ({pending, _}) -> true;
+ (pending) -> true;
+ (_) -> false
+ end,
+ case lists:any(HasPending, Subs) of
+ true ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {_, Node}} -> {value, Node};
+ _ -> false
+ end;
+ false ->
+ false
+ end.
get_states(Nidx) ->
- node_hometree_odbc:get_states(Nidx).
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ "from pubsub_state where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected,
+ [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} ->
+ {result,
+ lists:map(fun ([SJID, Aff, Subs]) ->
+ #pubsub_state{stateid = {decode_jid(SJID), Nidx},
+ items = itemids(Nidx, SJID),
+ affiliation = decode_affiliation(Aff),
+ subscriptions = decode_subscriptions(Subs)}
+ end,
+ RItems)};
+ _ ->
+ {result, []}
+ end.
get_state(Nidx, JID) ->
- node_hometree_odbc:get_state(Nidx, JID).
+ State = get_state_without_itemids(Nidx, JID),
+ {SJID, _} = State#pubsub_state.stateid,
+ State#pubsub_state{items = itemids(Nidx, SJID)}.
+
+-spec(get_state_without_itemids/2 ::
+ (Nidx :: mod_pubsub:nodeIdx(),
+ Key :: ljid()) ->
+ mod_pubsub:pubsubState()
+ ).
+get_state_without_itemids(Nidx, JID) ->
+ J = encode_jid(JID),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ "from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected,
+ [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], [[SJID, Aff, Subs]]} ->
+ #pubsub_state{stateid = {decode_jid(SJID), Nidx},
+ affiliation = decode_affiliation(Aff),
+ subscriptions = decode_subscriptions(Subs)};
+ _ ->
+ #pubsub_state{stateid = {JID, Nidx}}
+ end.
set_state(State) ->
- node_hometree_odbc:set_state(State).
+ {_, Nidx} = State#pubsub_state.stateid,
+ set_state(Nidx, State).
+
+set_state(Nidx, State) ->
+ {JID, _} = State#pubsub_state.stateid,
+ J = encode_jid(JID),
+ S = encode_subscriptions(State#pubsub_state.subscriptions),
+ A = encode_affiliation(State#pubsub_state.affiliation),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
+ <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} ->
+ ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ "values('">>,
+ Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>])
+ end,
+ ok.
+
+del_state(Nidx, JID) ->
+ J = encode_jid(JID),
+ catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>,
+ J, <<"' and nodeid='">>, Nidx, <<"';">>]),
+ ok.
+
+get_items(Nidx, _From) ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' order by modification desc;">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
+ {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
+ _ ->
+ {result, []}
+ end.
+
+get_items(Nidx, From, none) ->
+ MaxItems = case catch
+ ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option "
+ "where nodeid='">>, Nidx, <<"' and name='max_items';">>])
+ of
+ {selected, [<<"val">>], [[Value]]} ->
+ Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))),
+ element(2, erl_parse:parse_term(Tokens));
+ _ ->
+ ?MAXITEMS
+ end,
+ get_items(Nidx, From, #rsm_in{max = MaxItems});
+get_items(Nidx, _From,
+ #rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) ->
+ Max = ejabberd_odbc:escape(jlib:i2l(M)),
+ {Way, Order} = case Direction of
+ % aft -> {<<"<">>, <<"desc">>};
+ % before when I == <<>> -> {<<"is not">>, <<"asc">>};
+ % before -> {<<">">>, <<"asc">>};
+ % _ when IncIndex =/= undefined ->
+ % {<<"<">>, <<"desc">>}; % using index
+ _ ->
+ {<<"is not">>, <<"desc">>}% Can be better
+ end,
+ [AttrName, Id] = case I of
+ undefined when IncIndex =/= undefined ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item pi "
+ "where exists ( select count(*) as count1 "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' and modification > pi.modification having count1 = ">>,
+ ejabberd_odbc:escape(jlib:i2l(IncIndex)), <<" );">>])
+ of
+ {selected, [_], [[O]]} ->
+ [<<"modification">>, <<"'", O/binary, "'">>];
+ _ ->
+ [<<"modification">>, <<"null">>]
+ end;
+ undefined ->
+ [<<"modification">>, <<"null">>];
+ <<>> ->
+ [<<"modification">>, <<"null">>];
+ I ->
+ [A, B] = str:tokens(ejabberd_odbc:escape(jlib:i2l(I)), <<"@">>),
+ [A, <<"'", B/binary, "'">>]
+ end,
+ Count = case catch
+ ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
+ of
+ {selected, [_], [[C]]} -> C;
+ _ -> <<"0">>
+ end,
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
+ AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
+ case RItems of
+ [[_, _, _, F, _]|_] ->
+ Index = case catch
+ ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item "
+ "where nodeid='">>, Nidx, <<"' and ">>,
+ AttrName, <<" > '">>, F, <<"';">>])
+ of
+ %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
+ {selected, [_], [[In]]} -> In;
+ _ -> <<"0">>
+ end,
+ [_, _, _, L, _] = lists:last(RItems),
+ RsmOut = #rsm_out{count = Count, index = Index,
+ first = <<"modification@", F/binary>>,
+ last = <<"modification@", (jlib:i2l(L))/binary>>},
+ {result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], RsmOut}};
+ [] ->
+ {result, {[], #rsm_out{count = Count}}}
+ end;
+ _ ->
+ {result, {[], none}}
+ end.
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
-get_items(Nidx, From, RSM) ->
- node_hometree_odbc:get_items(Nidx, From, RSM).
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
+ SubKey = jlib:jid_tolower(JID),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
+ Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true ->
+ get_items(Nidx, JID, RSM)
+ end.
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree_odbc:get_items(Nidx, JID, AccessModel,
- PresenceSubscription, RosterGroup, SubId, RSM).
+get_last_items(Nidx, _From, Count) ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' order by modification desc limit ">>, jlib:i2l(Count), <<";">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
+ {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
+ _ ->
+ {result, []}
+ end.
get_item(Nidx, ItemId) ->
- node_hometree_odbc:get_item(Nidx, ItemId).
+ I = ejabberd_odbc:escape(ItemId),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} ->
+ {result, raw_to_item(Nidx, RItem)};
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end.
-get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree_odbc:get_item(Nidx, ItemId, JID,
- AccessModel, PresenceSubscription, RosterGroup, SubId).
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
+ SubKey = jlib:jid_tolower(JID),
+ GenKey = jlib:jid_remove_resource(SubKey),
+ {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
+ Whitelisted = node_flat:can_fetch_item(Affiliation, Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ (Affiliation == outcast) or (Affiliation == publish_only) ->
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true ->
+ get_item(Nidx, ItemId)
+ end.
set_item(Item) ->
- node_hometree_odbc:set_item(Item).
+ {ItemId, Nidx} = Item#pubsub_item.itemid,
+ I = ejabberd_odbc:escape(ItemId),
+ {C, _} = Item#pubsub_item.creation,
+ {M, JID} = Item#pubsub_item.modification,
+ P = encode_jid(JID),
+ Payload = Item#pubsub_item.payload,
+ XML = ejabberd_odbc:escape(str:join([xml:element_to_binary(X) || X<-Payload], <<>>)),
+ S = fun ({T1, T2, T3}) ->
+ str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
+ end,
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>, P,
+ <<"', modification='">>, S(M),
+ <<"', payload='">>, XML,
+ <<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
+ of
+ {updated, 1} ->
+ ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
+ "publisher, creation, modification, payload) "
+ "values('">>, Nidx, <<"', '">>, I, <<"', '">>, P,
+ <<"', '">>, S(C), <<"', '">>, S(M),
+ <<"', '">>, XML, <<"');">>])
+ end,
+ ok.
-get_item_name(Host, Node, Id) ->
- node_hometree_odbc:get_item_name(Host, Node, Id).
+del_item(Nidx, ItemId) ->
+ I = ejabberd_odbc:escape(ItemId),
+ catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>,
+ I, <<"' and nodeid='">>, Nidx, <<"';">>]).
-get_last_items(Nidx, From, Count) ->
- node_hometree_odbc:get_last_items(Nidx, From, Count).
+del_items(_, []) ->
+ ok;
+del_items(Nidx, [ItemId]) ->
+ del_item(Nidx, ItemId);
+del_items(Nidx, ItemIds) ->
+ I = str:join([[<<"'">>, ejabberd_odbc:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
+ I, <<") and nodeid='">>, Nidx, <<"';">>]).
+
+get_item_name(_Host, _Node, Id) ->
+ Id.
node_to_path(Node) ->
- [(Node)].
+ node_flat:node_to_path(Node).
path_to_node(Path) ->
- case Path of
- % default slot
- [Node] -> iolist_to_binary(Node);
- % handle old possible entries, used when migrating database content to new format
- [Node | _] when is_binary(Node) ->
- iolist_to_binary(str:join([<<"">> | Path], <<"/">>));
- % default case (used by PEP for example)
- _ -> iolist_to_binary(Path)
+ node_flat:path_to_node(Path).
+
+
+first_in_list(_Pred, []) ->
+ false;
+first_in_list(Pred, [H | T]) ->
+ case Pred(H) of
+ true -> {value, H};
+ _ -> first_in_list(Pred, T)
+ end.
+
+itemids(Nidx, {U, S, R}) ->
+ itemids(Nidx, encode_jid({U, S, R}));
+itemids(Nidx, SJID) ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where "
+ "nodeid='">>, Nidx, <<"' and publisher like '">>, SJID,
+ <<"%' order by modification desc;">>])
+ of
+ {selected, [<<"itemid">>], RItems} ->
+ [ItemId || [ItemId] <- RItems];
+ _ ->
+ []
+ end.
+
+select_affiliation_subscriptions(Nidx, JID) ->
+ J = encode_jid(JID),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from "
+ "pubsub_state where nodeid='">>,
+ Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {selected, [<<"affiliation">>, <<"subscriptions">>], [[A, S]]} ->
+ {decode_affiliation(A), decode_subscriptions(S)};
+ _ ->
+ {none, []}
+ end.
+
+select_affiliation_subscriptions(Nidx, JID, JID) ->
+ select_affiliation_subscriptions(Nidx, JID);
+select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
+ {result, Affiliation} = get_affiliation(Nidx, GenKey),
+ {result, Subscriptions} = get_subscriptions(Nidx, SubKey),
+ {Affiliation, Subscriptions}.
+
+update_affiliation(Nidx, JID, Affiliation) ->
+ J = encode_jid(JID),
+ A = encode_affiliation(Affiliation),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>,
+ A, <<"' where nodeid='">>, Nidx,
+ <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} ->
+ ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ "values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>])
end.
+
+update_subscription(Nidx, JID, Subscription) ->
+ J = encode_jid(JID),
+ S = encode_subscriptions(Subscription),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
+ <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} ->
+ ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ "values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>])
+ end.
+
+-spec(decode_jid/1 ::
+ ( SJID :: binary())
+ -> ljid()
+ ).
+decode_jid(SJID) ->
+ jlib:jid_tolower(jlib:string_to_jid(SJID)).
+
+-spec(decode_affiliation/1 ::
+ ( Arg :: binary())
+ -> atom()
+ ).
+decode_affiliation(<<"o">>) -> owner;
+decode_affiliation(<<"p">>) -> publisher;
+decode_affiliation(<<"u">>) -> publish_only;
+decode_affiliation(<<"m">>) -> member;
+decode_affiliation(<<"c">>) -> outcast;
+decode_affiliation(_) -> none.
+
+-spec(decode_subscription/1 ::
+ ( Arg :: binary())
+ -> atom()
+ ).
+decode_subscription(<<"s">>) -> subscribed;
+decode_subscription(<<"p">>) -> pending;
+decode_subscription(<<"u">>) -> unconfigured;
+decode_subscription(_) -> none.
+
+-spec(decode_subscriptions/1 ::
+ ( Subscriptions :: binary())
+ -> [] | [{atom(), binary()},...]
+ ).
+decode_subscriptions(Subscriptions) ->
+ lists:foldl(fun (Subscription, Acc) ->
+ case str:tokens(Subscription, <<":">>) of
+ [S, SubId] -> [{decode_subscription(S), SubId} | Acc];
+ _ -> Acc
+ end
+ end,
+ [], str:tokens(Subscriptions, <<",">>)).
+
+-spec(encode_jid/1 ::
+ ( JID :: ljid())
+ -> binary()
+ ).
+encode_jid(JID) ->
+ ejabberd_odbc:escape(jlib:jid_to_string(JID)).
+
+-spec(encode_host/1 ::
+ ( Host :: host())
+ -> binary()
+ ).
+encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
+encode_host(Host) -> ejabberd_odbc:escape(Host).
+
+-spec(encode_affiliation/1 ::
+ ( Arg :: atom())
+ -> binary()
+ ).
+encode_affiliation(owner) -> <<"o">>;
+encode_affiliation(publisher) -> <<"p">>;
+encode_affiliation(publish_only) -> <<"u">>;
+encode_affiliation(member) -> <<"m">>;
+encode_affiliation(outcast) -> <<"c">>;
+encode_affiliation(_) -> <<"n">>.
+
+-spec(encode_subscription/1 ::
+ ( Arg :: atom())
+ -> binary()
+ ).
+encode_subscription(subscribed) -> <<"s">>;
+encode_subscription(pending) -> <<"p">>;
+encode_subscription(unconfigured) -> <<"u">>;
+encode_subscription(_) -> <<"n">>.
+
+-spec(encode_subscriptions/1 ::
+ ( Subscriptions :: [] | [{atom(), binary()},...])
+ -> binary()
+ ).
+encode_subscriptions(Subscriptions) ->
+ str:join([<<(encode_subscription(S))/binary, ":", SubId/binary>>
+ || {S, SubId} <- Subscriptions], <<",">>).
+
+%%% record getter/setter
+
+state_to_raw(Nidx, State) ->
+ {JID, _} = State#pubsub_state.stateid,
+ J = encode_jid(JID),
+ A = encode_affiliation(State#pubsub_state.affiliation),
+ S = encode_subscriptions(State#pubsub_state.subscriptions),
+ [<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>].
+
+raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
+ JID = decode_jid(SJID),
+ ToTime = fun (Str) ->
+ [T1, T2, T3] = str:tokens(Str, <<":">>),
+ {jlib:l2i(T1), jlib:l2i(T2), jlib:l2i(T3)}
+ end,
+ Payload = case xml_stream:parse_element(XML) of
+ {error, _Reason} -> [];
+ El -> [El]
+ end,
+ #pubsub_item{itemid = {ItemId, Nidx},
+ creation = {ToTime(Creation), JID},
+ modification = {ToTime(Modification), JID},
+ payload = Payload}.
diff --git a/src/node_hometree.erl b/src/node_hometree.erl
index 296fbdde7..e291887f1 100644
--- a/src/node_hometree.erl
+++ b/src/node_hometree.erl
@@ -24,20 +24,6 @@
%%% @end
%%% ====================================================================
-%%% @todo The item table should be handled by the plugin, but plugin that do
-%%% not want to manage it should be able to use the default behaviour.
-%%% @todo Plugin modules should be able to register to receive presence update
-%%% send to pubsub.
-
-%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
-%%% <p>It is used as a default for all unknown PubSub node type. It can serve
-%%% as a developer basis and reference to build its own custom pubsub node
-%%% types.</p>
-%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
-%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
-%%% development is still a work in progress. However, the system is already
-%%% useable and useful as is. Please, send us comments, feedback and
-%%% improvements.</p>
-module(node_hometree).
-behaviour(gen_pubsub_node).
@@ -55,81 +41,32 @@
get_entity_subscriptions/2, get_node_subscriptions/1,
get_subscriptions/2, set_subscriptions/4,
get_pending_nodes/2, get_states/1, get_state/2,
- set_state/1, get_items/6, get_items/2,
- get_items/7, get_items/3, get_item/7,
+ set_state/1, get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
path_to_node/1]).
-init(Host, ServerHost, _Opts) ->
- pubsub_subscription:init(),
- mnesia:create_table(pubsub_state,
- [{disc_copies, [node()]},
- {type, ordered_set},
- {attributes, record_info(fields, pubsub_state)}]),
- mnesia:create_table(pubsub_item,
- [{disc_only_copies, [node()]},
- {attributes, record_info(fields, pubsub_item)}]),
- ItemsFields = record_info(fields, pubsub_item),
- case mnesia:table_info(pubsub_item, attributes) of
- ItemsFields -> ok;
- _ -> mnesia:transform_table(pubsub_item, ignore, ItemsFields)
- end,
+init(Host, ServerHost, Opts) ->
+ node_flat:init(Host, ServerHost, Opts),
Owner = mod_pubsub:service_jid(Host),
mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>),
mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>),
ok.
-terminate(_Host, _ServerHost) ->
- ok.
+terminate(Host, ServerHost) ->
+ node_flat:terminate(Host, ServerHost).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, open},
- {roster_groups_allowed, []},
- {publish_model, publishers},
- {notification_type, headline},
- {max_payload_size, ?MAX_PAYLOAD_SIZE},
- {send_last_published_item, on_sub_and_presence},
- {deliver_notifications, true},
- {presence_based_delivery, false}].
+ node_flat:options().
features() ->
- [<<"create-nodes">>,
- <<"auto-create">>,
- <<"access-authorize">>,
- <<"delete-nodes">>,
- <<"delete-items">>,
- <<"get-pending">>,
- <<"instant-nodes">>,
- <<"manage-subscriptions">>,
- <<"modify-affiliations">>,
- <<"multi-subscribe">>,
- <<"outcast-affiliation">>,
- <<"persistent-items">>,
- <<"publish">>,
- <<"publish-only-affiliation">>,
- <<"purge-nodes">>,
- <<"retract-items">>,
- <<"retrieve-affiliations">>,
- <<"retrieve-items">>,
- <<"retrieve-subscriptions">>,
- <<"subscribe">>,
- <<"subscription-notifications">>,
- <<"subscription-options">>].
+ node_flat:features().
%% @doc Checks if the current user has the permission to create the requested node
-%% <p>In {@link node_default}, the permission is decided by the place in the
+%% <p>In hometree node, the permission is decided by the place in the
%% hierarchy where the user is creating the node. The access parameter is also
-%% checked in the default module. This parameter depends on the value of the
+%% checked. This parameter depends on the value of the
%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
-%% <p>This function also check that node can be created a a children of its
+%% <p>This function also check that node can be created as a children of its
%% parent node</p>
create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
@@ -150,707 +87,92 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
{result, Allowed}.
create_node(Nidx, Owner) ->
- OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- set_state(#pubsub_state{stateid = {OwnerKey, Nidx},
- affiliation = owner}),
- {result, {default, broadcast}}.
+ node_flat:create_node(Nidx, Owner).
delete_node(Nodes) ->
- Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
- lists:map(fun (S) -> {J, S} end, Ss)
- end,
- Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
- {result, States} = get_states(Nidx),
- lists:foreach(fun (#pubsub_state{stateid = {LJID, _}, items = Items}) ->
- del_items(Nidx, Items),
- del_state(Nidx, LJID)
- end, States),
- {PubsubNode, lists:flatmap(Tr, States)}
- end, Nodes),
- {result, {default, broadcast, Reply}}.
-
-%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
-%% <p>The mechanism works as follow:
-%% <ul>
-%% <li>The main PubSub module prepares the subscription and passes the
-%% result of the preparation as a record.</li>
-%% <li>This function gets the prepared record and several other parameters and
-%% can decide to:<ul>
-%% <li>reject the subscription;</li>
-%% <li>allow it as is, letting the main module perform the database
-%% persistance;</li>
-%% <li>allow it, modifying the record. The main module will store the
-%% modified record;</li>
-%% <li>allow it, but perform the needed persistance operations.</li></ul>
-%% </li></ul></p>
-%% <p>The selected behaviour depends on the return parameter:
-%% <ul>
-%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
-%% subscription will actually be performed.</li>
-%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
-%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
-%% parameter contains an error, no subscription will be performed.</li>
-%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
-%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
-%% passed in parameter <tt>SubscribeResult</tt>.</li>
-%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
-%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
-%% no further persistance operation will be performed. This case is used,
-%% when the plugin module is doing the persistance by itself or when it want
-%% to completly disable persistance.</li></ul>
-%% </p>
-%% <p>In the default plugin module, the record is unchanged.</p>
+ node_flat:delete_node(Nodes).
+
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- SubKey = jlib:jid_tolower(Subscriber),
- GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
- GenState = get_state(Nidx, GenKey),
- SubState = case SubKey of
- GenKey -> GenState;
- _ -> get_state(Nidx, SubKey)
- end,
- Affiliation = GenState#pubsub_state.affiliation,
- Subscriptions = SubState#pubsub_state.subscriptions,
- Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
- PendingSubscription = lists:any(fun
- ({pending, _}) -> true;
- (_) -> false
- end,
- Subscriptions),
- Owner = Affiliation == owner,
- if not Authorized ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- PendingSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
- (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- %%ForbiddenAnonymous ->
- %% % Requesting entity is anonymous
- %% {error, ?ERR_FORBIDDEN};
- true ->
- SubId = pubsub_subscription:add_subscription(Subscriber, Nidx, Options),
- NewSub = case AccessModel of
- authorize -> pending;
- _ -> subscribed
- end,
- set_state(SubState#pubsub_state{subscriptions =
- [{NewSub, SubId} | Subscriptions]}),
- case {NewSub, SendLast} of
- {subscribed, never} ->
- {result, {default, subscribed, SubId}};
- {subscribed, _} ->
- {result, {default, subscribed, SubId, send_last}};
- {_, _} ->
- {result, {default, pending, SubId}}
- end
- end.
+ node_flat:subscribe_node(Nidx, Sender, Subscriber,
+ AccessModel, SendLast, PresenceSubscription,
+ RosterGroup, Options).
-%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- SubKey = jlib:jid_tolower(Subscriber),
- GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
- GenState = get_state(Nidx, GenKey),
- SubState = case SubKey of
- GenKey -> GenState;
- _ -> get_state(Nidx, SubKey)
- end,
- Subscriptions = lists:filter(fun
- ({_Sub, _SubId}) -> true;
- (_SubId) -> false
- end,
- SubState#pubsub_state.subscriptions),
- SubIdExists = case SubId of
- <<>> -> false;
- Binary when is_binary(Binary) -> true;
- _ -> false
- end,
- if
- %% Requesting entity is prohibited from unsubscribing entity
- not Authorized ->
- {error, ?ERR_FORBIDDEN};
- %% Entity did not specify SubId
- %%SubId == "", ?? ->
- %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %% Invalid subscription identifier
- %%InvalidSubId ->
- %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- %% Requesting entity is not a subscriber
- Subscriptions == [] ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
- %% Subid supplied, so use that.
- SubIdExists ->
- Sub = first_in_list(fun
- ({_, S}) when S == SubId -> true;
- (_) -> false
- end,
- SubState#pubsub_state.subscriptions),
- case Sub of
- {value, S} ->
- delete_subscriptions(SubKey, Nidx, [S], SubState),
- {result, default};
- false ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
- end;
- %% Asking to remove all subscriptions to the given node
- SubId == all ->
- delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
- {result, default};
- %% No subid supplied, but there's only one matching subscription
- length(Subscriptions) == 1 ->
- delete_subscriptions(SubKey, Nidx, Subscriptions, SubState),
- {result, default};
- %% No subid and more than one possible subscription match.
- true ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
- end.
-
-delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) ->
- NewSubs = lists:foldl(fun ({Subscription, SubId}, Acc) ->
- pubsub_subscription:delete_subscription(SubKey, Nidx, SubId),
- Acc -- [{Subscription, SubId}]
- end, SubState#pubsub_state.subscriptions, Subscriptions),
- case {SubState#pubsub_state.affiliation, NewSubs} of
- {none, []} -> del_state(Nidx, SubKey);
- _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
- end.
-
-%% @doc <p>Publishes the item passed as parameter.</p>
-%% <p>The mechanism works as follow:
-%% <ul>
-%% <li>The main PubSub module prepares the item to publish and passes the
-%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
-%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
-%% <li>reject the publication;</li>
-%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
-%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
-%% <li>allow it, but perform the needed persistance operations.</li></ul>
-%% </li></ul></p>
-%% <p>The selected behaviour depends on the return parameter:
-%% <ul>
-%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
-%% publication is actually performed.</li>
-%% <li><tt>true</tt>: Publication operation is allowed, based on the
-%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
-%% parameter contains an error, no subscription will actually be
-%% performed.</li>
-%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
-%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
-%% in parameter <tt>Item</tt>. The persistance will be performed by the main
-%% module.</li>
-%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
-%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
-%% no further persistance operation will be performed. This case is used,
-%% when the plugin module is doing the persistance by itself or when it want
-%% to completly disable persistance.</li></ul>
-%% </p>
-%% <p>In the default plugin module, the record is unchanged.</p>
-publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
- SubKey = jlib:jid_tolower(Publisher),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- SubState = case SubKey of
- GenKey -> GenState;
- _ -> get_state(Nidx, SubKey)
- end,
- Affiliation = GenState#pubsub_state.affiliation,
- Subscribed = case PublishModel of
- subscribers -> is_subscribed(SubState#pubsub_state.subscriptions);
- _ -> undefined
- end,
- if not ((PublishModel == open) or
- (PublishModel == publishers) and
- ((Affiliation == owner)
- or (Affiliation == publisher)
- or (Affiliation == publish_only))
- or (Subscribed == true)) ->
- {error, ?ERR_FORBIDDEN};
- true ->
- if MaxItems > 0 ->
- Now = now(),
- PubId = {Now, SubKey},
- Item = case get_item(Nidx, ItemId) of
- {result, OldItem} ->
- OldItem#pubsub_item{modification = PubId,
- payload = Payload};
- _ ->
- #pubsub_item{itemid = {ItemId, Nidx},
- creation = {Now, GenKey},
- modification = PubId,
- payload = Payload}
- end,
- Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
- {result, {NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
- set_item(Item),
- set_state(GenState#pubsub_state{items = NI}),
- {result, {default, broadcast, OI}};
- true ->
- {result, {default, broadcast, []}}
- end
- end.
-
-%% @doc <p>This function is used to remove extra items, most notably when the
-%% maximum number of items has been reached.</p>
-%% <p>This function is used internally by the core PubSub module, as no
-%% permission check is performed.</p>
-%% <p>In the default plugin module, the oldest items are removed, but other
-%% rules can be used.</p>
-%% <p>If another PubSub plugin wants to delegate the item removal (and if the
-%% plugin is using the default pubsub storage), it can implements this function like this:
-%% ```remove_extra_items(Nidx, MaxItems, ItemIds) ->
-%% node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p>
-remove_extra_items(_Nidx, unlimited, ItemIds) ->
- {result, {ItemIds, []}};
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- NewItems = lists:sublist(ItemIds, MaxItems),
- OldItems = lists:nthtail(length(NewItems), ItemIds),
- del_items(Nidx, OldItems),
- {result, {NewItems, OldItems}}.
-
-%% @doc <p>Triggers item deletion.</p>
-%% <p>Default plugin: The user performing the deletion must be the node owner
-%% or a publisher, or PublishModel being open.</p>
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- SubKey = jlib:jid_tolower(Publisher),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- #pubsub_state{affiliation = Affiliation, items = Items} = GenState,
- Allowed = Affiliation == publisher orelse
- Affiliation == owner orelse
- PublishModel == open orelse
- case get_item(Nidx, ItemId) of
- {result, #pubsub_item{creation = {_, GenKey}}} -> true;
- _ -> false
- end,
- if not Allowed ->
- {error, ?ERR_FORBIDDEN};
- true ->
- case lists:member(ItemId, Items) of
- true ->
- del_item(Nidx, ItemId),
- set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}),
- {result, {default, broadcast}};
- false ->
- case Affiliation of
- owner ->
- {result, States} = get_states(Nidx),
- lists:foldl(fun
- (#pubsub_state{items = PI} = S, Res) ->
- case lists:member(ItemId, PI) of
- true ->
- Nitems = lists:delete(ItemId, PI),
- del_item(Nidx, ItemId),
- set_state(S#pubsub_state{items = Nitems}),
- {result, {default, broadcast}};
- false ->
- Res
- end;
- (_, Res) ->
- Res
- end,
- {error, ?ERR_ITEM_NOT_FOUND}, States);
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
- end
- end
- end.
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- case GenState of
- #pubsub_state{affiliation = owner} ->
- {result, States} = get_states(Nidx),
- lists:foreach(fun
- (#pubsub_state{items = []}) ->
- ok;
- (#pubsub_state{items = Items} = S) ->
- del_items(Nidx, Items),
- set_state(S#pubsub_state{items = []})
- end,
- States),
- {result, {default, broadcast}};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end.
-
-%% @doc <p>Return the current affiliations for the given user</p>
-%% <p>The default module reads affiliations in the main Mnesia
-%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
-%% table, it should return an empty list, as the affiliation will be read by
-%% the default PubSub module. Otherwise, it should return its own affiliation,
-%% that will be added to the affiliation stored in the main
-%% <tt>pubsub_state</tt> table.</p>
+ node_flat:purge_node(Nidx, Owner).
+
get_entity_affiliations(Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
- NodeTree = mod_pubsub:tree(Host),
- Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
- _ -> Acc
- end
- end,
- [], States),
- {result, Reply}.
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- {result, States} = get_states(Nidx),
- Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
- {result, lists:map(Tr, States)}.
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- #pubsub_state{affiliation = Affiliation} = get_state(Nidx, GenKey),
- {result, Affiliation}.
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- case {Affiliation, GenState#pubsub_state.subscriptions} of
- {none, []} -> del_state(Nidx, GenKey);
- _ -> set_state(GenState#pubsub_state{affiliation = Affiliation})
- end.
-
-%% @doc <p>Return the current subscriptions for the given user</p>
-%% <p>The default module reads subscriptions in the main Mnesia
-%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
-%% table, it should return an empty list, as the affiliation will be read by
-%% the default PubSub module. Otherwise, it should return its own affiliation,
-%% that will be added to the affiliation stored in the main
-%% <tt>pubsub_state</tt> table.</p>
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
+
get_entity_subscriptions(Host, Owner) ->
- {U, D, _} = SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- States = case SubKey of
- GenKey ->
- mnesia:match_object(#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
- _ ->
- mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'})
- ++
- mnesia:match_object(#pubsub_state{stateid = {SubKey, '_'}, _ = '_'})
- end,
- NodeTree = mod_pubsub:tree(Host),
- Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {Host, _}} = Node ->
- lists:foldl(fun ({Sub, SubId}, Acc2) ->
- [{Node, Sub, SubId, J} | Acc2]
- end,
- Acc, Ss);
- _ ->
- Acc
- end
- end,
- [], States),
- {result, Reply}.
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- {result, States} = get_states(Nidx),
- Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) ->
- case Subscriptions of
- [_ | _] ->
- lists:foldl(fun ({S, SubId}, Acc) ->
- [{J, S, SubId} | Acc]
- end,
- [], Subscriptions);
- [] ->
- [];
- _ ->
- [{J, none}]
- end
- end,
- {result, lists:flatmap(Tr, States)}.
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- SubState = get_state(Nidx, SubKey),
- {result, SubState#pubsub_state.subscriptions}.
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- SubKey = jlib:jid_tolower(Owner),
- SubState = get_state(Nidx, SubKey),
- case {SubId, SubState#pubsub_state.subscriptions} of
- {_, []} ->
- case Subscription of
- none ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
- _ ->
- new_subscription(Nidx, Owner, Subscription, SubState)
- end;
- {<<>>, [{_, SID}]} ->
- case Subscription of
- none -> unsub_with_subid(Nidx, SID, SubState);
- _ -> replace_subscription({Subscription, SID}, SubState)
- end;
- {<<>>, [_ | _]} ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
- _ ->
- case Subscription of
- none -> unsub_with_subid(Nidx, SubId, SubState);
- _ -> replace_subscription({Subscription, SubId}, SubState)
- end
- end.
-
-replace_subscription(NewSub, SubState) ->
- NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
- set_state(SubState#pubsub_state{subscriptions = NewSubs}).
-
-replace_subscription(_, [], Acc) -> Acc;
-replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
- replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
-
-new_subscription(Nidx, Owner, Sub, SubState) ->
- SubId = pubsub_subscription:add_subscription(Owner, Nidx, []),
- Subs = SubState#pubsub_state.subscriptions,
- set_state(SubState#pubsub_state{subscriptions = [{Sub, SubId} | Subs]}),
- {Sub, SubId}.
-
-unsub_with_subid(Nidx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) ->
- pubsub_subscription:delete_subscription(SubState#pubsub_state.stateid, Nidx, SubId),
- NewSubs = [{S, Sid}
- || {S, Sid} <- SubState#pubsub_state.subscriptions,
- SubId =/= Sid],
- case {NewSubs, SubState#pubsub_state.affiliation} of
- {[], none} -> del_state(Nidx, Entity);
- _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
- end.
-
-%% @doc <p>Returns a list of Owner's nodes on Host with pending
-%% subscriptions.</p>
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
get_pending_nodes(Host, Owner) ->
- GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
- affiliation = owner,
- _ = '_'}),
- NodeIdxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
- NodeTree = mod_pubsub:tree(Host),
- Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
- case lists:member(Nidx, NodeIdxs) of
- true ->
- case get_nodes_helper(NodeTree, S) of
- {value, Node} -> [Node | Acc];
- false -> Acc
- end;
- false ->
- Acc
- end
- end,
- [], pubsub_state),
- {result, Reply}.
-
-get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
- HasPending = fun
- ({pending, _}) -> true;
- (pending) -> true;
- (_) -> false
- end,
- case lists:any(HasPending, Subs) of
- true ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {_, Node}} -> {value, Node};
- _ -> false
- end;
- false ->
- false
- end.
-
-%% @doc Returns the list of stored states for a given node.
-%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
-%% <p>We can consider that the pubsub_state table have been created by the main
-%% mod_pubsub module.</p>
-%% <p>PubSub plugins can store the states where they wants (for example in a
-%% relational database).</p>
-%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
-%% they can implement this function like this:
-%% ```get_states(Nidx) ->
-%% node_default:get_states(Nidx).'''</p>
+ node_flat:get_pending_nodes(Host, Owner).
+
get_states(Nidx) ->
- States = case catch mnesia:match_object(
- #pubsub_state{stateid = {'_', Nidx}, _ = '_'}) of
- List when is_list(List) -> List;
- _ -> []
- end,
- {result, States}.
-
-%% @doc <p>Returns a state (one state list), given its reference.</p>
-get_state(Nidx, Key) ->
- StateId = {Key, Nidx},
- case catch mnesia:read({pubsub_state, StateId}) of
- [State] when is_record(State, pubsub_state) -> State;
- _ -> #pubsub_state{stateid = StateId}
- end.
-
-%% @doc <p>Write a state into database.</p>
-set_state(State) when is_record(State, pubsub_state) ->
- mnesia:write(State).
-%set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
-
-%% @doc <p>Delete a state from database.</p>
-del_state(Nidx, Key) ->
- mnesia:delete({pubsub_state, {Key, Nidx}}).
-
-%% @doc Returns the list of stored items for a given node.
-%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
-%% <p>We can consider that the pubsub_item table have been created by the main
-%% mod_pubsub module.</p>
-%% <p>PubSub plugins can store the items where they wants (for example in a
-%% relational database), or they can even decide not to persist any items.</p>
-%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
-%% they can implement this function like this:
-%% ```get_items(Nidx, From) ->
-%% node_default:get_items(Nidx, From).'''</p>
-get_items(Nidx, From) ->
- get_items(Nidx, From, none).
-get_items(Nidx, _From, _RSM) ->
- Items = mnesia:match_object(#pubsub_item{itemid = {'_', Nidx}, _ = '_'}),
- {result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), none}}.
-
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, _RSM) ->
- SubKey = jlib:jid_tolower(JID),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- SubState = get_state(Nidx, SubKey),
- Affiliation = GenState#pubsub_state.affiliation,
- Subscriptions = SubState#pubsub_state.subscriptions,
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- (AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_items(Nidx, JID)
- end.
-
-%% @doc <p>Returns an item (one item list), given its reference.</p>
+ node_flat:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- case mnesia:read({pubsub_item, {ItemId, Nidx}}) of
- [Item] when is_record(Item, pubsub_item) -> {result, Item};
- _ -> {error, ?ERR_ITEM_NOT_FOUND}
- end.
-
-get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
- SubKey = jlib:jid_tolower(JID),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- Affiliation = GenState#pubsub_state.affiliation,
- Subscriptions = GenState#pubsub_state.subscriptions,
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- (AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_item(Nidx, ItemId)
- end.
-
-%% @doc <p>Write an item into database.</p>
-set_item(Item) when is_record(Item, pubsub_item) ->
- mnesia:write(Item).
-%set_item(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}.
-
-%% @doc <p>Delete an item from database.</p>
-del_item(Nidx, ItemId) ->
- mnesia:delete({pubsub_item, {ItemId, Nidx}}).
-
-del_items(Nidx, ItemIds) ->
- lists:foreach(fun (ItemId) -> del_item(Nidx, ItemId)
- end,
- ItemIds).
-
-get_item_name(_Host, _Node, Id) ->
- Id.
-
-%% @doc <p>Return the name of the node if known: Default is to return
-%% node id.</p>
+ node_flat:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat:get_item_name(Host, Node, Id).
+
+%% @doc <p>Return the path of the node.</p>
node_to_path(Node) ->
str:tokens(Node, <<"/">>).
path_to_node([]) -> <<>>;
path_to_node(Path) -> iolist_to_binary(str:join([<<"">> | Path], <<"/">>)).
-can_fetch_item(owner, _) -> true;
-can_fetch_item(member, _) -> true;
-can_fetch_item(publisher, _) -> true;
-can_fetch_item(publish_only, _) -> false;
-can_fetch_item(outcast, _) -> false;
-can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
-%can_fetch_item(_Affiliation, _Subscription) -> false.
-
-is_subscribed(Subscriptions) ->
- lists:any(fun
- ({subscribed, _SubId}) -> true;
- (_) -> false
- end,
- Subscriptions).
-
-first_in_list(_Pred, []) ->
- false;
-first_in_list(Pred, [H | T]) ->
- case Pred(H) of
- true -> {value, H};
- _ -> first_in_list(Pred, T)
- end.
diff --git a/src/node_hometree_odbc.erl b/src/node_hometree_odbc.erl
index e99ce7247..d5d29cf75 100644
--- a/src/node_hometree_odbc.erl
+++ b/src/node_hometree_odbc.erl
@@ -24,20 +24,6 @@
%%% @end
%%% ====================================================================
-%%% @todo The item table should be handled by the plugin, but plugin that do
-%%% not want to manage it should be able to use the default behaviour.
-%%% @todo Plugin modules should be able to register to receive presence update
-%%% send to pubsub.
-
-%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
-%%% <p>It is used as a default for all unknown PubSub node type. It can serve
-%%% as a developer basis and reference to build its own custom pubsub node
-%%% types.</p>
-%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
-%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
-%%% development is still a work in progress. However, the system is already
-%%% useable and useful as is. Please, send us comments, feedback and
-%%% improvements.</p>
-module(node_hometree_odbc).
-behaviour(gen_pubsub_node).
@@ -55,26 +41,20 @@
get_entity_subscriptions/2, get_node_subscriptions/1,
get_subscriptions/2, set_subscriptions/4,
get_pending_nodes/2, get_states/1, get_state/2,
- set_state/1, get_items/7, get_items/3,
- get_items/6, get_items/2, get_item/7,
+ set_state/1, get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
path_to_node/1,
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
--export([decode_jid/1, encode_jid/1,
- decode_affiliation/1, decode_subscriptions/1,
- encode_affiliation/1, encode_subscriptions/1,
- encode_host/1]).
-
-init(Host, ServerHost, _Opts) ->
- pubsub_subscription_odbc:init(),
+init(Host, ServerHost, Opts) ->
+ node_flat_odbc:init(Host, ServerHost, Opts),
Owner = mod_pubsub:service_jid(Host),
mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>),
mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>),
ok.
-terminate(_Host, _ServerHost) ->
- ok.
+terminate(Host, ServerHost) ->
+ node_flat_odbc:terminate(Host, ServerHost).
options() ->
[{odbc, true}, {rsm, true} | node_hometree:options()].
@@ -82,921 +62,96 @@ options() ->
features() ->
[<<"rsm">> | node_hometree:features()].
-%% @doc Checks if the current user has the permission to create the requested node
-%% <p>In {@link node_default}, the permission is decided by the place in the
-%% hierarchy where the user is creating the node. The access parameter is also
-%% checked in the default module. This parameter depends on the value of the
-%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
-%% <p>This function also check that node can be created a a children of its
-%% parent node</p>
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- {_U, _S, _R} = OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner},
- catch ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values(">>, state_to_raw(Nidx, State), <<");">>]),
- {result, {default, broadcast}}.
+ node_flat_odbc:create_node(Nidx, Owner).
delete_node(Nodes) ->
- Reply = lists:map(fun (#pubsub_node{id = Nidx} = Node) ->
- Subscriptions = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, subscriptions "
- "from pubsub_state where nodeid='">>, Nidx, <<"';">>])
- of
- {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
- [{decode_jid(SJID), decode_subscriptions(Subs)} || [SJID, Subs] <- RItems];
- _ ->
- []
- end,
- {Node, Subscriptions}
- end,
- Nodes),
- {result, {default, broadcast, Reply}}.
+ node_flat_odbc:delete_node(Nodes).
-%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
-%% <p>The mechanism works as follow:
-%% <ul>
-%% <li>The main PubSub module prepares the subscription and passes the
-%% result of the preparation as a record.</li>
-%% <li>This function gets the prepared record and several other parameters and
-%% can decide to:<ul>
-%% <li>reject the subscription;</li>
-%% <li>allow it as is, letting the main module perform the database
-%% persistance;</li>
-%% <li>allow it, modifying the record. The main module will store the
-%% modified record;</li>
-%% <li>allow it, but perform the needed persistance operations.</li></ul>
-%% </li></ul></p>
-%% <p>The selected behaviour depends on the return parameter:
-%% <ul>
-%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
-%% subscription will actually be performed.</li>
-%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
-%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
-%% parameter contains an error, no subscription will be performed.</li>
-%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
-%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
-%% passed in parameter <tt>SubscribeResult</tt>.</li>
-%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
-%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
-%% no further persistance operation will be performed. This case is used,
-%% when the plugin module is doing the persistance by itself or when it want
-%% to completly disable persistance.</li></ul>
-%% </p>
-%% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- SubKey = jlib:jid_tolower(Subscriber),
- GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
- Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
- PendingSubscription = lists:any(fun
- ({pending, _}) -> true;
- (_) -> false
- end,
- Subscriptions),
- Owner = Affiliation == owner,
- if not Authorized ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- PendingSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)};
- (AccessModel == presence) and (not PresenceSubscription) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and (not RosterGroup) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and (not Whitelisted) and (not Owner) ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- %%ForbiddenAnonymous ->
- %% % Requesting entity is anonymous
- %% {error, ?ERR_FORBIDDEN};
- true ->
- {result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, Nidx, Options),
- NewSub = case AccessModel of
- authorize -> pending;
- _ -> subscribed
- end,
- update_subscription(Nidx, SubKey, [{NewSub, SubId} | Subscriptions]),
- case {NewSub, SendLast} of
- {subscribed, never} -> {result, {default, subscribed, SubId}};
- {subscribed, _} -> {result, {default, subscribed, SubId, send_last}};
- {_, _} -> {result, {default, pending, SubId}}
- end
- end.
+ node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
-%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- SubKey = jlib:jid_tolower(Subscriber),
- GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey,
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, SubKey),
- SubIdExists = case SubId of
- <<>> -> false;
- Binary when is_binary(Binary) -> true;
- _ -> false
- end,
- if
- %% Requesting entity is prohibited from unsubscribing entity
- not Authorized ->
- {error, ?ERR_FORBIDDEN};
- %% Entity did not specify SubId
- %%SubId == "", ?? ->
- %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %% Invalid subscription identifier
- %%InvalidSubId ->
- %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- %% Requesting entity is not a subscriber
- Subscriptions == [] ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)};
- %% Subid supplied, so use that.
- SubIdExists ->
- Sub = first_in_list(fun
- ({_, S}) when S == SubId -> true;
- (_) -> false
- end,
- Subscriptions),
- case Sub of
- {value, S} ->
- delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions),
- {result, default};
- false ->
- {error,
- ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}
- end;
- %% Asking to remove all subscriptions to the given node
- SubId == all ->
- [delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions)
- || S <- Subscriptions],
- {result, default};
- %% No subid supplied, but there's only one matching subscription
- length(Subscriptions) == 1 ->
- delete_subscription(SubKey, Nidx, hd(Subscriptions), Affiliation, Subscriptions),
- {result, default};
- %% No subid and more than one possible subscription match.
- true ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
- end.
+ node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
-delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) ->
- NewSubs = Subscriptions -- [{Subscription, SubId}],
- pubsub_subscription_odbc:unsubscribe_node(SubKey, Nidx, SubId),
- case {Affiliation, NewSubs} of
- {none, []} -> del_state(Nidx, SubKey);
- _ -> update_subscription(Nidx, SubKey, NewSubs)
- end.
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
-%% @doc <p>Publishes the item passed as parameter.</p>
-%% <p>The mechanism works as follow:
-%% <ul>
-%% <li>The main PubSub module prepares the item to publish and passes the
-%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
-%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
-%% <li>reject the publication;</li>
-%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
-%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
-%% <li>allow it, but perform the needed persistance operations.</li></ul>
-%% </li></ul></p>
-%% <p>The selected behaviour depends on the return parameter:
-%% <ul>
-%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
-%% publication is actually performed.</li>
-%% <li><tt>true</tt>: Publication operation is allowed, based on the
-%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
-%% parameter contains an error, no subscription will actually be
-%% performed.</li>
-%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
-%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
-%% in parameter <tt>Item</tt>. The persistance will be performed by the main
-%% module.</li>
-%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
-%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
-%% no further persistance operation will be performed. This case is used,
-%% when the plugin module is doing the persistance by itself or when it want
-%% to completly disable persistance.</li></ul>
-%% </p>
-%% <p>In the default plugin module, the record is unchanged.</p>
-publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
- SubKey = jlib:jid_tolower(Publisher),
- GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
- Subscribed = case PublishModel of
- subscribers -> is_subscribed(Subscriptions);
- _ -> undefined
- end,
- if not ((PublishModel == open) or
- (PublishModel == publishers) and
- ((Affiliation == owner)
- or (Affiliation == publisher)
- or (Affiliation == publish_only))
- or (Subscribed == true)) ->
- {error, ?ERR_FORBIDDEN};
- true ->
- if MaxItems > 0 ->
- PubId = {now(), SubKey},
- set_item(#pubsub_item{itemid = {ItemId, Nidx},
- creation = {now(), GenKey},
- modification = PubId,
- payload = Payload}),
- Items = [ItemId | itemids(Nidx, GenKey) -- [ItemId]],
- {result, {_, OI}} = remove_extra_items(Nidx, MaxItems, Items),
- {result, {default, broadcast, OI}};
- true ->
- {result, {default, broadcast, []}}
- end
- end.
-
-%% @doc <p>This function is used to remove extra items, most notably when the
-%% maximum number of items has been reached.</p>
-%% <p>This function is used internally by the core PubSub module, as no
-%% permission check is performed.</p>
-%% <p>In the default plugin module, the oldest items are removed, but other
-%% rules can be used.</p>
-%% <p>If another PubSub plugin wants to delegate the item removal (and if the
-%% plugin is using the default pubsub storage), it can implements this function like this:
-%% ```remove_extra_items(Nidx, MaxItems, ItemIds) ->
-%% node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p>
-remove_extra_items(_Nidx, unlimited, ItemIds) ->
- {result, {ItemIds, []}};
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- NewItems = lists:sublist(ItemIds, MaxItems),
- OldItems = lists:nthtail(length(NewItems), ItemIds),
- del_items(Nidx, OldItems),
- {result, {NewItems, OldItems}}.
+ node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
-%% @doc <p>Triggers item deletion.</p>
-%% <p>Default plugin: The user performing the deletion must be the node owner
-%% or a publisher, or PublishModel being open.</p>
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- SubKey = jlib:jid_tolower(Publisher),
- GenKey = jlib:jid_remove_resource(SubKey),
- {result, Affiliation} = get_affiliation(Nidx, GenKey),
- Allowed = Affiliation == publisher orelse
- Affiliation == owner orelse
- PublishModel == open orelse
- case get_item(Nidx, ItemId) of
- {result, #pubsub_item{creation = {_, GenKey}}} -> true;
- _ -> false
- end,
- if not Allowed ->
- {error, ?ERR_FORBIDDEN};
- true ->
- case del_item(Nidx, ItemId) of
- {updated, 1} -> {result, {default, broadcast}};
- _ -> {error, ?ERR_ITEM_NOT_FOUND}
- end
- end.
+ node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(Nidx, GenKey),
- case GenState of
- #pubsub_state{affiliation = owner} ->
- {result, States} = get_states(Nidx),
- lists:foreach(fun
- (#pubsub_state{items = []}) -> ok;
- (#pubsub_state{items = Items}) -> del_items(Nidx, Items)
- end,
- States),
- {result, {default, broadcast}};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end.
+ node_flat_odbc:purge_node(Nidx, Owner).
-%% @doc <p>Return the current affiliations for the given user</p>
-%% <p>The default module reads affiliations in the main Mnesia
-%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
-%% table, it should return an empty list, as the affiliation will be read by
-%% the default PubSub module. Otherwise, it should return its own affiliation,
-%% that will be added to the affiliation stored in the main
-%% <tt>pubsub_state</tt> table.</p>
get_entity_affiliations(Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- H = encode_host(Host),
- J = encode_jid(GenKey),
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation "
- "from pubsub_state i, pubsub_node n where "
- "i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>])
- of
- {selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} ->
- [{nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
- || [N, T, I, A] <- RItems];
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state "
- "where nodeid='">>, Nidx, <<"';">>])
- of
- {selected, [<<"jid">>, <<"affiliation">>], RItems} ->
- [{decode_jid(J), decode_affiliation(A)} || [J, A] <- RItems];
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- J = encode_jid(GenKey),
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state "
- "where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {selected, [<<"affiliation">>], [[A]]} ->
- decode_affiliation(A);
- _ ->
- none
- end,
- {result, Reply}.
+ node_flat_odbc:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- {_, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey),
- case {Affiliation, Subscriptions} of
- {none, []} -> del_state(Nidx, GenKey);
- _ -> update_affiliation(Nidx, GenKey, Affiliation)
- end.
+ node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
-%% @doc <p>Return the current subscriptions for the given user</p>
-%% <p>The default module reads subscriptions in the main Mnesia
-%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
-%% table, it should return an empty list, as the affiliation will be read by
-%% the default PubSub module. Otherwise, it should return its own affiliation,
-%% that will be added to the affiliation stored in the main
-%% <tt>pubsub_state</tt> table.</p>
get_entity_subscriptions(Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- H = encode_host(Host),
- SJ = encode_jid(SubKey),
- GJ = encode_jid(GenKey),
- Query = case SubKey of
- GenKey ->
- [<<"select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
- _ ->
- [<<"select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
- end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected,
- [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
- lists:foldl(fun ([N, T, I, J, S], Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Node, none, Jid} | Acc];
- Subs ->
- lists:foldl(fun ({Sub, SubId}, Acc2) ->
- [{Node, Sub, SubId, Jid} | Acc2]
- end,
- Acc, Subs)
- end
- end,
- [], RItems);
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_entity_subscriptions(Host, Owner).
-%% do the same as get_entity_subscriptions but filter result only to
-%% nodes having send_last_published_item=on_sub_and_presence
-%% as this call avoid seeking node, it must return node and type as well
--spec(get_entity_subscriptions_for_send_last/2 ::
- (
- Host :: mod_pubsub:hostPubsub(),
- Owner :: jid())
- -> {result,
- [{mod_pubsub:pubsubNode(),
- mod_pubsub:subscription(),
- mod_pubsub:subId(),
- ljid()}]
- }
- ).
get_entity_subscriptions_for_send_last(Host, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- GenKey = jlib:jid_remove_resource(SubKey),
- H = encode_host(Host),
- SJ = encode_jid(SubKey),
- GJ = encode_jid(GenKey),
- Query = case SubKey of
- GenKey ->
- [<<"select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
- "and val='on_sub_and_presence' and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
- _ ->
- [<<"select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
- "and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
- end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected,
- [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
- lists:foldl(fun ([N, T, I, J, S], Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Node, none, Jid} | Acc];
- Subs ->
- lists:foldl(fun ({Sub, SubId}, Acc2) ->
- [{Node, Sub, SubId, Jid}| Acc2]
- end,
- Acc, Subs)
- end
- end,
- [], RItems);
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
get_node_subscriptions(Nidx) ->
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
- "where nodeid='">>, Nidx, <<"';">>])
- of
- {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
- lists:foldl(fun ([J, S], Acc) ->
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Jid, none} | Acc];
- Subs ->
- lists:foldl(fun ({Sub, SubId}, Acc2) ->
- [{Jid, Sub, SubId} | Acc2]
- end,
- Acc, Subs)
- end
- end,
- [], RItems);
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- SubKey = jlib:jid_tolower(Owner),
- J = encode_jid(SubKey),
- Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state where "
- "nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {selected, [<<"subscriptions">>], [[S]]} ->
- decode_subscriptions(S);
- _ ->
- []
- end,
- {result, Reply}.
+ node_flat_odbc:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- SubKey = jlib:jid_tolower(Owner),
- SubState = get_state_without_itemids(Nidx, SubKey),
- case {SubId, SubState#pubsub_state.subscriptions} of
- {_, []} ->
- case Subscription of
- none ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)};
- _ ->
- new_subscription(Nidx, Owner, Subscription, SubState)
- end;
- {<<>>, [{_, SID}]} ->
- case Subscription of
- none -> unsub_with_subid(Nidx, SID, SubState);
- _ -> replace_subscription({Subscription, SID}, SubState)
- end;
- {<<>>, [_ | _]} ->
- {error,
- ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)};
- _ ->
- case Subscription of
- none -> unsub_with_subid(Nidx, SubId, SubState);
- _ -> replace_subscription({Subscription, SubId}, SubState)
- end
- end.
-
-replace_subscription(NewSub, SubState) ->
- NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
- set_state(SubState#pubsub_state{subscriptions = NewSubs}).
+ node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
-replace_subscription(_, [], Acc) -> Acc;
-replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
- replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
-
-new_subscription(Nidx, Owner, Subscription, SubState) ->
- {result, SubId} = pubsub_subscription_odbc:subscribe_node(Owner, Nidx, []),
- Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions],
- set_state(SubState#pubsub_state{subscriptions = Subscriptions}),
- {Subscription, SubId}.
-
-unsub_with_subid(Nidx, SubId, SubState) ->
- pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
- NewSubs = [{S, Sid}
- || {S, Sid} <- SubState#pubsub_state.subscriptions,
- SubId =/= Sid],
- case {NewSubs, SubState#pubsub_state.affiliation} of
- {[], none} -> del_state(Nidx, element(1, SubState#pubsub_state.stateid));
- _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs})
- end.
-
-%% @doc <p>Returns a list of Owner's nodes on Host with pending
-%% subscriptions.</p>
get_pending_nodes(Host, Owner) ->
- GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
- affiliation = owner, _ = '_'}),
- Nidxxs = [Nidx || #pubsub_state{stateid = {_, Nidx}} <- States],
- NodeTree = mod_pubsub:tree(Host),
- Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, Nidx}} = S, Acc) ->
- case lists:member(Nidx, Nidxxs) of
- true ->
- case get_nodes_helper(NodeTree, S) of
- {value, Node} -> [Node | Acc];
- false -> Acc
- end;
- false ->
- Acc
- end
- end,
- [], pubsub_state),
- {result, Reply}.
+ node_flat_odbc:get_pending_nodes(Host, Owner).
-get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
- HasPending = fun
- ({pending, _}) -> true;
- (pending) -> true;
- (_) -> false
- end,
- case lists:any(HasPending, Subs) of
- true ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {_, Node}} -> {value, Node};
- _ -> false
- end;
- false ->
- false
- end.
-
-%% @doc Returns the list of stored states for a given node.
-%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
-%% <p>We can consider that the pubsub_state table have been created by the main
-%% mod_pubsub module.</p>
-%% <p>PubSub plugins can store the states where they wants (for example in a
-%% relational database).</p>
-%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
-%% they can implement this function like this:
-%% ```get_states(Nidx) ->
-%% node_default:get_states(Nidx).'''</p>
get_states(Nidx) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
- "from pubsub_state where nodeid='">>, Nidx, <<"';">>])
- of
- {selected,
- [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} ->
- {result,
- lists:map(fun ([SJID, Aff, Subs]) ->
- #pubsub_state{stateid = {decode_jid(SJID), Nidx},
- items = itemids(Nidx, SJID),
- affiliation = decode_affiliation(Aff),
- subscriptions = decode_subscriptions(Subs)}
- end,
- RItems)};
- _ ->
- {result, []}
- end.
+ node_flat_odbc:get_states(Nidx).
-%% @doc <p>Returns a state (one state list), given its reference.</p>
get_state(Nidx, JID) ->
- State = get_state_without_itemids(Nidx, JID),
- {SJID, _} = State#pubsub_state.stateid,
- State#pubsub_state{items = itemids(Nidx, SJID)}.
-
--spec(get_state_without_itemids/2 ::
- (Nidx :: mod_pubsub:nodeIdx(),
- Key :: ljid()) ->
- mod_pubsub:pubsubState()
- ).
-get_state_without_itemids(Nidx, JID) ->
- J = encode_jid(JID),
- case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
- "from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>])
- of
- {selected,
- [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], [[SJID, Aff, Subs]]} ->
- #pubsub_state{stateid = {decode_jid(SJID), Nidx},
- affiliation = decode_affiliation(Aff),
- subscriptions = decode_subscriptions(Subs)};
- _ ->
- #pubsub_state{stateid = {JID, Nidx}}
- end.
+ node_flat_odbc:get_state(Nidx, JID).
-%% @doc <p>Write a state into database.</p>
set_state(State) ->
- {_, Nidx} = State#pubsub_state.stateid,
- set_state(Nidx, State).
+ node_flat_odbc:set_state(State).
-set_state(Nidx, State) ->
- {JID, _} = State#pubsub_state.stateid,
- J = encode_jid(JID),
- S = encode_subscriptions(State#pubsub_state.subscriptions),
- A = encode_affiliation(State#pubsub_state.affiliation),
- case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
- <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('">>,
- Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>])
- end,
- ok.
-
-%% @doc <p>Delete a state from database.</p>
-del_state(Nidx, JID) ->
- J = encode_jid(JID),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>,
- J, <<"' and nodeid='">>, Nidx, <<"';">>]),
- ok.
-
-%% @doc Returns the list of stored items for a given node.
-%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
-%% <p>We can consider that the pubsub_item table have been created by the main
-%% mod_pubsub module.</p>
-%% <p>PubSub plugins can store the items where they wants (for example in a
-%% relational database), or they can even decide not to persist any items.</p>
-%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
-%% they can implement this function like this:
-%% ```get_items(Nidx, From) ->
-%% node_default:get_items(Nidx, From).'''</p>
-get_items(Nidx, _From) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' order by modification desc;">>])
- of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
- {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
- _ ->
- {result, []}
- end.
-
-get_items(Nidx, From, none) ->
- MaxItems = case catch
- ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option "
- "where nodeid='">>, Nidx, <<"' and name='max_items';">>])
- of
- {selected, [<<"val">>], [[Value]]} ->
- Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))),
- element(2, erl_parse:parse_term(Tokens));
- _ ->
- ?MAXITEMS
- end,
- get_items(Nidx, From, #rsm_in{max = MaxItems});
-get_items(Nidx, _From,
- #rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) ->
- Max = ejabberd_odbc:escape(jlib:i2l(M)),
- {Way, Order} = case Direction of
- % aft -> {<<"<">>, <<"desc">>};
- % before when I == <<>> -> {<<"is not">>, <<"asc">>};
- % before -> {<<">">>, <<"asc">>};
- % _ when IncIndex =/= undefined ->
- % {<<"<">>, <<"desc">>}; % using index
- _ ->
- {<<"is not">>, <<"desc">>}% Can be better
- end,
- [AttrName, Id] = case I of
- undefined when IncIndex =/= undefined ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item pi "
- "where exists ( select count(*) as count1 "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' and modification > pi.modification having count1 = ">>,
- ejabberd_odbc:escape(jlib:i2l(IncIndex)), <<" );">>])
- of
- {selected, [_], [[O]]} ->
- [<<"modification">>, <<"'", O/binary, "'">>];
- _ ->
- [<<"modification">>, <<"null">>]
- end;
- undefined ->
- [<<"modification">>, <<"null">>];
- <<>> ->
- [<<"modification">>, <<"null">>];
- I ->
- [A, B] = str:tokens(ejabberd_odbc:escape(jlib:i2l(I)), <<"@">>),
- [A, <<"'", B/binary, "'">>]
- end,
- Count = case catch
- ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
- of
- {selected, [_], [[C]]} -> C;
- _ -> <<"0">>
- end,
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
- AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
- of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
- case RItems of
- [[_, _, _, F, _]|_] ->
- Index = case catch
- ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item "
- "where nodeid='">>, Nidx, <<"' and ">>,
- AttrName, <<" > '">>, F, <<"';">>])
- of
- %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
- {selected, [_], [[In]]} -> In;
- _ -> <<"0">>
- end,
- [_, _, _, L, _] = lists:last(RItems),
- RsmOut = #rsm_out{count = Count, index = Index,
- first = <<"modification@", F/binary>>,
- last = <<"modification@", (jlib:i2l(L))/binary>>},
- {result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], RsmOut}};
- [] ->
- {result, {[], #rsm_out{count = Count}}}
- end;
- _ ->
- {result, {[], none}}
- end.
+get_items(Nidx, From, RSM) ->
+ node_flat_odbc:get_items(Nidx, From, RSM).
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat_odbc:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
-get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
- SubKey = jlib:jid_tolower(JID),
- GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- (AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_items(Nidx, JID, RSM)
- end.
-
-get_last_items(Nidx, _From, Count) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' order by modification desc limit ">>, jlib:i2l(Count), <<";">>])
- of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
- {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
- _ ->
- {result, []}
- end.
-
-%% @doc <p>Returns an item (one item list), given its reference.</p>
get_item(Nidx, ItemId) ->
- I = ejabberd_odbc:escape(ItemId),
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
- "modification, payload from pubsub_item "
- "where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
- of
- {selected,
- [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} ->
- {result, raw_to_item(Nidx, RItem)};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
- end.
+ node_flat_odbc:get_item(Nidx, ItemId).
-get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
- SubKey = jlib:jid_tolower(JID),
- GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- (Affiliation == outcast) or (Affiliation == publish_only) ->
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and not PresenceSubscription ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)};
- (AccessModel == roster) and not RosterGroup ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)};
- (AccessModel == whitelist) and not Whitelisted ->
- {error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
- (AccessModel == authorize) and not Whitelisted ->
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_item(Nidx, ItemId)
- end.
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat_odbc:get_item(Nidx, ItemId, JID,
+ AccessModel, PresenceSubscription, RosterGroup, SubId).
-%% @doc <p>Write an item into database.</p>
set_item(Item) ->
- {ItemId, Nidx} = Item#pubsub_item.itemid,
- I = ejabberd_odbc:escape(ItemId),
- {C, _} = Item#pubsub_item.creation,
- {M, JID} = Item#pubsub_item.modification,
- P = encode_jid(JID),
- Payload = Item#pubsub_item.payload,
- XML = ejabberd_odbc:escape(str:join([xml:element_to_binary(X) || X<-Payload], <<>>)),
- S = fun ({T1, T2, T3}) ->
- str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
- end,
- case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>, P,
- <<"', modification='">>, S(M),
- <<"', payload='">>, XML,
- <<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
- "publisher, creation, modification, payload) "
- "values('">>, Nidx, <<"', '">>, I, <<"', '">>, P,
- <<"', '">>, S(C), <<"', '">>, S(M),
- <<"', '">>, XML, <<"');">>])
- end,
- ok.
-
-%% @doc <p>Delete an item from database.</p>
-del_item(Nidx, ItemId) ->
- I = ejabberd_odbc:escape(ItemId),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>,
- I, <<"' and nodeid='">>, Nidx, <<"';">>]).
+ node_flat_odbc:set_item(Item).
-del_items(_, []) ->
- ok;
-del_items(Nidx, [ItemId]) ->
- del_item(Nidx, ItemId);
-del_items(Nidx, ItemIds) ->
- I = str:join([[<<"'">>, ejabberd_odbc:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
- catch
- ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
- I, <<") and nodeid='">>, Nidx, <<"';">>]).
+get_item_name(Host, Node, Id) ->
+ node_flat_odbc:get_item_name(Host, Node, Id).
-get_item_name(_Host, _Node, Id) ->
- Id.
+get_last_items(Nidx, From, Count) ->
+ node_flat_odbc:get_last_items(Nidx, From, Count).
node_to_path(Node) ->
node_hometree:node_to_path(Node).
@@ -1004,196 +159,3 @@ node_to_path(Node) ->
path_to_node(Path) ->
node_hometree:path_to_node(Path).
-can_fetch_item(owner, _) -> true;
-can_fetch_item(member, _) -> true;
-can_fetch_item(publisher, _) -> true;
-can_fetch_item(publish_only, _) -> false;
-can_fetch_item(outcast, _) -> false;
-can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
-%can_fetch_item(_Affiliation, _Subscription) -> false.
-
-is_subscribed(Subscriptions) ->
- lists:any(fun
- ({subscribed, _SubId}) -> true;
- (_) -> false
- end,
- Subscriptions).
-
-first_in_list(_Pred, []) ->
- false;
-first_in_list(Pred, [H | T]) ->
- case Pred(H) of
- true -> {value, H};
- _ -> first_in_list(Pred, T)
- end.
-
-itemids(Nidx, {U, S, R}) ->
- itemids(Nidx, encode_jid({U, S, R}));
-itemids(Nidx, SJID) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where "
- "nodeid='">>, Nidx, <<"' and publisher like '">>, SJID,
- <<"%' order by modification desc;">>])
- of
- {selected, [<<"itemid">>], RItems} ->
- [ItemId || [ItemId] <- RItems];
- _ ->
- []
- end.
-
-select_affiliation_subscriptions(Nidx, JID) ->
- J = encode_jid(JID),
- case catch
- ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from "
- "pubsub_state where nodeid='">>,
- Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {selected, [<<"affiliation">>, <<"subscriptions">>], [[A, S]]} ->
- {decode_affiliation(A), decode_subscriptions(S)};
- _ ->
- {none, []}
- end.
-
-select_affiliation_subscriptions(Nidx, JID, JID) ->
- select_affiliation_subscriptions(Nidx, JID);
-select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
- {result, Affiliation} = get_affiliation(Nidx, GenKey),
- {result, Subscriptions} = get_subscriptions(Nidx, SubKey),
- {Affiliation, Subscriptions}.
-
-update_affiliation(Nidx, JID, Affiliation) ->
- J = encode_jid(JID),
- A = encode_affiliation(Affiliation),
- case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>,
- A, <<"' where nodeid='">>, Nidx,
- <<"' and jid='">>, J, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>])
- end.
-
-update_subscription(Nidx, JID, Subscription) ->
- J = encode_jid(JID),
- S = encode_subscriptions(Subscription),
- case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
- <<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
- of
- {updated, 1} ->
- ok;
- _ ->
- catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>])
- end.
-
--spec(decode_jid/1 ::
- ( SJID :: binary())
- -> ljid()
- ).
-decode_jid(SJID) ->
- jlib:jid_tolower(jlib:string_to_jid(SJID)).
-
--spec(decode_affiliation/1 ::
- ( Arg :: binary())
- -> atom()
- ).
-decode_affiliation(<<"o">>) -> owner;
-decode_affiliation(<<"p">>) -> publisher;
-decode_affiliation(<<"u">>) -> publish_only;
-decode_affiliation(<<"m">>) -> member;
-decode_affiliation(<<"c">>) -> outcast;
-decode_affiliation(_) -> none.
-
--spec(decode_subscription/1 ::
- ( Arg :: binary())
- -> atom()
- ).
-decode_subscription(<<"s">>) -> subscribed;
-decode_subscription(<<"p">>) -> pending;
-decode_subscription(<<"u">>) -> unconfigured;
-decode_subscription(_) -> none.
-
--spec(decode_subscriptions/1 ::
- ( Subscriptions :: binary())
- -> [] | [{atom(), binary()},...]
- ).
-decode_subscriptions(Subscriptions) ->
- lists:foldl(fun (Subscription, Acc) ->
- case str:tokens(Subscription, <<":">>) of
- [S, SubId] -> [{decode_subscription(S), SubId} | Acc];
- _ -> Acc
- end
- end,
- [], str:tokens(Subscriptions, <<",">>)).
-
--spec(encode_jid/1 ::
- ( JID :: ljid())
- -> binary()
- ).
-encode_jid(JID) ->
- ejabberd_odbc:escape(jlib:jid_to_string(JID)).
-
--spec(encode_host/1 ::
- ( Host :: host())
- -> binary()
- ).
-encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
-encode_host(Host) -> ejabberd_odbc:escape(Host).
-
--spec(encode_affiliation/1 ::
- ( Arg :: atom())
- -> binary()
- ).
-encode_affiliation(owner) -> <<"o">>;
-encode_affiliation(publisher) -> <<"p">>;
-encode_affiliation(publish_only) -> <<"u">>;
-encode_affiliation(member) -> <<"m">>;
-encode_affiliation(outcast) -> <<"c">>;
-encode_affiliation(_) -> <<"n">>.
-
--spec(encode_subscription/1 ::
- ( Arg :: atom())
- -> binary()
- ).
-encode_subscription(subscribed) -> <<"s">>;
-encode_subscription(pending) -> <<"p">>;
-encode_subscription(unconfigured) -> <<"u">>;
-encode_subscription(_) -> <<"n">>.
-
--spec(encode_subscriptions/1 ::
- ( Subscriptions :: [] | [{atom(), binary()},...])
- -> binary()
- ).
-encode_subscriptions(Subscriptions) ->
- str:join([<<(encode_subscription(S))/binary, ":", SubId/binary>>
- || {S, SubId} <- Subscriptions], <<",">>).
-
-%%% record getter/setter
-
-state_to_raw(Nidx, State) ->
- {JID, _} = State#pubsub_state.stateid,
- J = encode_jid(JID),
- A = encode_affiliation(State#pubsub_state.affiliation),
- S = encode_subscriptions(State#pubsub_state.subscriptions),
- [<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>].
-
-raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
- JID = decode_jid(SJID),
- ToTime = fun (Str) ->
- [T1, T2, T3] = str:tokens(Str, <<":">>),
- {jlib:l2i(T1), jlib:l2i(T2), jlib:l2i(T3)}
- end,
- Payload = case xml_stream:parse_element(XML) of
- {error, _Reason} -> [];
- El -> [El]
- end,
- #pubsub_item{itemid = {ItemId, Nidx},
- creation = {ToTime(Creation), JID},
- modification = {ToTime(Modification), JID},
- payload = Payload}.
diff --git a/src/node_mb.erl b/src/node_mb.erl
index 9dc4bf81f..91e49505c 100644
--- a/src/node_mb.erl
+++ b/src/node_mb.erl
@@ -37,7 +37,6 @@
%%% ignore_pep_from_offline: false
%%% plugins:
%%% - "flat"
-%%% - "hometree"
%%% - "pep" # Requires mod_caps.
%%% pep_mapping:
%%% "urn:xmpp:microblog:0": "mb"
diff --git a/src/node_pep.erl b/src/node_pep.erl
index 34a841d3e..b219f6449 100644
--- a/src/node_pep.erl
+++ b/src/node_pep.erl
@@ -49,12 +49,12 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts),
+ node_flat:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost), ok.
+ node_flat:terminate(Host, ServerHost), ok.
options() ->
[{deliver_payloads, true},
@@ -112,34 +112,34 @@ create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
{result, Allowed}.
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Nodes) ->
- {result, {_, _, Result}} = node_hometree:delete_node(Nodes),
+ {result, {_, _, Result}} = node_flat:delete_node(Nodes),
{result, {[], Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- case node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
+ case node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
{_, D, _} = SubKey = jlib:jid_tolower(Owner),
@@ -158,13 +158,13 @@ get_entity_affiliations(Host, Owner) ->
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
{U, D, _} = SubKey = jlib:jid_tolower(Owner),
@@ -198,45 +198,45 @@ get_entity_subscriptions(Host, Owner) ->
{result, Reply}.
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
diff --git a/src/node_pep_odbc.erl b/src/node_pep_odbc.erl
index d80e686fb..39ea8f0b7 100644
--- a/src/node_pep_odbc.erl
+++ b/src/node_pep_odbc.erl
@@ -50,12 +50,12 @@
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
init(Host, ServerHost, Opts) ->
- node_hometree_odbc:init(Host, ServerHost, Opts),
+ node_flat_odbc:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
terminate(Host, ServerHost) ->
- node_hometree_odbc:terminate(Host, ServerHost), ok.
+ node_flat_odbc:terminate(Host, ServerHost), ok.
options() ->
[{odbc, true}, {rsm, true} | node_pep:options()].
@@ -67,56 +67,56 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree_odbc:create_node(Nidx, Owner),
+ node_flat_odbc:create_node(Nidx, Owner),
{result, {default, broadcast}}.
delete_node(Nodes) ->
- {result, {_, _, Result}} = node_hometree_odbc:delete_node(Nodes),
+ {result, {_, _, Result}} = node_flat_odbc:delete_node(Nodes),
{result, {[], Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- case node_hometree_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
+ case node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree_odbc:purge_node(Nidx, Owner).
+ node_flat_odbc:purge_node(Nidx, Owner).
get_entity_affiliations(_Host, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- node_hometree_odbc:get_entity_affiliations(OwnerKey, Owner).
+ node_flat_odbc:get_entity_affiliations(OwnerKey, Owner).
get_node_affiliations(Nidx) ->
- node_hometree_odbc:get_node_affiliations(Nidx).
+ node_flat_odbc:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree_odbc:get_affiliation(Nidx, Owner).
+ node_flat_odbc:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree_odbc:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(_Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- Host = node_hometree_odbc:encode_host(element(2, SubKey)),
- SJ = node_hometree_odbc:encode_jid(SubKey),
- GJ = node_hometree_odbc:encode_jid(GenKey),
+ Host = node_flat_odbc:encode_host(element(2, SubKey)),
+ SJ = node_flat_odbc:encode_jid(SubKey),
+ GJ = node_flat_odbc:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -134,11 +134,11 @@ get_entity_subscriptions(_Host, Owner) ->
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
- O = node_hometree_odbc:decode_jid(H),
+ O = node_flat_odbc:decode_jid(H),
Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
- node_hometree_odbc:decode_subscriptions(S),
- node_hometree_odbc:decode_jid(J)}
+ node_flat_odbc:decode_subscriptions(S),
+ node_flat_odbc:decode_jid(J)}
end,
RItems);
_ ->
@@ -149,9 +149,9 @@ get_entity_subscriptions(_Host, Owner) ->
get_entity_subscriptions_for_send_last(_Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- Host = node_hometree_odbc:encode_host(element(2, SubKey)),
- SJ = node_hometree_odbc:encode_jid(SubKey),
- GJ = node_hometree_odbc:encode_jid(GenKey),
+ Host = node_flat_odbc:encode_host(element(2, SubKey)),
+ SJ = node_flat_odbc:encode_jid(SubKey),
+ GJ = node_flat_odbc:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -173,11 +173,11 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
- O = node_hometree_odbc:decode_jid(H),
+ O = node_flat_odbc:decode_jid(H),
Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
- node_hometree_odbc:decode_subscriptions(S),
- node_hometree_odbc:decode_jid(J)}
+ node_flat_odbc:decode_subscriptions(S),
+ node_flat_odbc:decode_jid(J)}
end,
RItems);
_ ->
@@ -186,48 +186,48 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
{result, Reply}.
get_node_subscriptions(Nidx) ->
- node_hometree_odbc:get_node_subscriptions(Nidx).
+ node_flat_odbc:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree_odbc:get_subscriptions(Nidx, Owner).
+ node_flat_odbc:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree_odbc:get_pending_nodes(Host, Owner).
+ node_flat_odbc:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree_odbc:get_states(Nidx).
+ node_flat_odbc:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree_odbc:get_state(Nidx, JID).
+ node_flat_odbc:get_state(Nidx, JID).
set_state(State) ->
- node_hometree_odbc:set_state(State).
+ node_flat_odbc:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree_odbc:get_items(Nidx, From, RSM).
+ node_flat_odbc:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree_odbc:get_items(Nidx, JID, AccessModel,
+ node_flat_odbc:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_last_items(Nidx, JID, Count) ->
- node_hometree_odbc:get_last_items(Nidx, JID, Count).
+ node_flat_odbc:get_last_items(Nidx, JID, Count).
get_item(Nidx, ItemId) ->
- node_hometree_odbc:get_item(Nidx, ItemId).
+ node_flat_odbc:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree_odbc:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat_odbc:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree_odbc:set_item(Item).
+ node_flat_odbc:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree_odbc:get_item_name(Host, Node, Id).
+ node_flat_odbc:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat_odbc:node_to_path(Node).
diff --git a/src/node_private.erl b/src/node_private.erl
index eab06d562..d27b5e29d 100644
--- a/src/node_private.erl
+++ b/src/node_private.erl
@@ -45,10 +45,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts).
+ node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+ node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -85,89 +85,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
- node_hometree:delete_node(Removed).
+ node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
diff --git a/src/node_public.erl b/src/node_public.erl
index efefd67fe..b3f209251 100644
--- a/src/node_public.erl
+++ b/src/node_public.erl
@@ -45,10 +45,10 @@
path_to_node/1]).
init(Host, ServerHost, Opts) ->
- node_hometree:init(Host, ServerHost, Opts).
+ node_flat:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
- node_hometree:terminate(Host, ServerHost).
+ node_flat:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
@@ -85,89 +85,89 @@ features() ->
<<"subscription-notifications">>].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
- node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_hometree:create_node(Nidx, Owner).
+ node_flat:create_node(Nidx, Owner).
delete_node(Removed) ->
- node_hometree:delete_node(Removed).
+ node_flat:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_hometree:purge_node(Nidx, Owner).
+ node_flat:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_hometree:get_entity_affiliations(Host, Owner).
+ node_flat:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_hometree:get_node_affiliations(Nidx).
+ node_flat:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_hometree:get_affiliation(Nidx, Owner).
+ node_flat:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_hometree:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_hometree:get_entity_subscriptions(Host, Owner).
+ node_flat:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_hometree:get_node_subscriptions(Nidx).
+ node_flat:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_hometree:get_subscriptions(Nidx, Owner).
+ node_flat:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_hometree:get_pending_nodes(Host, Owner).
+ node_flat:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_hometree:get_states(Nidx).
+ node_flat:get_states(Nidx).
get_state(Nidx, JID) ->
- node_hometree:get_state(Nidx, JID).
+ node_flat:get_state(Nidx, JID).
set_state(State) ->
- node_hometree:set_state(State).
+ node_flat:set_state(State).
get_items(Nidx, From, RSM) ->
- node_hometree:get_items(Nidx, From, RSM).
+ node_flat:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_hometree:get_items(Nidx, JID, AccessModel,
+ node_flat:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_hometree:get_item(Nidx, ItemId).
+ node_flat:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_hometree:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_hometree:set_item(Item).
+ node_flat:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_hometree:get_item_name(Host, Node, Id).
+ node_flat:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_flat:node_to_path(Node).
diff --git a/src/nodetree_tree_odbc.erl b/src/nodetree_tree_odbc.erl
index 38fb51c2a..038dc472c 100644
--- a/src/nodetree_tree_odbc.erl
+++ b/src/nodetree_tree_odbc.erl
@@ -66,7 +66,7 @@ set_node(Record) when is_record(Record, pubsub_node) ->
[First | _] -> First
end,
Type = Record#pubsub_node.type,
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
P = ejabberd_odbc:escape(Parent),
Nidx = case nodeidx(Host, Node) of
@@ -116,7 +116,7 @@ get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
@@ -151,7 +151,7 @@ get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>, H, <<"';">>])
@@ -178,7 +178,7 @@ get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
@@ -196,7 +196,7 @@ get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
get_subnodes_tree(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
@@ -256,7 +256,7 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
end.
delete_node(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
Removed = get_subnodes_tree(Host, Node),
catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_node where host='">>,
@@ -295,7 +295,7 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
id = Nidx, type = Type, options = Options}.
nodeidx(Host, Node) ->
- H = node_hometree_odbc:encode_host(Host),
+ H = node_flat_odbc:encode_host(Host),
N = ejabberd_odbc:escape(Node),
case catch
ejabberd_odbc:sql_query_t([<<"select nodeid from pubsub_node where "
@@ -311,5 +311,5 @@ nodeidx(Host, Node) ->
end.
nodeowners(Nidx) ->
- {result, Res} = node_hometree_odbc:get_node_affiliations(Nidx),
+ {result, Res} = node_flat_odbc:get_node_affiliations(Nidx),
[LJID || {LJID, Aff} <- Res, Aff =:= owner].
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
index dc528d79a..075a53fe2 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -1663,7 +1663,11 @@ mam_query_all(Config, NS) ->
QID = randoms:get_string(),
MyJID = my_jid(Config),
Peer = ?config(slave, Config),
- I = send(Config, #iq{type = get, sub_els = [#mam_query{xmlns = NS, id = QID}]}),
+ Type = case NS of
+ ?NS_MAM_TMP -> get;
+ _ -> set
+ end,
+ I = send(Config, #iq{type = Type, sub_els = [#mam_query{xmlns = NS, id = QID}]}),
maybe_recv_iq_result(NS, I),
Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5);
true -> lists:seq(1, 5) ++ lists:seq(1, 5)
@@ -1686,21 +1690,21 @@ mam_query_all(Config, NS) ->
if NS == ?NS_MAM_TMP ->
?recv1(#iq{type = result, id = I, sub_els = []});
true ->
- ?recv1(#message{sub_els = [#mam_fin{id = QID}]})
+ ?recv1(#message{sub_els = [#mam_fin{complete = true, id = QID}]})
end.
mam_query_with(Config, JID, NS) ->
MyJID = my_jid(Config),
Peer = ?config(slave, Config),
- Query = if NS == ?NS_MAM_TMP ->
- #mam_query{xmlns = NS, with = JID};
+ {Query, Type} = if NS == ?NS_MAM_TMP ->
+ {#mam_query{xmlns = NS, with = JID}, get};
true ->
Fs = [#xdata_field{var = <<"jid">>,
values = [jlib:jid_to_string(JID)]}],
- #mam_query{xmlns = NS,
- xdata = #xdata{type = submit, fields = Fs}}
+ {#mam_query{xmlns = NS,
+ xdata = #xdata{type = submit, fields = Fs}}, set}
end,
- I = send(Config, #iq{type = get, sub_els = [Query]}),
+ I = send(Config, #iq{type = Type, sub_els = [Query]}),
Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5);
true -> lists:seq(1, 5) ++ lists:seq(1, 5)
end,
@@ -1722,7 +1726,7 @@ mam_query_with(Config, JID, NS) ->
if NS == ?NS_MAM_TMP ->
?recv1(#iq{type = result, id = I, sub_els = []});
true ->
- ?recv1(#message{sub_els = [#mam_fin{}]})
+ ?recv1(#message{sub_els = [#mam_fin{complete = true}]})
end.
maybe_recv_iq_result(?NS_MAM_0, I1) ->
@@ -1733,9 +1737,13 @@ maybe_recv_iq_result(_, _) ->
mam_query_rsm(Config, NS) ->
MyJID = my_jid(Config),
Peer = ?config(slave, Config),
+ Type = case NS of
+ ?NS_MAM_TMP -> get;
+ _ -> set
+ end,
%% Get the first 3 items out of 5
I1 = send(Config,
- #iq{type = get,
+ #iq{type = Type,
sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 3}}]}),
maybe_recv_iq_result(NS, I1),
lists:foreach(
@@ -1759,12 +1767,13 @@ mam_query_rsm(Config, NS) ->
rsm = #rsm_set{last = Last, count = 5}}]});
true ->
?recv1(#message{sub_els = [#mam_fin{
+ complete = false,
rsm = #rsm_set{last = Last, count = 10}}]})
end,
%% Get the next items starting from the `Last`.
%% Limit the response to 2 items.
I2 = send(Config,
- #iq{type = get,
+ #iq{type = Type,
sub_els = [#mam_query{xmlns = NS,
rsm = #rsm_set{max = 2,
'after' = Last}}]}),
@@ -1794,15 +1803,16 @@ mam_query_rsm(Config, NS) ->
true ->
?recv1(#message{
sub_els = [#mam_fin{
+ complete = false,
rsm = #rsm_set{
count = 10,
first = #rsm_first{data = First}}}]})
end,
- %% Paging back. Should receive 2 elements: 2, 3.
+ %% Paging back. Should receive 3 elements: 1, 2, 3.
I3 = send(Config,
- #iq{type = get,
+ #iq{type = Type,
sub_els = [#mam_query{xmlns = NS,
- rsm = #rsm_set{max = 2,
+ rsm = #rsm_set{max = 3,
before = First}}]}),
maybe_recv_iq_result(NS, I3),
lists:foreach(
@@ -1819,17 +1829,18 @@ mam_query_rsm(Config, NS) ->
[#message{
from = MyJID, to = Peer,
body = [Text]}]}]}]})
- end, lists:seq(2, 3)),
+ end, lists:seq(1, 3)),
if NS == ?NS_MAM_TMP ->
?recv1(#iq{type = result, id = I3,
sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]});
true ->
?recv1(#message{
- sub_els = [#mam_fin{rsm = #rsm_set{count = 10}}]})
+ sub_els = [#mam_fin{complete = true,
+ rsm = #rsm_set{count = 10}}]})
end,
%% Getting the item count. Should be 5 (or 10).
I4 = send(Config,
- #iq{type = get,
+ #iq{type = Type,
sub_els = [#mam_query{xmlns = NS,
rsm = #rsm_set{max = 0}}]}),
maybe_recv_iq_result(NS, I4),
@@ -1843,9 +1854,40 @@ mam_query_rsm(Config, NS) ->
true ->
?recv1(#message{
sub_els = [#mam_fin{
+ complete = false,
rsm = #rsm_set{count = 10,
first = undefined,
last = undefined}}]})
+ end,
+ %% Should receive 2 last messages
+ I5 = send(Config,
+ #iq{type = Type,
+ sub_els = [#mam_query{xmlns = NS,
+ rsm = #rsm_set{max = 2,
+ before = none}}]}),
+ maybe_recv_iq_result(NS, I5),
+ lists:foreach(
+ fun(N) ->
+ Text = #text{data = jlib:integer_to_binary(N)},
+ ?recv1(#message{to = MyJID,
+ sub_els =
+ [#mam_result{
+ xmlns = NS,
+ sub_els =
+ [#forwarded{
+ delay = #delay{},
+ sub_els =
+ [#message{
+ from = MyJID, to = Peer,
+ body = [Text]}]}]}]})
+ end, lists:seq(4, 5)),
+ if NS == ?NS_MAM_TMP ->
+ ?recv1(#iq{type = result, id = I5,
+ sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]});
+ true ->
+ ?recv1(#message{
+ sub_els = [#mam_fin{complete = false,
+ rsm = #rsm_set{count = 10}}]})
end.
client_state_master(Config) ->
diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
index b1c95ffdf..111d72f79 100644
--- a/test/ejabberd_SUITE_data/ejabberd.yml
+++ b/test/ejabberd_SUITE_data/ejabberd.yml
@@ -12,8 +12,7 @@ host_config:
mod_announce:
db_type: odbc
access: local
- mod_blocking:
- db_type: odbc
+ mod_blocking: []
mod_caps:
db_type: odbc
mod_last:
@@ -65,8 +64,7 @@ Welcome to this XMPP server."
mod_announce:
db_type: odbc
access: local
- mod_blocking:
- db_type: odbc
+ mod_blocking: []
mod_caps:
db_type: odbc
mod_last:
@@ -124,8 +122,7 @@ Welcome to this XMPP server."
mod_announce:
db_type: odbc
access: local
- mod_blocking:
- db_type: odbc
+ mod_blocking: []
mod_caps:
db_type: odbc
mod_last:
@@ -176,8 +173,7 @@ Welcome to this XMPP server."
mod_announce:
db_type: internal
access: local
- mod_blocking:
- db_type: internal
+ mod_blocking: []
mod_caps:
db_type: internal
mod_last:
@@ -232,8 +228,7 @@ Welcome to this XMPP server."
mod_announce:
db_type: riak
access: local
- mod_blocking:
- db_type: riak
+ mod_blocking: []
mod_caps:
db_type: riak
mod_last:
diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl
index 0dbc62808..987eb7c41 100644
--- a/tools/xmpp_codec.erl
+++ b/tools/xmpp_codec.erl
@@ -2082,7 +2082,7 @@ encode({mam_result, _, _, _, _} = Result) ->
encode_mam_result(Result, []);
encode({mam_prefs, _, _, _, _} = Prefs) ->
encode_mam_prefs(Prefs, []);
-encode({mam_fin, _, _} = Fin) ->
+encode({mam_fin, _, _, _, _} = Fin) ->
encode_mam_fin(Fin,
[{<<"xmlns">>, <<"urn:xmpp:mam:0">>}]);
encode({forwarded, _, _} = Forwarded) ->
@@ -2308,7 +2308,7 @@ get_ns({rsm_first, _, _}) ->
get_ns({rsm_set, _, _, _, _, _, _, _}) ->
<<"http://jabber.org/protocol/rsm">>;
get_ns({mam_archived, _, _}) -> <<"urn:xmpp:mam:tmp">>;
-get_ns({mam_fin, _, _}) -> <<"urn:xmpp:mam:0">>;
+get_ns({mam_fin, _, _, _, _}) -> <<"urn:xmpp:mam:0">>;
get_ns({forwarded, _, _}) -> <<"urn:xmpp:forward:0">>;
get_ns({carbons_disable}) -> <<"urn:xmpp:carbons:2">>;
get_ns({carbons_enable}) -> <<"urn:xmpp:carbons:2">>;
@@ -2505,7 +2505,7 @@ pp(mam_query, 7) ->
pp(mam_archived, 2) -> [by, id];
pp(mam_result, 4) -> [xmlns, queryid, id, sub_els];
pp(mam_prefs, 4) -> [xmlns, default, always, never];
-pp(mam_fin, 2) -> [id, rsm];
+pp(mam_fin, 4) -> [id, rsm, stable, complete];
pp(forwarded, 2) -> [delay, sub_els];
pp(carbons_disable, 0) -> [];
pp(carbons_enable, 0) -> [];
@@ -3709,9 +3709,10 @@ decode_mam_fin(__TopXMLNS, __IgnoreEls,
{xmlel, <<"fin">>, _attrs, _els}) ->
Rsm = decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els,
undefined),
- Id = decode_mam_fin_attrs(__TopXMLNS, _attrs,
- undefined),
- {mam_fin, Id, Rsm}.
+ {Id, Stable, Complete} =
+ decode_mam_fin_attrs(__TopXMLNS, _attrs, undefined,
+ undefined, undefined),
+ {mam_fin, Id, Rsm, Stable, Complete}.
decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [], Rsm) ->
Rsm;
@@ -3729,16 +3730,37 @@ decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [_ | _els],
decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, Rsm).
decode_mam_fin_attrs(__TopXMLNS,
- [{<<"queryid">>, _val} | _attrs], _Id) ->
- decode_mam_fin_attrs(__TopXMLNS, _attrs, _val);
-decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id) ->
- decode_mam_fin_attrs(__TopXMLNS, _attrs, Id);
-decode_mam_fin_attrs(__TopXMLNS, [], Id) ->
- decode_mam_fin_attr_queryid(__TopXMLNS, Id).
-
-encode_mam_fin({mam_fin, Id, Rsm}, _xmlns_attrs) ->
+ [{<<"queryid">>, _val} | _attrs], _Id, Stable,
+ Complete) ->
+ decode_mam_fin_attrs(__TopXMLNS, _attrs, _val, Stable,
+ Complete);
+decode_mam_fin_attrs(__TopXMLNS,
+ [{<<"stable">>, _val} | _attrs], Id, _Stable,
+ Complete) ->
+ decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, _val,
+ Complete);
+decode_mam_fin_attrs(__TopXMLNS,
+ [{<<"complete">>, _val} | _attrs], Id, Stable,
+ _Complete) ->
+ decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable,
+ _val);
+decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id,
+ Stable, Complete) ->
+ decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable,
+ Complete);
+decode_mam_fin_attrs(__TopXMLNS, [], Id, Stable,
+ Complete) ->
+ {decode_mam_fin_attr_queryid(__TopXMLNS, Id),
+ decode_mam_fin_attr_stable(__TopXMLNS, Stable),
+ decode_mam_fin_attr_complete(__TopXMLNS, Complete)}.
+
+encode_mam_fin({mam_fin, Id, Rsm, Stable, Complete},
+ _xmlns_attrs) ->
_els = lists:reverse('encode_mam_fin_$rsm'(Rsm, [])),
- _attrs = encode_mam_fin_attr_queryid(Id, _xmlns_attrs),
+ _attrs = encode_mam_fin_attr_complete(Complete,
+ encode_mam_fin_attr_stable(Stable,
+ encode_mam_fin_attr_queryid(Id,
+ _xmlns_attrs))),
{xmlel, <<"fin">>, _attrs, _els}.
'encode_mam_fin_$rsm'(undefined, _acc) -> _acc;
@@ -3755,6 +3777,35 @@ encode_mam_fin_attr_queryid(undefined, _acc) -> _acc;
encode_mam_fin_attr_queryid(_val, _acc) ->
[{<<"queryid">>, _val} | _acc].
+decode_mam_fin_attr_stable(__TopXMLNS, undefined) ->
+ undefined;
+decode_mam_fin_attr_stable(__TopXMLNS, _val) ->
+ case catch dec_bool(_val) of
+ {'EXIT', _} ->
+ erlang:error({xmpp_codec,
+ {bad_attr_value, <<"stable">>, <<"fin">>, __TopXMLNS}});
+ _res -> _res
+ end.
+
+encode_mam_fin_attr_stable(undefined, _acc) -> _acc;
+encode_mam_fin_attr_stable(_val, _acc) ->
+ [{<<"stable">>, enc_bool(_val)} | _acc].
+
+decode_mam_fin_attr_complete(__TopXMLNS, undefined) ->
+ undefined;
+decode_mam_fin_attr_complete(__TopXMLNS, _val) ->
+ case catch dec_bool(_val) of
+ {'EXIT', _} ->
+ erlang:error({xmpp_codec,
+ {bad_attr_value, <<"complete">>, <<"fin">>,
+ __TopXMLNS}});
+ _res -> _res
+ end.
+
+encode_mam_fin_attr_complete(undefined, _acc) -> _acc;
+encode_mam_fin_attr_complete(_val, _acc) ->
+ [{<<"complete">>, enc_bool(_val)} | _acc].
+
decode_mam_prefs(__TopXMLNS, __IgnoreEls,
{xmlel, <<"prefs">>, _attrs, _els}) ->
{Never, Always} = decode_mam_prefs_els(__TopXMLNS,
@@ -4746,10 +4797,10 @@ encode_rsm_before(Cdata, _xmlns_attrs) ->
_attrs = _xmlns_attrs,
{xmlel, <<"before">>, _attrs, _els}.
-decode_rsm_before_cdata(__TopXMLNS, <<>>) -> undefined;
+decode_rsm_before_cdata(__TopXMLNS, <<>>) -> none;
decode_rsm_before_cdata(__TopXMLNS, _val) -> _val.
-encode_rsm_before_cdata(undefined, _acc) -> _acc;
+encode_rsm_before_cdata(none, _acc) -> _acc;
encode_rsm_before_cdata(_val, _acc) ->
[{xmlcdata, _val} | _acc].
diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl
index 567946fe2..7996f6a11 100644
--- a/tools/xmpp_codec.hrl
+++ b/tools/xmpp_codec.hrl
@@ -280,7 +280,7 @@
units = [] :: [binary()]}).
-record(rsm_set, {'after' :: binary(),
- before :: binary(),
+ before :: 'none' | binary(),
count :: non_neg_integer(),
first :: #rsm_first{},
index :: non_neg_integer(),
@@ -288,7 +288,9 @@
max :: non_neg_integer()}).
-record(mam_fin, {id :: binary(),
- rsm :: #rsm_set{}}).
+ rsm :: #rsm_set{},
+ stable :: any(),
+ complete :: any()}).
-record(vcard_tel, {home = false :: boolean(),
work = false :: boolean(),
diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec
index dfa516ef5..38508ce6c 100644
--- a/tools/xmpp_codec.spec
+++ b/tools/xmpp_codec.spec
@@ -2071,6 +2071,7 @@
-xml(rsm_before,
#elem{name = <<"before">>,
xmlns = <<"http://jabber.org/protocol/rsm">>,
+ cdata = #cdata{default = none},
result = '$cdata'}).
-xml(rsm_last,
@@ -2209,8 +2210,14 @@
-xml(mam_fin,
#elem{name = <<"fin">>,
xmlns = <<"urn:xmpp:mam:0">>,
- result = {mam_fin, '$id', '$rsm'},
- attrs = [#attr{name = <<"queryid">>, label = '$id'}],
+ result = {mam_fin, '$id', '$rsm', '$stable', '$complete'},
+ attrs = [#attr{name = <<"queryid">>, label = '$id'},
+ #attr{name = <<"stable">>, label = '$stable',
+ dec = {dec_bool, []},
+ enc = {enc_bool, []}},
+ #attr{name = <<"complete">>, label = '$complete',
+ dec = {dec_bool, []},
+ enc = {enc_bool, []}}],
refs = [#ref{name = rsm_set, min = 0, max = 1, label = '$rsm'}]}).
-xml(forwarded,