diff options
Diffstat (limited to 'src/node_flat.erl')
-rw-r--r-- | src/node_flat.erl | 374 |
1 files changed, 245 insertions, 129 deletions
diff --git a/src/node_flat.erl b/src/node_flat.erl index 2fb24ee69..f0deeb7e2 100644 --- a/src/node_flat.erl +++ b/src/node_flat.erl @@ -5,7 +5,7 @@ %%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net> %%% %%% -%%% ejabberd, Copyright (C) 2002-2016 ProcessOne +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -34,7 +34,7 @@ -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, @@ -46,18 +46,22 @@ 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, + get_last_items/3, get_only_item/2, get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1, can_fetch_item/2, is_subscribed/1]). + path_to_node/1, can_fetch_item/2, is_subscribed/1, transform/1]). init(_Host, _ServerHost, _Opts) -> - %pubsub_subscription:init(), - mnesia:create_table(pubsub_state, - [{disc_copies, [node()]}, + %pubsub_subscription:init(Host, ServerHost, Opts), + ejabberd_mnesia:create(?MODULE, pubsub_state, + [{disc_copies, [node()]}, {index, [nodeidx]}, {type, ordered_set}, {attributes, record_info(fields, pubsub_state)}]), - mnesia:create_table(pubsub_item, - [{disc_only_copies, [node()]}, + ejabberd_mnesia:create(?MODULE, pubsub_item, + [{disc_only_copies, [node()]}, {index, [nodeidx]}, {attributes, record_info(fields, pubsub_item)}]), + ejabberd_mnesia:create(?MODULE, pubsub_orphan, + [{disc_copies, [node()]}, + {attributes, record_info(fields, pubsub_orphan)}]), ItemsFields = record_info(fields, pubsub_item), case mnesia:table_info(pubsub_item, attributes) of ItemsFields -> ok; @@ -99,16 +103,18 @@ features() -> <<"modify-affiliations">>, <<"outcast-affiliation">>, <<"persistent-items">>, + <<"multi-items">>, <<"publish">>, <<"publish-only-affiliation">>, + <<"publish-options">>, <<"purge-nodes">>, <<"retract-items">>, <<"retrieve-affiliations">>, <<"retrieve-items">>, <<"retrieve-subscriptions">>, <<"subscribe">>, + %%<<"subscription-options">>, <<"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 @@ -127,7 +133,7 @@ create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) -> create_node(Nidx, Owner) -> OwnerKey = jid:tolower(jid:remove_resource(Owner)), set_state(#pubsub_state{stateid = {OwnerKey, Nidx}, - affiliation = owner}), + nodeidx = Nidx, affiliation = owner}), {result, {default, broadcast}}. delete_node(Nodes) -> @@ -136,10 +142,11 @@ delete_node(Nodes) -> 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) + lists:foreach(fun (State) -> + del_items(Nidx, State#pubsub_state.items), + del_state(State#pubsub_state{items = []}) end, States), + del_orphan_items(Nidx), {PubsubNode, lists:flatmap(Tr, States)} end, Nodes), {result, {default, broadcast, Reply}}. @@ -153,10 +160,10 @@ delete_node(Nodes) -> %% can decide to:<ul> %% <li>reject the subscription;</li> %% <li>allow it as is, letting the main module perform the database -%% persistance;</li> +%% persistence;</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>allow it, but perform the needed persistence operations.</li></ul> %% </li></ul></p> %% <p>The selected behaviour depends on the return parameter: %% <ul> @@ -170,9 +177,9 @@ delete_node(Nodes) -> %% 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> +%% no further persistence operation will be performed. This case is used, +%% when the plugin module is doing the persistence by itself or when it want +%% to completely disable persistence.</li></ul> %% </p> %% <p>In the default plugin module, the record is unchanged.</p> subscribe_node(Nidx, Sender, Subscriber, AccessModel, @@ -196,27 +203,27 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, Owner = Affiliation == owner, if not Authorized -> {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)}; + mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_invalid_jid())}; (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; PendingSubscription -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_pending_subscription())}; (AccessModel == presence) and (not PresenceSubscription) and (not Owner) -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_presence_subscription_required())}; (AccessModel == roster) and (not RosterGroup) and (not Owner) -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_not_in_roster_group())}; (AccessModel == whitelist) and (not Whitelisted) and (not Owner) -> {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + mod_pubsub:extended_error((xmpp:err_not_allowed()), mod_pubsub:err_closed_node())}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; %%ForbiddenAnonymous -> %% % Requesting entity is anonymous - %% {error, ?ERR_FORBIDDEN}; + %% {error, xmpp:err_forbidden()}; true -> %%SubId = pubsub_subscription:add_subscription(Subscriber, Nidx, Options), {NewSub, SubId} = case Subscriptions of @@ -265,17 +272,17 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> if %% Requesting entity is prohibited from unsubscribing entity not Authorized -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; %% Entity did not specify SubId %%SubId == "", ?? -> - %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %% {error, mod_pubsub:extended_error(xmpp:err_bad_request(), "subid-required")}; %% Invalid subscription identifier %%InvalidSubId -> - %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + %% {error, mod_pubsub:extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; %% Requesting entity is not a subscriber Subscriptions == [] -> {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}; + mod_pubsub:extended_error(xmpp:err_unexpected_request(), mod_pubsub:err_not_subscribed())}; %% Subid supplied, so use that. SubIdExists -> Sub = first_in_list(fun @@ -285,33 +292,33 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> SubState#pubsub_state.subscriptions), case Sub of {value, S} -> - delete_subscriptions(SubKey, Nidx, [S], SubState), + delete_subscriptions(SubState, [S]), {result, default}; false -> {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)} + mod_pubsub:extended_error(xmpp:err_unexpected_request(), mod_pubsub:err_not_subscribed())} end; %% Asking to remove all subscriptions to the given node SubId == all -> - delete_subscriptions(SubKey, Nidx, Subscriptions, SubState), + delete_subscriptions(SubState, Subscriptions), {result, default}; %% No subid supplied, but there's only one matching subscription length(Subscriptions) == 1 -> - delete_subscriptions(SubKey, Nidx, Subscriptions, SubState), + delete_subscriptions(SubState, Subscriptions), {result, default}; %% No subid and more than one possible subscription match. true -> {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)} + mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_subid_required())} end. -delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) -> +delete_subscriptions(SubState, Subscriptions) -> 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); + {none, []} -> del_state(SubState); _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs}) end. @@ -322,9 +329,9 @@ delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) -> %% 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 as is, letting the main module perform the database persistence;</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>allow it, but perform the needed persistence operations.</li></ul> %% </li></ul></p> %% <p>The selected behaviour depends on the return parameter: %% <ul> @@ -336,13 +343,13 @@ delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) -> %% 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 +%% in parameter <tt>Item</tt>. The persistence 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> +%% no further persistence operation will be performed. This case is used, +%% when the plugin module is doing the persistence by itself or when it want +%% to completely disable persistence.</li></ul> %% </p> %% <p>In the default plugin module, the record is unchanged.</p> publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, @@ -366,26 +373,30 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, or (Affiliation == publisher) or (Affiliation == publish_only)) or (Subscribed == true)) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; true -> if MaxItems > 0 -> - Now = p1_time_compat:timestamp(), - PubId = {Now, SubKey}, - Item = case get_item(Nidx, ItemId) of - {result, OldItem} -> - OldItem#pubsub_item{modification = PubId, - payload = Payload}; + Now = erlang:timestamp(), + case get_item(Nidx, ItemId) of + {result, #pubsub_item{creation = {_, GenKey}} = OldItem} -> + set_item(OldItem#pubsub_item{ + modification = {Now, SubKey}, + payload = Payload}), + {result, {default, broadcast, []}}; + {result, _} -> + {error, xmpp:err_forbidden()}; _ -> - #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}}; + Items = [ItemId | GenState#pubsub_state.items], + {result, {NI, OI}} = remove_extra_items(Nidx, MaxItems, Items), + set_state(GenState#pubsub_state{items = NI}), + set_item(#pubsub_item{ + itemid = {ItemId, Nidx}, + nodeidx = Nidx, + creation = {Now, GenKey}, + modification = {Now, SubKey}, + payload = Payload}), + {result, {default, broadcast, OI}} + end; true -> {result, {default, broadcast, []}} end @@ -419,13 +430,13 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) -> #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, + (PublishModel == open andalso + case get_item(Nidx, ItemId) of + {result, #pubsub_item{creation = {_, GenKey}}} -> true; + _ -> false + end), if not Allowed -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; true -> case lists:member(ItemId, Items) of true -> @@ -436,23 +447,32 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) -> case Affiliation of owner -> {result, States} = get_states(Nidx), + Records = States ++ mnesia:read({pubsub_orphan, Nidx}), lists:foldl(fun - (#pubsub_state{items = PI} = S, Res) -> - case lists:member(ItemId, PI) of + (#pubsub_state{items = RI} = S, Res) -> + case lists:member(ItemId, RI) of true -> - Nitems = lists:delete(ItemId, PI), + NI = lists:delete(ItemId, RI), del_item(Nidx, ItemId), - set_state(S#pubsub_state{items = Nitems}), + mnesia:write(S#pubsub_state{items = NI}), {result, {default, broadcast}}; false -> Res end; - (_, Res) -> - Res + (#pubsub_orphan{items = RI} = S, Res) -> + case lists:member(ItemId, RI) of + true -> + NI = lists:delete(ItemId, RI), + del_item(Nidx, ItemId), + mnesia:write(S#pubsub_orphan{items = NI}), + {result, {default, broadcast}}; + false -> + Res + end end, - {error, ?ERR_ITEM_NOT_FOUND}, States); + {error, xmpp:err_item_not_found()}, Records); _ -> - {error, ?ERR_ITEM_NOT_FOUND} + {error, xmpp:err_forbidden()} end end end. @@ -472,9 +492,10 @@ purge_node(Nidx, Owner) -> set_state(S#pubsub_state{items = []}) end, States), + del_orphan_items(Nidx), {result, {default, broadcast}}; _ -> - {error, ?ERR_FORBIDDEN} + {error, xmpp:err_forbidden()} end. %% @doc <p>Return the current affiliations for the given user</p> @@ -514,8 +535,8 @@ set_affiliation(Nidx, Owner, Affiliation) -> GenKey = 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}) + {none, []} -> {result, del_state(GenState)}; + _ -> {result, set_state(GenState#pubsub_state{affiliation = Affiliation})} end. %% @doc <p>Return the current subscriptions for the given user</p> @@ -554,17 +575,10 @@ 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 + lists:foldl(fun ({S, SubId}, Acc) -> + [{J, S, SubId} | Acc] + end, + [], Subscriptions) end, {result, lists:flatmap(Tr, States)}. @@ -581,28 +595,28 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) -> case Subscription of none -> {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)}; + mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_not_subscribed())}; _ -> new_subscription(Nidx, Owner, Subscription, SubState) end; {<<>>, [{_, SID}]} -> case Subscription of - none -> unsub_with_subid(Nidx, SID, SubState); + none -> unsub_with_subid(SubState, SID); _ -> replace_subscription({Subscription, SID}, SubState) end; {<<>>, [_ | _]} -> {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}; + mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_subid_required())}; _ -> case Subscription of - none -> unsub_with_subid(Nidx, SubId, SubState); + none -> unsub_with_subid(SubState, SubId); _ -> 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}). + {result, set_state(SubState#pubsub_state{subscriptions = NewSubs})}. replace_subscription(_, [], Acc) -> Acc; replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) -> @@ -613,16 +627,16 @@ new_subscription(_Nidx, _Owner, Sub, SubState) -> SubId = pubsub_subscription:make_subid(), Subs = SubState#pubsub_state.subscriptions, set_state(SubState#pubsub_state{subscriptions = [{Sub, SubId} | Subs]}), - {Sub, SubId}. + {result, {Sub, SubId}}. -unsub_with_subid(Nidx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) -> +unsub_with_subid(SubState, SubId) -> %%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}) + {[], none} -> {result, del_state(SubState)}; + _ -> {result, set_state(SubState#pubsub_state{subscriptions = NewSubs})} end. %% @doc <p>Returns a list of Owner's nodes on Host with pending @@ -675,8 +689,7 @@ get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs} %% ```get_states(Nidx) -> %% node_default:get_states(Nidx).'''</p> get_states(Nidx) -> - States = case catch mnesia:match_object( - #pubsub_state{stateid = {'_', Nidx}, _ = '_'}) of + States = case catch mnesia:index_read(pubsub_state, Nidx, #pubsub_state.nodeidx) of List when is_list(List) -> List; _ -> [] end, @@ -687,7 +700,7 @@ 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} + _ -> #pubsub_state{stateid = StateId, nodeidx = Nidx} end. %% @doc <p>Write a state into database.</p> @@ -696,7 +709,20 @@ set_state(State) when is_record(State, pubsub_state) -> %set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}. %% @doc <p>Delete a state from database.</p> -del_state(Nidx, Key) -> +del_state(#pubsub_state{stateid = {Key, Nidx}, items = Items}) -> + case Items of + [] -> + ok; + _ -> + Orphan = #pubsub_orphan{nodeid = Nidx, items = + case mnesia:read({pubsub_orphan, Nidx}) of + [#pubsub_orphan{items = ItemIds}] -> + lists:usort(ItemIds++Items); + _ -> + Items + end}, + mnesia:write(Orphan) + end, mnesia:delete({pubsub_state, {Key, Nidx}}). %% @doc Returns the list of stored items for a given node. @@ -705,9 +731,50 @@ del_state(Nidx, Key) -> %% 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> -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, _From, undefined) -> + RItems = lists:keysort(#pubsub_item.creation, + mnesia:index_read(pubsub_item, Nidx, #pubsub_item.nodeidx)), + {result, {RItems, undefined}}; + +get_items(Nidx, _From, #rsm_set{max = Max, index = IncIndex, + 'after' = After, before = Before}) -> + case lists:keysort(#pubsub_item.creation, + mnesia:index_read(pubsub_item, Nidx, #pubsub_item.nodeidx)) of + [] -> + {result, {[], #rsm_set{count = 0}}}; + RItems -> + Count = length(RItems), + Limit = case Max of + undefined -> ?MAXITEMS; + _ -> Max + end, + {Offset, ItemsPage} = + case {IncIndex, Before, After} of + {I, undefined, undefined} -> + SubList = lists:nthtail(I, RItems), + {I, lists:sublist(SubList, Limit)}; + {_, <<>>, undefined} -> + %% 2.5 Requesting the Last Page in a Result Set + SubList = lists:reverse(RItems), + {0, lists:sublist(SubList, Limit)}; + {_, Stamp, undefined} -> + BeforeNow = encode_stamp(Stamp), + SubList = lists:dropwhile( + fun(#pubsub_item{creation = {Now, _}}) -> + Now >= BeforeNow + end, lists:reverse(RItems)), + {0, lists:sublist(SubList, Limit)}; + {_, undefined, Stamp} -> + AfterNow = encode_stamp(Stamp), + SubList = lists:dropwhile( + fun(#pubsub_item{creation = {Now, _}}) -> + Now =< AfterNow + end, RItems), + {0, lists:sublist(SubList, Limit)} + end, + Rsm = rsm_page(Count, IncIndex, Offset, ItemsPage), + {result, {ItemsPage, Rsm}} + end. get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) -> SubKey = jid:tolower(JID), @@ -721,23 +788,23 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM 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")}; + %{error, mod_pubsub:extended_error(xmpp:err_bad_request(), "subid-required")}; %%InvalidSubId -> %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + %{error, mod_pubsub:extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; (AccessModel == presence) and not PresenceSubscription -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_presence_subscription_required())}; (AccessModel == roster) and not RosterGroup -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_not_in_roster_group())}; (AccessModel == whitelist) and not Whitelisted -> {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + mod_pubsub:extended_error((xmpp:err_not_allowed()), mod_pubsub:err_closed_node())}; (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; @@ -745,12 +812,22 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM get_items(Nidx, JID, RSM) end. +get_only_item(Nidx, From) -> + get_last_items(Nidx, From, 1). + +get_last_items(Nidx, _From, Count) when Count > 0 -> + Items = mnesia:index_read(pubsub_item, Nidx, #pubsub_item.nodeidx), + LastItems = lists:reverse(lists:keysort(#pubsub_item.modification, Items)), + {result, lists:sublist(LastItems, Count)}; +get_last_items(_Nidx, _From, _Count) -> + {result, []}. + %% @doc <p>Returns an item (one item list), given its reference.</p> 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} + _ -> {error, xmpp:err_item_not_found()} end. get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> @@ -762,23 +839,23 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub 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")}; + %{error, mod_pubsub:extended_error(xmpp:err_bad_request(), "subid-required")}; %%InvalidSubId -> %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + %{error, mod_pubsub:extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; (AccessModel == presence) and not PresenceSubscription -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_presence_subscription_required())}; (AccessModel == roster) and not RosterGroup -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_not_in_roster_group())}; (AccessModel == whitelist) and not Whitelisted -> {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + mod_pubsub:extended_error((xmpp:err_not_allowed()), mod_pubsub:err_closed_node())}; (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; @@ -800,23 +877,33 @@ del_items(Nidx, ItemIds) -> end, ItemIds). +del_orphan_items(Nidx) -> + case mnesia:read({pubsub_orphan, Nidx}) of + [#pubsub_orphan{items = ItemIds}] -> + del_items(Nidx, ItemIds), + mnesia:delete({pubsub_orphan, Nidx}); + _ -> + ok + end. + get_item_name(_Host, _Node, Id) -> - Id. + {result, Id}. %% @doc <p>Return the path of the node. In flat it's just node id.</p> node_to_path(Node) -> - [(Node)]. + {result, [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) - end. + {result, + 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) + end}. can_fetch_item(owner, _) -> true; can_fetch_item(member, _) -> true; @@ -840,3 +927,32 @@ first_in_list(Pred, [H | T]) -> true -> {value, H}; _ -> first_in_list(Pred, T) end. + +rsm_page(Count, _, _, []) -> + #rsm_set{count = Count}; +rsm_page(Count, Index, Offset, Items) -> + FirstItem = hd(Items), + LastItem = lists:last(Items), + First = decode_stamp(element(1, FirstItem#pubsub_item.creation)), + Last = decode_stamp(element(1, LastItem#pubsub_item.creation)), + #rsm_set{count = Count, index = Index, + first = #rsm_first{index = Offset, data = First}, + last = Last}. + +encode_stamp(Stamp) -> + case catch xmpp_util:decode_timestamp(Stamp) of + {MS,S,US} -> {MS,S,US}; + _ -> Stamp + end. +decode_stamp(Stamp) -> + case catch xmpp_util:encode_timestamp(Stamp) of + TimeStamp when is_binary(TimeStamp) -> TimeStamp; + _ -> Stamp + end. + +transform({pubsub_state, {Id, Nidx}, Is, A, Ss}) -> + {pubsub_state, {Id, Nidx}, Nidx, Is, A, Ss}; +transform({pubsub_item, {Id, Nidx}, C, M, P}) -> + {pubsub_item, {Id, Nidx}, Nidx, C, M, P}; +transform(Rec) -> + Rec. |