diff options
-rw-r--r-- | README | 4 | ||||
-rw-r--r-- | src/ejabberd_admin.erl | 4 | ||||
-rw-r--r-- | src/ejabberd_c2s.erl | 5 | ||||
-rw-r--r-- | src/ejabberd_web_admin.erl | 75 | ||||
-rw-r--r-- | src/ext_mod.erl | 77 | ||||
-rw-r--r-- | src/mod_http_fileserver.erl | 13 | ||||
-rw-r--r-- | src/mod_mam.erl | 317 | ||||
-rw-r--r-- | src/mod_muc.erl | 3 | ||||
-rw-r--r-- | src/mod_muc_admin.erl | 14 | ||||
-rw-r--r-- | src/mod_muc_room.erl | 40 | ||||
-rw-r--r-- | src/mod_pubsub.erl | 9 | ||||
-rw-r--r-- | src/node_buddy.erl | 58 | ||||
-rw-r--r-- | src/node_club.erl | 58 | ||||
-rw-r--r-- | src/node_dispatch.erl | 4 | ||||
-rw-r--r-- | src/node_flat.erl | 769 | ||||
-rw-r--r-- | src/node_flat_odbc.erl | 989 | ||||
-rw-r--r-- | src/node_hometree.erl | 808 | ||||
-rw-r--r-- | src/node_hometree_odbc.erl | 1122 | ||||
-rw-r--r-- | src/node_mb.erl | 1 | ||||
-rw-r--r-- | src/node_pep.erl | 52 | ||||
-rw-r--r-- | src/node_pep_odbc.erl | 80 | ||||
-rw-r--r-- | src/node_private.erl | 58 | ||||
-rw-r--r-- | src/node_public.erl | 58 | ||||
-rw-r--r-- | src/nodetree_tree_odbc.erl | 16 | ||||
-rw-r--r-- | test/ejabberd_SUITE.erl | 74 | ||||
-rw-r--r-- | test/ejabberd_SUITE_data/ejabberd.yml | 15 | ||||
-rw-r--r-- | tools/xmpp_codec.erl | 85 | ||||
-rw-r--r-- | tools/xmpp_codec.hrl | 6 | ||||
-rw-r--r-- | tools/xmpp_codec.spec | 11 |
29 files changed, 2452 insertions, 2373 deletions
@@ -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, |