%%%
%%% @reference See <a href="http://www.xmpp.org/extensions/xep-0060.html">XEP-0060: Pubsub</a> for
%%% the latest version of the PubSub specification.
-%%% This module uses version 1.11 of the specification as a base.
+%%% This module uses version 1.12 of the specification as a base.
%%% Most of the specification is implemented.
%%% Functions concerning configuration should be rewritten.
%%% Code is derivated from the original pubsub v1.7, by Alexey Shchepin <alexey@process-one.net>
-module(mod_pubsub).
-author('christophe.romain@process-one.net').
--version('1.11-01').
+-version('1.12-01').
-behaviour(gen_server).
-behaviour(gen_mod).
string_to_subscription/1,
string_to_affiliation/1,
extended_error/2,
- extended_error/3,
- make_stanza/3,
- route_stanza/3
+ extended_error/3
]).
%% API and gen_server callbacks
%% Plugins = [Plugin::string()]
%% @doc Send a message to JID with the supplied Subscription
send_authorization_approval(Host, JID, Node, Subscription) ->
- Stanza = {xmlelement, "message",
- [],
- [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}],
- [{xmlelement, "subscription",
- [{"node", Node},
- {"jid", jlib:jid_to_string(JID)},
- {"subscription", subscription_to_string(Subscription)}],
- []}]}]},
+ Stanza = event_stanza(
+ [{xmlelement, "subscription",
+ [{"node", Node},
+ {"jid", jlib:jid_to_string(JID)},
+ {"subscription", subscription_to_string(Subscription)}],
+ []}]),
ejabberd_router ! {route, service_jid(Host), JID, Stanza}.
handle_authorization_response(Host, From, To, Packet, XFields) ->
%% [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
%% [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NMI),
%% ?XFIELD("jid-single", "Node Creator", "creator", jlib:jid_to_string(OwnerKey))]}]),
- %% todo publish_item(Host, ServerHost, ["pubsub", "nodes"], node_to_string(Node)),
case Result of
default -> {result, Reply};
_ -> {result, Result}
{error, Error} ->
{error, Error};
{result, {Result, broadcast, Removed}} ->
- broadcast_removed_node(Host, Removed),
- %%broadcast_retract_item(Host, ["pubsub", "nodes"], node_to_string(Node)),
+ lists:foreach(fun(RNode) ->
+ broadcast_removed_node(Host, RNode)
+ end, Removed),
case Result of
default -> {result, Reply};
_ -> {result, Result}
end;
{result, {Result, Removed}} ->
- broadcast_removed_node(Host, Removed),
+ lists:foreach(fun(RNode) ->
+ broadcast_removed_node(Host, RNode)
+ end, Removed),
case Result of
default -> {result, Reply};
_ -> {result, Result}
{error, Reason} ->
{error, Reason};
{result, {Result, broadcast, Removed}} ->
- lists:foreach(fun(OldItem) ->
- broadcast_retract_item(Host, Node, OldItem)
- end, Removed),
+ broadcast_retract_items(Host, Node, Removed),
broadcast_publish_item(Host, Node, ItemId, jlib:jid_tolower(Publisher), Payload),
case Result of
default -> {result, Reply};
_ -> {result, Result}
end;
{result, default, Removed} ->
- lists:foreach(fun(OldItem) ->
- broadcast_retract_item(Host, Node, OldItem)
- end, Removed),
+ broadcast_retract_items(Host, Node, Removed),
{result, Reply};
{result, Result, Removed} ->
- lists:foreach(fun(OldItem) ->
- broadcast_retract_item(Host, Node, OldItem)
- end, Removed),
+ broadcast_retract_items(Host, Node, Removed),
{result, Result};
{result, default} ->
{result, Reply};
Action = fun(#pubsub_node{type = Type}) ->
Features = features(Type),
PersistentFeature = lists:member("persistent-items", Features),
- DeleteFeature = lists:member("delete-nodes", Features),
+ DeleteFeature = lists:member("delete-any", Features),
if
%%-> iq_pubsub just does that matchs
%% %% Request does not specify an item
{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
not DeleteFeature ->
%% Service does not support item deletion
- {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "delete-nodes")};
+ {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "delete-any")};
true ->
node_call(Type, delete_item, [Host, Node, Publisher, ItemId])
end
{error, Reason} ->
{error, Reason};
{result, {Result, broadcast}} ->
- broadcast_retract_item(Host, Node, ItemId, ForceNotify),
+ broadcast_retract_items(Host, Node, [ItemId], ForceNotify),
case Result of
default -> {result, Reply};
_ -> {result, Result}
[First|Tail] = Items,
[lists:foldl(
fun(CurItem, LastItem) ->
- {_, {LMS, LS, LmS}} = LastItem#pubsub_item.creation,
- {_, {CMS, CS, CmS}} = CurItem#pubsub_item.creation,
- LTimestamp = LMS * 1000000 + LS * 1000 + LmS,
- CTimestamp = CMS * 1000000 + CS * 1000 + CmS,
+ {_, LTimeStamp} = LastItem#pubsub_item.creation,
+ {_, CTimeStamp} = CurItem#pubsub_item.creation,
if
- CTimestamp > LTimestamp -> CurItem;
+ CTimeStamp > LTimeStamp -> CurItem;
true -> LastItem
end
end, First, Tail)];
end,
{xmlelement, "item", ItemAttrs, Payload}
end, ToSend),
- Stanza = {xmlelement, "message", [],
- [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}],
- [{xmlelement, "items", [{"node", node_to_string(Node)}],
- ItemsEls}]}]},
+ Stanza = event_stanza(
+ [{xmlelement, "items", [{"node", node_to_string(Node)}],
+ ItemsEls}]),
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza}.
%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
%% Subscription = atom()
%% PresenceDelivery = boolean()
%% @doc <p>Check if a notification must be delivered or not.</p>
-is_to_delivered(_, none, _) -> false;
-is_to_delivered(_, pending, _) -> false;
-is_to_delivered(_, _, false) -> true;
-is_to_delivered({User, Server, _}, _, true) ->
+is_to_deliver(_, none, _) -> false;
+is_to_deliver(_, pending, _) -> false;
+is_to_deliver(_, _, false) -> true;
+is_to_deliver({User, Server, _}, _, true) ->
case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of
[] -> false;
Ss ->
payload_xmlelements([{xmlelement, _, _, _}|Tail], Count) -> payload_xmlelements(Tail, Count+1);
payload_xmlelements([_|Tail], Count) -> payload_xmlelements(Tail, Count).
+%% @spec (Els) -> stanza()
+%% Els = [xmlelement()]
+%% @doc <p>Build pubsub event stanza
+event_stanza(Els) ->
+ {xmlelement, "message", [],
+ [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}], Els}]}.
+
%%%%%% broadcast functions
broadcast_publish_item(Host, Node, ItemId, _From, Payload) ->
Action =
fun(#pubsub_node{options = Options, type = Type}) ->
- case node_call(Type, get_states, [Host, Node]) of
- {error, _} -> {result, false};
- {result, []} -> {result, false};
- {result, States} ->
- PresenceDelivery = get_option(Options, presence_based_delivery),
- BroadcastAll = get_option(Options, broadcast_all_resources),
- Content = case get_option(Options, deliver_payloads) of
- true -> Payload;
- false -> []
- end,
- ItemAttrs = case ItemId of
- "" -> [];
- _ -> [{"id", ItemId}]
- end,
- Stanza = make_stanza(Node, ItemAttrs, Content),
- lists:foreach(
- fun(#pubsub_state{stateid = {LJID, _},
- subscription = Subscription}) ->
- case is_to_delivered(LJID, Subscription, PresenceDelivery) of
- true ->
- DestJIDs = case BroadcastAll of
- true -> ejabberd_sm:get_user_resources(element(1, LJID), element(2, LJID));
- false -> [LJID]
- end,
- route_stanza(Host, DestJIDs, Stanza);
- false ->
- ok
- end
- end, States),
- broadcast_by_caps(Host, Node, Type, Stanza),
- {result, true}
- end
+ case node_call(Type, get_states, [Host, Node]) of
+ {result, []} ->
+ {result, false};
+ {result, States} ->
+ Content = case get_option(Options, deliver_payloads) of
+ true -> Payload;
+ false -> []
+ end,
+ ItemAttrs = case ItemId of
+ "" -> [];
+ _ -> [{"id", ItemId}]
+ end,
+ Stanza = event_stanza(
+ [{xmlelement, "items", [{"node", node_to_string(Node)}],
+ [{xmlelement, "item", ItemAttrs, Content}]}]),
+ broadcast_stanza(Host, Options, States, Stanza),
+ broadcast_by_caps(Host, Node, Type, Stanza),
+ {result, true};
+ _ ->
+ {result, false}
+ end
end,
transaction(Host, Node, Action, sync_dirty).
-%% ItemAttrs is a list of tuples:
-%% For example: [{"id", ItemId}]
-make_stanza(Node, ItemAttrs, Payload) ->
- {xmlelement, "message", [],
- [{xmlelement, "event",
- [{"xmlns", ?NS_PUBSUB_EVENT}],
- [{xmlelement, "items", [{"node", node_to_string(Node)}],
- [{xmlelement, "item", ItemAttrs, Payload}]}]}]}.
-
-%% DestJIDs = [{LUser, LServer, LResource}]
-route_stanza(Host, DestJIDs, Stanza) ->
- lists:foreach(
- fun(DestJID) ->
- ejabberd_router ! {route, service_jid(Host), jlib:make_jid(DestJID), Stanza}
- end, DestJIDs).
-
-broadcast_retract_item(Host, Node, ItemId) ->
- broadcast_retract_item(Host, Node, ItemId, false).
-broadcast_retract_item(Host, Node, ItemId, ForceNotify) ->
+broadcast_retract_items(Host, Node, ItemIds) ->
+ broadcast_retract_items(Host, Node, ItemIds, false).
+broadcast_retract_items(Host, Node, ItemIds, ForceNotify) ->
Action =
fun(#pubsub_node{options = Options, type = Type}) ->
- case node_call(Type, get_states, [Host, Node]) of
- {error, _} -> {result, false};
- {result, []} -> {result, false};
- {result, States} ->
- Notify = case ForceNotify of
- true -> true;
- _ -> get_option(Options, notify_retract)
- end,
- ItemAttrs = case ItemId of
- "" -> [];
- _ -> [{"id", ItemId}]
- end,
- Stanza = {xmlelement, "message", [],
- [{xmlelement, "event",
- [{"xmlns", ?NS_PUBSUB_EVENT}],
- [{xmlelement, "items", [{"node", node_to_string(Node)}],
- [{xmlelement, "retract", ItemAttrs, []}]}]}]},
- case Notify of
- true ->
- lists:foreach(
- fun(#pubsub_state{stateid = {JID, _},
- subscription = Subscription}) ->
- if (Subscription /= none) and
- (Subscription /= pending) ->
- ejabberd_router ! {route, service_jid(Host), jlib:make_jid(JID), Stanza};
- true ->
- ok
- end
- end, States),
- broadcast_by_caps(Host, Node, Type, Stanza),
- {result, true};
- false ->
- {result, false}
- end
- end
+ case (get_option(Options, notify_retract) or ForceNotify) of
+ true ->
+ case node_call(Type, get_states, [Host, Node]) of
+ {result, []} ->
+ {result, false};
+ {result, States} ->
+ RetractEls = lists:map(
+ fun(ItemId) ->
+ ItemAttrs = case ItemId of
+ "" -> [];
+ _ -> [{"id", ItemId}]
+ end,
+ {xmlelement, "retract", ItemAttrs, []}
+ end, ItemIds),
+ Stanza = event_stanza(
+ [{xmlelement, "items", [{"node", node_to_string(Node)}],
+ RetractEls}]),
+ broadcast_stanza(Host, Options, States, Stanza),
+ broadcast_by_caps(Host, Node, Type, Stanza),
+ {result, true};
+ _ ->
+ {result, false}
+ end;
+ _ ->
+ {result, false}
+ end
end,
transaction(Host, Node, Action, sync_dirty).
broadcast_purge_node(Host, Node) ->
Action =
fun(#pubsub_node{options = Options, type = Type}) ->
- case node_call(Type, get_states, [Host, Node]) of
- {error, _} -> {result, false};
- {result, []} -> {result, false};
- {result, States} ->
- Stanza = {xmlelement, "message", [],
- [{xmlelement, "event",
- [{"xmlns", ?NS_PUBSUB_EVENT}],
- [{xmlelement, "purge", [{"node", node_to_string(Node)}],
- []}]}]},
- case get_option(Options, notify_retract) of
- true ->
- lists:foreach(
- fun(#pubsub_state{stateid = {JID,_},
- subscription = Subscription}) ->
- if (Subscription /= none) and
- (Subscription /= pending) ->
- ejabberd_router ! {route, service_jid(Host), jlib:make_jid(JID), Stanza};
- true ->
- ok
- end
- end, States),
- broadcast_by_caps(Host, Node, Type, Stanza),
- {result, true};
- false ->
- {result, false}
- end
- end
+ case get_option(Options, notify_retract) of
+ true ->
+ case node_call(Type, get_states, [Host, Node]) of
+ {result, []} ->
+ {result, false};
+ {result, States} ->
+ Stanza = event_stanza(
+ [{xmlelement, "purge", [{"node", node_to_string(Node)}], []}]),
+ broadcast_stanza(Host, Options, States, Stanza),
+ broadcast_by_caps(Host, Node, Type, Stanza),
+ {result, true};
+ _ ->
+ {result, false}
+ end;
+ _ ->
+ {result, false}
+ end
end,
transaction(Host, Node, Action, sync_dirty).
-broadcast_removed_node(Host, Removed) ->
- lists:foreach(
- fun(Node) ->
- Action =
- fun(#pubsub_node{options = Options, type = Type}) ->
- Stanza = {xmlelement, "message", [],
- [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}],
- [{xmlelement, "delete", [{"node", node_to_string(Node)}],
- []}]}]},
- case get_option(Options, notify_delete) of
- true ->
- case node_call(Type, get_states, [Host, Node]) of
- {result, States} ->
- lists:foreach(
- fun(#pubsub_state{stateid = {JID, _},
- subscription = Subscription}) ->
- if (Subscription /= none) and
- (Subscription /= pending) ->
- ejabberd_router ! {route, service_jid(Host), jlib:make_jid(JID), Stanza};
- true ->
- ok
- end
- end, States),
- broadcast_by_caps(Host, Node, Type, Stanza),
- {result, true};
- _ ->
- {result, false}
- end;
- _ ->
- {result, false}
- end
- end,
- transaction(Host, Node, Action, sync_dirty)
- end, Removed).
+broadcast_removed_node(Host, Node) ->
+ Action =
+ fun(#pubsub_node{options = Options, type = Type}) ->
+ case get_option(Options, notify_delete) of
+ true ->
+ case node_call(Type, get_states, [Host, Node]) of
+ {result, []} ->
+ {result, false};
+ {result, States} ->
+ Stanza = event_stanza(
+ [{xmlelement, "delete", [{"node", node_to_string(Node)}], []}]),
+ broadcast_stanza(Host, Options, States, Stanza),
+ broadcast_by_caps(Host, Node, Type, Stanza),
+ {result, true};
+ _ ->
+ {result, false}
+ end;
+ _ ->
+ {result, false}
+ end
+ end,
+ transaction(Host, Node, Action, sync_dirty).
broadcast_config_notification(Host, Node, Lang) ->
Action =
fun(#pubsub_node{options = Options, owners = Owners, type = Type}) ->
- case node_call(Type, get_states, [Host, Node]) of
- {error, _} -> {result, false};
- {result, []} -> {result, false};
- {result, States} ->
- case get_option(Options, notify_config) of
- true ->
- PresenceDelivery = get_option(Options, presence_based_delivery),
- Content = case get_option(Options, deliver_payloads) of
- true ->
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- get_configure_xfields(Type, Options, Lang, Owners)}];
- false ->
- []
- end,
- Stanza = {xmlelement, "message", [],
- [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}],
- [{xmlelement, "items", [{"node", node_to_string(Node)}],
- [{xmlelement, "item", [{"id", "configuration"}],
- Content}]}]}]},
- lists:foreach(
- fun(#pubsub_state{stateid = {LJID, _},
- subscription = Subscription}) ->
- case is_to_delivered(LJID, Subscription, PresenceDelivery) of
- true ->
- ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza};
- false ->
- ok
- end
- end, States),
- broadcast_by_caps(Host, Node, Type, Stanza),
- {result, true};
- _ ->
- {result, false}
- end
- end
+ case get_option(Options, notify_config) of
+ true ->
+ case node_call(Type, get_states, [Host, Node]) of
+ {result, []} ->
+ {result, false};
+ {result, States} ->
+ Content = case get_option(Options, deliver_payloads) of
+ true ->
+ [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
+ get_configure_xfields(Type, Options, Lang, Owners)}];
+ false ->
+ []
+ end,
+ Stanza = event_stanza(
+ [{xmlelement, "items", [{"node", node_to_string(Node)}],
+ [{xmlelement, "item", [{"id", "configuration"}],
+ Content}]}]),
+ broadcast_stanza(Host, Options, States, Stanza),
+ broadcast_by_caps(Host, Node, Type, Stanza),
+ {result, true};
+ _ ->
+ {result, false}
+ end;
+ _ ->
+ {result, false}
+ end
end,
transaction(Host, Node, Action, sync_dirty).
-%TODO: simplify broadcast_* using a generic function like that:
-%broadcast(Host, Node, Fun) ->
-% transaction(fun() ->
-% case tree_call(Host, get_node, [Host, Node]) of
-% #pubsub_node{options = Options, owners = Owners, type = Type} ->
-% case node_call(Type, get_states, [Host, Node]) of
-% {error, _} -> {result, false};
-% {result, []} -> {result, false};
-% {result, States} ->
-% lists:foreach(fun(#pubsub_state{stateid = {JID,_}, subscription = Subscription}) ->
-% Fun(Host, Node, Options, Owners, JID, Subscription)
-% end, States),
-% {result, true}
-% end;
-% Other ->
-% Other
-% end
-% end, sync_dirty).
-
+broadcast_stanza(Host, NodeOpts, States, Stanza) ->
+ PresenceDelivery = get_option(NodeOpts, presence_based_delivery),
+ BroadcastAll = get_option(NodeOpts, broadcast_all_resources),
+ From = service_jid(Host),
+ lists:foreach(fun(#pubsub_state{stateid = {LJID, _}, subscription = Subs}) ->
+ case is_to_deliver(LJID, Subs, PresenceDelivery) of
+ true ->
+ JIDs = case BroadcastAll of
+ true -> ejabberd_sm:get_user_resources(element(1, LJID), element(2, LJID));
+ false -> [LJID]
+ end,
+ lists:foreach(fun(JID) ->
+ ejabberd_router ! {route, From, jlib:make_jid(JID), Stanza}
+ end, JIDs);
+ false ->
+ ok
+ end
+ end, States).
%% broadcast Stanza to all contacts of the user that are advertising
%% interest in this kind of Node.
features() ->
[
- %"access-authorize", % OPTIONAL
+ %TODO "access-authorize", % OPTIONAL
"access-open", % OPTIONAL this relates to access_model option in node_default
"access-presence", % OPTIONAL this relates to access_model option in node_pep
- %"access-roster", % OPTIONAL
- %"access-whitelist", % OPTIONAL
+ %TODO "access-roster", % OPTIONAL
+ %TODO "access-whitelist", % OPTIONAL
% see plugin "auto-create", % OPTIONAL
% see plugin "auto-subscribe", % RECOMMENDED
"collections", % RECOMMENDED
"config-node", % RECOMMENDED
"create-and-configure", % RECOMMENDED
% see plugin "create-nodes", % RECOMMENDED
- %TODO "delete-any", % OPTIONAL
+ % see plugin "delete-any", % RECOMMENDED
% see plugin "delete-nodes", % RECOMMENDED
% see plugin "filtered-notifications", % RECOMMENDED
%TODO "get-pending", % OPTIONAL
["create-nodes",
"auto-create",
"delete-nodes",
+ "delete-any",
"instant-nodes",
"manage-subscriptions",
"modify-affiliations",
%% @doc <p></p>
create_node(Host, Node, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- mnesia:write(#pubsub_state{stateid = {OwnerKey, {Host, Node}},
- affiliation = owner, subscription = none}),
+ set_state(#pubsub_state{stateid = {OwnerKey, {Host, Node}}, affiliation = owner}),
{result, {default, broadcast}}.
fun(Node) ->
lists:foreach(
fun(#pubsub_state{stateid = StateId, items = Items}) ->
- lists:foreach(
- fun(ItemId) ->
- mnesia:delete(
- {pubsub_item, {ItemId, {Host, Node}}})
- end, Items),
- mnesia:delete({pubsub_state, StateId})
+ del_items(Host, Node, Items),
+ del_state(StateId)
end,
mnesia:match_object(
#pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}))
%% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(Host, Node, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup) ->
- SenderKey = jlib:jid_tolower(Sender),
- Authorized = (jlib:jid_remove_resource(SenderKey) == jlib:jid_remove_resource(Subscriber)),
- % TODO add some acl check for Authorized ?
- State = case get_state(Host, Node, Subscriber) of
- {error, ?ERR_ITEM_NOT_FOUND} ->
- #pubsub_state{stateid = {Subscriber, {Host, Node}}}; % TODO: bug on Key ?
- {result, S} -> S
- end,
+ SubscriberKey = jlib:jid_tolower(jlib:jid_remove_resource(Subscriber)),
+ Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == SubscriberKey),
+ State = get_state(Host, Node, SubscriberKey),
#pubsub_state{affiliation = Affiliation,
subscription = Subscription} = State,
if
if
AccessModel == authorize ->
pending;
- %%TODO Affiliation == none -> ?
%%NeedConfiguration ->
%% unconfigured
true ->
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Host, Node, Sender, Subscriber, _SubId) ->
- SenderKey = jlib:jid_tolower(Sender),
- Match = jlib:jid_remove_resource(SenderKey) == jlib:jid_remove_resource(Subscriber),
- Authorized = case Match of
- true ->
- true;
- false ->
- case get_state(Host, Node, SenderKey) of % TODO: bug on Key ?
- {result, #pubsub_state{affiliation=owner}} -> true;
- _ -> false
- end
- end,
- case get_state(Host, Node, Subscriber) of
- {error, ?ERR_ITEM_NOT_FOUND} ->
- %% Requesting entity is not a subscriber
+ SubscriberKey = jlib:jid_tolower(jlib:jid_remove_resource(Subscriber)),
+ Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == SubscriberKey),
+ State = get_state(Host, Node, SubscriberKey),
+ if
+ %% 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
+ State#pubsub_state.subscription == none ->
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")};
- {result, State} ->
- if
- %% 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
- State#pubsub_state.subscription == none ->
- {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")};
- %% Requesting entity is prohibited from unsubscribing entity
- not Authorized ->
- {error, ?ERR_FORBIDDEN};
- %% Was just subscriber, remove the record
- State#pubsub_state.affiliation == none ->
- mnesia:delete({pubsub_state, State#pubsub_state.stateid}),
- {result, default};
- true ->
- set_state(State#pubsub_state{subscription = none}),
- {result, default}
- end
+ %% Requesting entity is prohibited from unsubscribing entity
+ (not Authorized) and (State#pubsub_state.affiliation =/= owner) ->
+ {error, ?ERR_FORBIDDEN};
+ %% Was just subscriber, remove the record
+ State#pubsub_state.affiliation == none ->
+ del_state(State#pubsub_state.stateid),
+ {result, default};
+ true ->
+ set_state(State#pubsub_state{subscription = none}),
+ {result, default}
end.
%% @spec (Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
%% <p>In the default plugin module, the record is unchanged.</p>
publish_item(Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
PublisherKey = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
- State = case get_state(Host, Node, PublisherKey) of
- {error, ?ERR_ITEM_NOT_FOUND} -> #pubsub_state{stateid={PublisherKey, {Host, Node}}};
- {result, S} -> S
- end,
+ State = get_state(Host, Node, PublisherKey),
#pubsub_state{affiliation = Affiliation,
subscription = Subscription} = State,
if
%% Entity does not have sufficient privileges to publish to node
{error, ?ERR_FORBIDDEN};
true ->
- PubId = {PublisherKey, now()},
+ PubId = {PublisherKey, now()}, %% TODO, uses {now(),PublisherKey} for sorting (EJAB-824)
%% TODO: check creation, presence, roster (EJAB-663)
Item = case get_item(Host, Node, ItemId) of
- {error, ?ERR_ITEM_NOT_FOUND} ->
+ {result, OldItem} ->
+ OldItem#pubsub_item{modification = PubId,
+ payload = Payload};
+ _ ->
#pubsub_item{itemid = {ItemId, {Host, Node}},
creation = PubId,
modification = PubId,
- payload = Payload};
- {result, OldItem} ->
- OldItem#pubsub_item{modification = PubId,
- payload = Payload}
+ payload = Payload}
end,
Items = [ItemId | State#pubsub_state.items--[ItemId]],
{result, {NI, OI}} = remove_extra_items(
NewItems = lists:sublist(ItemIds, MaxItems),
OldItems = lists:nthtail(length(NewItems), ItemIds),
%% Remove extra items:
- lists:foreach(fun(ItemId) ->
- mnesia:delete({pubsub_item, {ItemId, {Host, Node}}})
- end, OldItems),
+ del_items(Host, Node, OldItems),
%% Return the new items list:
{result, {NewItems, OldItems}}.
%% or a publisher.</p>
delete_item(Host, Node, Publisher, ItemId) ->
PublisherKey = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
- State = case get_state(Host, Node, PublisherKey) of
- {error, ?ERR_ITEM_NOT_FOUND} ->
- #pubsub_state{stateid = {PublisherKey, {Host, Node}}};
- {result, S} ->
- S
- end,
+ State = get_state(Host, Node, PublisherKey),
#pubsub_state{affiliation = Affiliation, items = Items} = State,
Allowed = (Affiliation == publisher) orelse (Affiliation == owner)
orelse case get_item(Host, Node, ItemId) of
true ->
case get_item(Host, Node, ItemId) of
{result, _} ->
- mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}),
+ del_item(Host, Node, ItemId),
NewItems = lists:delete(ItemId, Items),
set_state(State#pubsub_state{items = NewItems}),
{result, {default, broadcast}};
purge_node(Host, Node, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
case get_state(Host, Node, OwnerKey) of
- {result, #pubsub_state{items = Items, affiliation = owner}} ->
- lists:foreach(fun(ItemId) ->
- mnesia:delete({pubsub_item, {ItemId, {Host, Node}}})
- end, Items),
+ #pubsub_state{items = Items, affiliation = owner} ->
+ del_items(Host, Node, Items),
{result, {default, broadcast}};
- {result, _} ->
- %% Entity is not owner
- {error, ?ERR_FORBIDDEN};
_ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ %% Entity is not owner
+ {error, ?ERR_FORBIDDEN}
end.
%% @spec (Host, JID) -> [{Node,Affiliation}]
get_entity_affiliations(Host, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
States = mnesia:match_object(
- #pubsub_state{stateid = {OwnerKey, {Host, '_'}},
- _ = '_'}),
+ #pubsub_state{stateid = {OwnerKey, {Host, '_'}}, _ = '_'}),
Tr = fun(#pubsub_state{stateid = {_, {_, N}}, affiliation = A}) ->
{N, A}
end,
get_node_affiliations(Host, Node) ->
States = mnesia:match_object(
- #pubsub_state{stateid = {'_', {Host, Node}},
- _ = '_'}),
+ #pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}),
Tr = fun(#pubsub_state{stateid = {J, {_, _}}, affiliation = A}) ->
{J, A}
end,
get_affiliation(Host, Node, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- Affiliation = case get_state(Host, Node, OwnerKey) of
- {result, #pubsub_state{affiliation = A}} -> A;
- _ -> none
- end,
- {result, Affiliation}.
+ State = get_state(Host, Node, OwnerKey),
+ {result, State#pubsub_state.affiliation}.
set_affiliation(Host, Node, Owner, Affiliation) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- Record = case get_state(Host, Node, OwnerKey) of
- {error, ?ERR_ITEM_NOT_FOUND} ->
- #pubsub_state{stateid = {OwnerKey, {Host, Node}},
- affiliation = Affiliation};
- {result, State} ->
- State#pubsub_state{affiliation = Affiliation}
- end,
- set_state(Record),
+ State = get_state(Host, Node, OwnerKey),
+ set_state(State#pubsub_state{affiliation = Affiliation}),
ok.
%% @spec (Host, Owner) -> [{Node,Subscription}]
get_entity_subscriptions(Host, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
States = mnesia:match_object(
- #pubsub_state{stateid = {OwnerKey, {Host, '_'}},
- _ = '_'}),
+ #pubsub_state{stateid = {OwnerKey, {Host, '_'}}, _ = '_'}),
Tr = fun(#pubsub_state{stateid = {_, {_, N}}, subscription = S}) ->
{N, S}
end,
get_node_subscriptions(Host, Node) ->
States = mnesia:match_object(
- #pubsub_state{stateid = {'_', {Host, Node}},
- _ = '_'}),
+ #pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}),
Tr = fun(#pubsub_state{stateid = {J, {_, _}}, subscription = S}) ->
{J, S}
end,
get_subscription(Host, Node, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- Subscription = case get_state(Host, Node, OwnerKey) of
- {result, #pubsub_state{subscription = S}} -> S;
- _ -> none
- end,
- {result, Subscription}.
+ State = get_state(Host, Node, OwnerKey),
+ {result, State#pubsub_state.subscription}.
set_subscription(Host, Node, Owner, Subscription) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- Record = case get_state(Host, Node, OwnerKey) of
- {error, ?ERR_ITEM_NOT_FOUND} ->
- #pubsub_state{stateid = {OwnerKey, {Host, Node}},
- subscription = Subscription};
- {result, State} ->
- State#pubsub_state{subscription = Subscription}
- end,
- set_state(Record),
+ State = get_state(Host, Node, OwnerKey),
+ set_state(State#pubsub_state{subscription = Subscription}),
ok.
%% @spec (Host, Node) -> [States] | []
%% State = mod_pubsub:pubsubItems()
%% @doc <p>Returns a state (one state list), given its reference.</p>
get_state(Host, Node, JID) ->
- case mnesia:read({pubsub_state, {JID, {Host, Node}}}) of
- [State] when is_record(State, pubsub_state) ->
- {result, State};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ StateId = {JID, {Host, Node}},
+ case mnesia:read({pubsub_state, StateId}) of
+ [State] when is_record(State, pubsub_state) -> State;
+ _ -> #pubsub_state{stateid=StateId}
end.
%% @spec (State) -> ok | {error, Reason::stanzaError()}
set_state(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}.
+%% @spec (StateId) -> ok | {error, Reason::stanzaError()}
+%% StateId = mod_pubsub:pubsubStateId()
+%% @doc <p>Delete a state from database.</p>
+del_state(StateId) ->
+ mnesia:delete({pubsub_state, StateId}).
+
%% @spec (Host, Node) -> [Items] | []
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
#pubsub_item{itemid = {'_', {Host, Node}}, _ = '_'}),
{result, Items}.
get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
- {Affiliation, Subscription} =
- case get_state(Host, Node, jlib:jid_tolower(jlib:jid_remove_resource(JID))) of
- {result, #pubsub_state{affiliation = A, subscription = S}} -> {A, S};
- _ -> {none, none}
- end,
+ State = get_state(Host, Node, jlib:jid_tolower(jlib:jid_remove_resource(JID))),
+ #pubsub_state{affiliation = Affiliation,
+ subscription = Subscription} = State,
Subscribed = not ((Subscription == none) or (Subscription == pending)),
if
%%SubID == "", ?? ->
{error, ?ERR_ITEM_NOT_FOUND}
end.
get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
- {Affiliation, Subscription} =
- case get_state(Host, Node, jlib:jid_tolower(jlib:jid_remove_resource(JID))) of
- {result, #pubsub_state{affiliation = A, subscription = S}} -> {A, S};
- _ -> {none, none}
- end,
+ State = get_state(Host, Node, jlib:jid_tolower(jlib:jid_remove_resource(JID))),
+ #pubsub_state{affiliation = Affiliation,
+ subscription = Subscription} = State,
Subscribed = not ((Subscription == none) or (Subscription == pending)),
if
%%SubID == "", ?? ->
set_item(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}.
+%% @spec (ItemId) -> ok | {error, Reason::stanzaError()}
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% ItemId = string()
+%% @doc <p>Delete an item from database.</p>
+del_item(Host, Node, ItemId) ->
+ mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}).
+del_items(Host, Node, ItemIds) ->
+ lists:foreach(fun(ItemId) ->
+ del_item(Host, Node, ItemId)
+ end, ItemIds).
+
%% @doc <p>Return the name of the node if known: Default is to return
%% node id.</p>
get_item_name(_Host, _Node, Id) ->