mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-24 17:29:28 +01:00
Initial port of pubsub changes(up to r2444) to exmpp branch.
pubsub odbc isn't ported yet. Not tested (only basic node creation and configuration), should still have losts of bugs to discover. SVN Revision: 2533
This commit is contained in:
parent
6470e6cc25
commit
938a4007b3
@ -46,7 +46,7 @@ behaviour_info(callbacks) ->
|
||||
{create_node, 2},
|
||||
{delete_node, 1},
|
||||
{purge_node, 2},
|
||||
{subscribe_node, 7},
|
||||
{subscribe_node, 8},
|
||||
{unsubscribe_node, 4},
|
||||
{publish_item, 6},
|
||||
{delete_item, 4},
|
||||
@ -57,8 +57,9 @@ behaviour_info(callbacks) ->
|
||||
{set_affiliation, 3},
|
||||
{get_node_subscriptions, 1},
|
||||
{get_entity_subscriptions, 2},
|
||||
{get_subscription, 2},
|
||||
{set_subscription, 3},
|
||||
{get_subscriptions, 2},
|
||||
{set_subscriptions, 4},
|
||||
{get_pending_nodes, 2},
|
||||
{get_states, 1},
|
||||
{get_state, 2},
|
||||
{set_state, 1},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -47,7 +47,7 @@
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/7,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
@ -58,8 +58,9 @@
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_node_subscriptions/1,
|
||||
get_subscription/2,
|
||||
set_subscription/3,
|
||||
get_subscriptions/2,
|
||||
set_subscriptions/4,
|
||||
get_pending_nodes/2,
|
||||
get_states/1,
|
||||
get_state/2,
|
||||
set_state/1,
|
||||
@ -79,8 +80,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, buddy},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
@ -122,8 +122,8 @@ create_node(NodeId, Owner) ->
|
||||
delete_node(Removed) ->
|
||||
node_hometree:delete_node(Removed).
|
||||
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
|
||||
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
|
||||
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
|
||||
node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
|
||||
@ -158,11 +158,15 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
get_node_subscriptions(NodeId) ->
|
||||
node_hometree:get_node_subscriptions(NodeId).
|
||||
|
||||
get_subscription(NodeId, Owner) ->
|
||||
node_hometree:get_subscription(NodeId, Owner).
|
||||
get_subscriptions(NodeId, Owner) ->
|
||||
node_hometree:get_subscriptions(NodeId, Owner).
|
||||
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
|
||||
|
||||
get_pending_nodes(Host, Owner) ->
|
||||
node_hometree:get_pending_nodes(Host, Owner).
|
||||
|
||||
set_subscription(NodeId, Owner, Subscription) ->
|
||||
node_hometree:set_subscription(NodeId, Owner, Subscription).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_hometree:get_states(NodeId).
|
||||
|
@ -26,9 +26,8 @@
|
||||
-module(node_club).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-behaviour(gen_pubsub_node).
|
||||
|
||||
@ -47,7 +46,7 @@
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/7,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
@ -58,8 +57,9 @@
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_node_subscriptions/1,
|
||||
get_subscription/2,
|
||||
set_subscription/3,
|
||||
get_subscriptions/2,
|
||||
set_subscriptions/4,
|
||||
get_pending_nodes/2,
|
||||
get_states/1,
|
||||
get_state/2,
|
||||
set_state/1,
|
||||
@ -79,8 +79,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, club},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
@ -121,8 +120,8 @@ create_node(NodeId, Owner) ->
|
||||
delete_node(Removed) ->
|
||||
node_hometree:delete_node(Removed).
|
||||
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
|
||||
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
|
||||
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
|
||||
node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
|
||||
@ -157,11 +156,14 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
get_node_subscriptions(NodeId) ->
|
||||
node_hometree:get_node_subscriptions(NodeId).
|
||||
|
||||
get_subscription(NodeId, Owner) ->
|
||||
node_hometree:get_subscription(NodeId, Owner).
|
||||
get_subscriptions(NodeId, Owner) ->
|
||||
node_hometree:get_subscriptions(NodeId, Owner).
|
||||
|
||||
set_subscription(NodeId, Owner, Subscription) ->
|
||||
node_hometree:set_subscription(NodeId, Owner, Subscription).
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
|
||||
|
||||
get_pending_nodes(Host, Owner) ->
|
||||
node_hometree:get_pending_nodes(Host, Owner).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_hometree:get_states(NodeId).
|
||||
|
175
src/mod_pubsub/node_dag.erl
Normal file
175
src/mod_pubsub/node_dag.erl
Normal file
@ -0,0 +1,175 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% @author Brian Cully <bjc@kublai.com>
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
-module(node_dag).
|
||||
-author('bjc@kublai.com').
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-behaviour(gen_pubsub_node).
|
||||
|
||||
%% API definition
|
||||
-export([init/3, terminate/2,
|
||||
options/0, features/0,
|
||||
create_node_permission/6,
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
remove_extra_items/3,
|
||||
get_entity_affiliations/2,
|
||||
get_node_affiliations/1,
|
||||
get_affiliation/2,
|
||||
set_affiliation/3,
|
||||
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_item/7,
|
||||
get_item/2,
|
||||
set_item/1,
|
||||
get_item_name/3]).
|
||||
|
||||
|
||||
init(Host, ServerHost, Opts) ->
|
||||
node_hometree:init(Host, ServerHost, Opts).
|
||||
|
||||
terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, leaf} | node_hometree:options()].
|
||||
|
||||
features() ->
|
||||
["multi-collection" | node_hometree:features()].
|
||||
|
||||
create_node_permission(_Host, _ServerHost, _Node, _ParentNode,
|
||||
_Owner, _Access) ->
|
||||
{result, true}.
|
||||
|
||||
create_node(NodeID, Owner) ->
|
||||
node_hometree:create_node(NodeID, Owner).
|
||||
|
||||
delete_node(Removed) ->
|
||||
node_hometree:delete_node(Removed).
|
||||
|
||||
subscribe_node(NodeID, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
node_hometree:subscribe_node(NodeID, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup,
|
||||
Options).
|
||||
|
||||
unsubscribe_node(NodeID, Sender, Subscriber, SubID) ->
|
||||
node_hometree:unsubscribe_node(NodeID, Sender, Subscriber, SubID).
|
||||
|
||||
publish_item(NodeID, Publisher, Model, MaxItems, ItemID, Payload) ->
|
||||
%% TODO: should look up the NodeTree plugin here. There's no
|
||||
%% access to the Host of the request at this level, so for now we
|
||||
%% just use nodetree_dag.
|
||||
case nodetree_dag:get_node(NodeID) of
|
||||
#pubsub_node{options = Options} ->
|
||||
case find_opt(node_type, Options) of
|
||||
collection ->
|
||||
{error, mod_pubsub:extended_error('not-allowed', "publish")};
|
||||
_ ->
|
||||
node_hometree:publish_item(NodeID, Publisher, Model,
|
||||
MaxItems, ItemID, Payload)
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
find_opt(_, []) -> false;
|
||||
find_opt(Option, [{Option, Value} | _]) -> Value;
|
||||
find_opt(Option, [_ | T]) -> find_opt(Option, T).
|
||||
|
||||
remove_extra_items(NodeID, MaxItems, ItemIDs) ->
|
||||
node_hometree:remove_extra_items(NodeID, MaxItems, ItemIDs).
|
||||
|
||||
delete_item(NodeID, Publisher, PublishModel, ItemID) ->
|
||||
node_hometree:delete_item(NodeID, Publisher, PublishModel, ItemID).
|
||||
|
||||
purge_node(NodeID, Owner) ->
|
||||
node_hometree:purge_node(NodeID, Owner).
|
||||
|
||||
get_entity_affiliations(Host, Owner) ->
|
||||
node_hometree:get_entity_affiliations(Host, Owner).
|
||||
|
||||
get_node_affiliations(NodeID) ->
|
||||
node_hometree:get_node_affiliations(NodeID).
|
||||
|
||||
get_affiliation(NodeID, Owner) ->
|
||||
node_hometree:get_affiliation(NodeID, Owner).
|
||||
|
||||
set_affiliation(NodeID, Owner, Affiliation) ->
|
||||
node_hometree:set_affiliation(NodeID, Owner, Affiliation).
|
||||
|
||||
get_entity_subscriptions(Host, Owner) ->
|
||||
node_hometree:get_entity_subscriptions(Host, Owner).
|
||||
|
||||
get_node_subscriptions(NodeID) ->
|
||||
node_hometree:get_node_subscriptions(NodeID).
|
||||
|
||||
get_subscriptions(NodeID, Owner) ->
|
||||
node_hometree:get_subscriptions(NodeID, Owner).
|
||||
|
||||
set_subscriptions(NodeID, Owner, Subscription, SubID) ->
|
||||
node_hometree:set_subscriptions(NodeID, Owner, Subscription, SubID).
|
||||
|
||||
get_pending_nodes(Host, Owner) ->
|
||||
node_hometree:get_pending_nodes(Host, Owner).
|
||||
|
||||
get_states(NodeID) ->
|
||||
node_hometree:get_states(NodeID).
|
||||
|
||||
get_state(NodeID, JID) ->
|
||||
node_hometree:get_state(NodeID, JID).
|
||||
|
||||
set_state(State) ->
|
||||
node_hometree:set_state(State).
|
||||
|
||||
get_items(NodeID, From) ->
|
||||
node_hometree:get_items(NodeID, From).
|
||||
|
||||
get_items(NodeID, JID, AccessModel, PresenceSubscription,
|
||||
RosterGroup, SubID) ->
|
||||
node_hometree:get_items(NodeID, JID, AccessModel, PresenceSubscription,
|
||||
RosterGroup, SubID).
|
||||
|
||||
get_item(NodeID, ItemID) ->
|
||||
node_hometree:get_item(NodeID, ItemID).
|
||||
|
||||
get_item(NodeID, ItemID, JID, AccessModel, PresenceSubscription,
|
||||
RosterGroup, SubID) ->
|
||||
node_hometree:get_item(NodeID, ItemID, JID, AccessModel,
|
||||
PresenceSubscription, RosterGroup, SubID).
|
||||
|
||||
set_item(Item) ->
|
||||
node_hometree:set_item(Item).
|
||||
|
||||
get_item_name(Host, Node, ID) ->
|
||||
node_hometree:get_item_name(Host, Node, ID).
|
@ -26,9 +26,8 @@
|
||||
-module(node_dispatch).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-behaviour(gen_pubsub_node).
|
||||
|
||||
@ -45,7 +44,7 @@
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/7,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
@ -56,8 +55,9 @@
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_node_subscriptions/1,
|
||||
get_subscription/2,
|
||||
set_subscription/3,
|
||||
get_subscriptions/2,
|
||||
set_subscriptions/4,
|
||||
get_pending_nodes/2,
|
||||
get_states/1,
|
||||
get_state/2,
|
||||
set_state/1,
|
||||
@ -77,8 +77,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, dispatch},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
@ -119,27 +118,27 @@ delete_node(Removed) ->
|
||||
node_hometree:delete_node(Removed).
|
||||
|
||||
subscribe_node(_NodeId, _Sender, _Subscriber, _AccessModel,
|
||||
_SendLast, _PresenceSubscription, _RosterGroup) ->
|
||||
{error, 'forbidden'}.
|
||||
_SendLast, _PresenceSubscription, _RosterGroup, _Options) ->
|
||||
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')}.
|
||||
|
||||
unsubscribe_node(_NodeId, _Sender, _Subscriber, _SubID) ->
|
||||
{error, 'forbidden'}.
|
||||
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')}.
|
||||
|
||||
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
lists:foreach(fun(SubNode) ->
|
||||
node_hometree:publish_item(
|
||||
SubNode#pubsub_node.id, Publisher, Model,
|
||||
MaxItems, ItemId, Payload)
|
||||
end, nodetree_default:get_subnodes(NodeId, Publisher)).
|
||||
end, nodetree_tree:get_subnodes(NodeId, Publisher, Publisher)).
|
||||
|
||||
remove_extra_items(_NodeId, _MaxItems, ItemIds) ->
|
||||
{result, {ItemIds, []}}.
|
||||
|
||||
delete_item(_NodeId, _Publisher, _PublishModel, _ItemId) ->
|
||||
{error, 'item-not-found'}.
|
||||
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')}.
|
||||
|
||||
purge_node(_NodeId, _Owner) ->
|
||||
{error, 'forbidden'}.
|
||||
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')}.
|
||||
|
||||
get_entity_affiliations(_Host, _Owner) ->
|
||||
{result, []}.
|
||||
@ -161,11 +160,14 @@ get_node_subscriptions(NodeId) ->
|
||||
%% DO NOT REMOVE
|
||||
node_hometree:get_node_subscriptions(NodeId).
|
||||
|
||||
get_subscription(_NodeId, _Owner) ->
|
||||
get_subscriptions(_NodeId, _Owner) ->
|
||||
{result, []}.
|
||||
|
||||
set_subscription(NodeId, Owner, Subscription) ->
|
||||
node_hometree:set_subscription(NodeId, Owner, Subscription).
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
|
||||
|
||||
get_pending_nodes(Host, Owner) ->
|
||||
node_hometree:get_pending_nodes(Host, Owner).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_hometree:get_states(NodeId).
|
||||
@ -193,3 +195,4 @@ set_item(Item) ->
|
||||
|
||||
get_item_name(Host, Node, Id) ->
|
||||
node_hometree:get_item_name(Host, Node, Id).
|
||||
|
||||
|
@ -25,9 +25,8 @@
|
||||
-module(node_flat).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-behaviour(gen_pubsub_node).
|
||||
|
||||
@ -38,7 +37,7 @@
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/7,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
@ -49,8 +48,9 @@
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_node_subscriptions/1,
|
||||
get_subscription/2,
|
||||
set_subscription/3,
|
||||
get_subscriptions/2,
|
||||
set_subscriptions/4,
|
||||
get_pending_nodes/2,
|
||||
get_states/1,
|
||||
get_state/2,
|
||||
set_state/1,
|
||||
@ -70,8 +70,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, flat},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
@ -109,8 +108,8 @@ create_node(NodeId, Owner) ->
|
||||
delete_node(Removed) ->
|
||||
node_hometree:delete_node(Removed).
|
||||
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
|
||||
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
|
||||
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
|
||||
node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
|
||||
@ -145,11 +144,14 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
get_node_subscriptions(NodeId) ->
|
||||
node_hometree:get_node_subscriptions(NodeId).
|
||||
|
||||
get_subscription(NodeId, Owner) ->
|
||||
node_hometree:get_subscription(NodeId, Owner).
|
||||
get_subscriptions(NodeId, Owner) ->
|
||||
node_hometree:get_subscriptions(NodeId, Owner).
|
||||
|
||||
set_subscription(NodeId, Owner, Subscription) ->
|
||||
node_hometree:set_subscription(NodeId, Owner, Subscription).
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
|
||||
|
||||
get_pending_nodes(Host, Owner) ->
|
||||
node_hometree:get_pending_nodes(Host, Owner).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_hometree:get_states(NodeId).
|
||||
|
@ -41,9 +41,8 @@
|
||||
-module(node_hometree).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-behaviour(gen_pubsub_node).
|
||||
|
||||
@ -54,7 +53,7 @@
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/7,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
@ -65,8 +64,9 @@
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_node_subscriptions/1,
|
||||
get_subscription/2,
|
||||
set_subscription/3,
|
||||
get_subscriptions/2,
|
||||
set_subscriptions/4,
|
||||
get_pending_nodes/2,
|
||||
get_states/1,
|
||||
get_state/2,
|
||||
set_state/1,
|
||||
@ -92,15 +92,10 @@
|
||||
%% plugin. It can be used for example by the developer to create the specific
|
||||
%% module database schema if it does not exists yet.</p>
|
||||
init(_Host, _ServerHost, _Opts) ->
|
||||
pubsub_subscription:init(),
|
||||
mnesia:create_table(pubsub_state,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, pubsub_state)}]),
|
||||
StatesFields = record_info(fields, pubsub_state),
|
||||
case mnesia:table_info(pubsub_state, attributes) of
|
||||
StatesFields -> ok;
|
||||
_ ->
|
||||
mnesia:transform_table(pubsub_state, ignore, StatesFields)
|
||||
end,
|
||||
mnesia:create_table(pubsub_item,
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes, record_info(fields, pubsub_item)}]),
|
||||
@ -138,8 +133,7 @@ terminate(_Host, _ServerHost) ->
|
||||
%% {send_last_published_item, never},
|
||||
%% {presence_based_delivery, false}]'''
|
||||
options() ->
|
||||
[{node_type, default},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
@ -159,11 +153,14 @@ 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",
|
||||
@ -173,7 +170,8 @@ features() ->
|
||||
"retrieve-items",
|
||||
"retrieve-subscriptions",
|
||||
"subscribe",
|
||||
"subscription-notifications"
|
||||
"subscription-notifications",
|
||||
"subscription-options"
|
||||
].
|
||||
|
||||
%% @spec (Host, ServerHost, Node, ParentNode, Owner, Access) -> bool()
|
||||
@ -229,8 +227,10 @@ create_node(NodeId, Owner) ->
|
||||
%% Removed = [mod_pubsub:pubsubNode()]
|
||||
%% @doc <p>purge items of deleted nodes after effective deletion.</p>
|
||||
delete_node(Removed) ->
|
||||
Tr = fun(#pubsub_state{stateid = {J, _}, subscription = S}) ->
|
||||
{J, S}
|
||||
Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
|
||||
lists:map(fun(S) ->
|
||||
{J, S}
|
||||
end, Ss)
|
||||
end,
|
||||
Reply = lists:map(
|
||||
fun(#pubsub_node{id = NodeId} = PubsubNode) ->
|
||||
@ -240,7 +240,7 @@ delete_node(Removed) ->
|
||||
del_items(NodeId, Items),
|
||||
del_state(NodeId, LJID)
|
||||
end, States),
|
||||
{PubsubNode, lists:map(Tr, States)}
|
||||
{PubsubNode, lists:flatmap(Tr, States)}
|
||||
end, Removed),
|
||||
{result, {default, broadcast, Reply}}.
|
||||
|
||||
@ -278,7 +278,7 @@ delete_node(Removed) ->
|
||||
%% </p>
|
||||
%% <p>In the default plugin module, the record is unchanged.</p>
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup) ->
|
||||
SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
SubKey = jlib:short_prepd_jid(Subscriber),
|
||||
GenKey = jlib:short_prepd_bare_jid(SubKey),
|
||||
Authorized = (jlib:short_prepd_bare_jid(Sender) == GenKey),
|
||||
@ -288,8 +288,11 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
_ -> get_state(NodeId, SubKey)
|
||||
end,
|
||||
Affiliation = GenState#pubsub_state.affiliation,
|
||||
Subscription = SubState#pubsub_state.subscription,
|
||||
Subscriptions = SubState#pubsub_state.subscriptions,
|
||||
Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
|
||||
PendingSubscription = lists:any(fun({pending, _}) -> true;
|
||||
(_) -> false
|
||||
end, Subscriptions),
|
||||
if
|
||||
not Authorized ->
|
||||
%% JIDs do not match
|
||||
@ -297,7 +300,7 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
Affiliation == outcast ->
|
||||
%% Requesting entity is blocked
|
||||
{error, 'forbidden'};
|
||||
Subscription == pending ->
|
||||
PendingSubscription ->
|
||||
%% Requesting entity has pending subscription
|
||||
{error, ?ERR_EXTENDED('not-authorized', "pending-subscription")};
|
||||
(AccessModel == presence) and (not PresenceSubscription) ->
|
||||
@ -319,36 +322,35 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
%% % Requesting entity is anonymous
|
||||
%% {error, 'forbidden'};
|
||||
true ->
|
||||
NewSubscription =
|
||||
if
|
||||
AccessModel == authorize ->
|
||||
pending;
|
||||
%%NeedConfiguration ->
|
||||
%% unconfigured
|
||||
true ->
|
||||
subscribed
|
||||
end,
|
||||
set_state(SubState#pubsub_state{subscription = NewSubscription}),
|
||||
case NewSubscription of
|
||||
subscribed ->
|
||||
case SendLast of
|
||||
never -> {result, {default, NewSubscription}};
|
||||
_ -> {result, {default, NewSubscription, send_last}}
|
||||
case pubsub_subscription:subscribe_node(Subscriber, NodeId, Options) of
|
||||
{result, SubId} ->
|
||||
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;
|
||||
_ ->
|
||||
{result, {default, NewSubscription}}
|
||||
{error, 'internal-server-error'}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (NodeId, Sender, Subscriber, SubID) ->
|
||||
%% @spec (NodeId, Sender, Subscriber, SubId) ->
|
||||
%% {error, Reason} | {result, []}
|
||||
%% NodeId = mod_pubsub:pubsubNodeId()
|
||||
%% Sender = mod_pubsub:jid()
|
||||
%% Subscriber = mod_pubsub:jid()
|
||||
%% SubID = string()
|
||||
%% SubId = mod_pubsub:subid()
|
||||
%% Reason = mod_pubsub:stanzaError()
|
||||
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, _SubId) ->
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
SubKey = jlib:short_prepd_jid(Subscriber),
|
||||
GenKey = jlib:short_prepd_bare_jid(SubKey),
|
||||
Authorized = (jlib:short_prepd_bare_jid(Sender) == GenKey),
|
||||
@ -357,28 +359,67 @@ unsubscribe_node(NodeId, Sender, Subscriber, _SubId) ->
|
||||
GenKey -> GenState;
|
||||
_ -> get_state(NodeId, SubKey)
|
||||
end,
|
||||
Subscriptions = lists:filter(fun({_Sub, _SubId}) -> true;
|
||||
(_SubId) -> false
|
||||
end, SubState#pubsub_state.subscriptions),
|
||||
SubIdExists = case SubId of
|
||||
[] -> false;
|
||||
List when is_list(List) -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if
|
||||
%% Requesting entity is prohibited from unsubscribing entity
|
||||
not Authorized ->
|
||||
{error, 'forbidden'};
|
||||
%% Entity did not specify SubID
|
||||
%%SubID == "", ?? ->
|
||||
%% Entity did not specify SubId
|
||||
%%SubId == "", ?? ->
|
||||
%% {error, ?ERR_EXTENDED('bad-request', "subid-required")};
|
||||
%% Invalid subscription identifier
|
||||
%%InvalidSubID ->
|
||||
%%InvalidSubId ->
|
||||
%% {error, ?ERR_EXTENDED('not-acceptable', "invalid-subid")};
|
||||
%% Requesting entity is not a subscriber
|
||||
SubState#pubsub_state.subscription == none ->
|
||||
Subscriptions == [] ->
|
||||
{error, ?ERR_EXTENDED('unexpected-request', "not-subscribed")};
|
||||
%% Was just subscriber, remove the record
|
||||
SubState#pubsub_state.affiliation == none ->
|
||||
del_state(NodeId, SubKey),
|
||||
%% Subid supplied, so use that.
|
||||
SubIdExists ->
|
||||
Sub = first_in_list(fun(S) ->
|
||||
case S of
|
||||
{_Sub, SubId} -> true;
|
||||
_ -> false
|
||||
end
|
||||
end, SubState#pubsub_state.subscriptions),
|
||||
case Sub of
|
||||
{value, S} ->
|
||||
delete_subscription(SubKey, NodeId, S, SubState),
|
||||
{result, default};
|
||||
false ->
|
||||
{error, ?ERR_EXTENDED('unexpected-request',
|
||||
"not-subscribed")}
|
||||
end;
|
||||
%% No subid supplied, but there's only one matching
|
||||
%% subscription, so use that.
|
||||
length(Subscriptions) == 1 ->
|
||||
delete_subscription(SubKey, NodeId, hd(Subscriptions), SubState),
|
||||
{result, default};
|
||||
true ->
|
||||
set_state(SubState#pubsub_state{subscription = none}),
|
||||
{result, default}
|
||||
{error, ?ERR_EXTENDED('bad-request', "subid-required")}
|
||||
end.
|
||||
|
||||
delete_subscription(SubKey, NodeID, {Subscription, SubId}, SubState) ->
|
||||
Affiliation = SubState#pubsub_state.affiliation,
|
||||
AllSubs = SubState#pubsub_state.subscriptions,
|
||||
NewSubs = AllSubs -- [{Subscription, SubId}],
|
||||
pubsub_subscription:unsubscribe_node(SubKey, NodeID, SubId),
|
||||
case {Affiliation, NewSubs} of
|
||||
{none, []} ->
|
||||
% Just a regular subscriber, and this is final item, so
|
||||
% delete the state.
|
||||
del_state(NodeID, SubKey);
|
||||
_ ->
|
||||
set_state(SubState#pubsub_state{subscriptions = NewSubs})
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
|
||||
%% {true, PubsubItem} | {result, Reply}
|
||||
%% NodeId = mod_pubsub:pubsubNodeId()
|
||||
@ -426,13 +467,15 @@ publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
|
||||
_ -> get_state(NodeId, SubKey)
|
||||
end,
|
||||
Affiliation = GenState#pubsub_state.affiliation,
|
||||
Subscription = SubState#pubsub_state.subscription,
|
||||
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 ((PublishModel == subscribers)
|
||||
and (Subscription == subscribed))) ->
|
||||
or (Subscribed == true)) ->
|
||||
%% Entity does not have sufficient privileges to publish to node
|
||||
{error, 'forbidden'};
|
||||
true ->
|
||||
@ -554,7 +597,7 @@ get_entity_affiliations(Host, Owner) ->
|
||||
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
|
||||
NodeTree = case ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
|
||||
[{nodetree, N}] -> N;
|
||||
_ -> nodetree_default
|
||||
_ -> nodetree_tree
|
||||
end,
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
|
||||
case NodeTree:get_node(N) of
|
||||
@ -566,8 +609,8 @@ get_entity_affiliations(Host, Owner) ->
|
||||
|
||||
get_node_affiliations(NodeId) ->
|
||||
{result, States} = get_states(NodeId),
|
||||
Tr = fun(#pubsub_state{stateid = {J, {_, _}}, affiliation = A}) ->
|
||||
{J, A}
|
||||
Tr = fun(#pubsub_state{stateid = {J, _}, affiliation = A}) ->
|
||||
{J, A}
|
||||
end,
|
||||
{result, lists:map(Tr, States)}.
|
||||
|
||||
@ -579,7 +622,7 @@ get_affiliation(NodeId, Owner) ->
|
||||
set_affiliation(NodeId, Owner, Affiliation) ->
|
||||
GenKey = jlib:short_prepd_bare_jid(Owner),
|
||||
GenState = get_state(NodeId, GenKey),
|
||||
case {Affiliation, GenState#pubsub_state.subscription} of
|
||||
case {Affiliation, GenState#pubsub_state.subscriptions} of
|
||||
{none, none} ->
|
||||
del_state(NodeId, GenKey);
|
||||
_ ->
|
||||
@ -598,7 +641,7 @@ set_affiliation(NodeId, Owner, Affiliation) ->
|
||||
%% <tt>pubsub_state</tt> table.</p>
|
||||
get_entity_subscriptions(Host, Owner) ->
|
||||
{U, D, _} = SubKey = jlib:short_prepd_jid(Owner),
|
||||
GenKey = jlib:short_prepd_bare_jid(SubKey),
|
||||
GenKey = jlib:short_prepd_bare_jid(Owner),
|
||||
States = case SubKey of
|
||||
GenKey -> mnesia:match_object(
|
||||
#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
|
||||
@ -609,11 +652,16 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
end,
|
||||
NodeTree = case ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
|
||||
[{nodetree, N}] -> N;
|
||||
_ -> nodetree_default
|
||||
_ -> nodetree_tree
|
||||
end,
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscription = S}, Acc) ->
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
|
||||
case NodeTree:get_node(N) of
|
||||
#pubsub_node{nodeid = {Host, _}} = Node -> [{Node, S, J}|Acc];
|
||||
#pubsub_node{nodeid = {Host, _}} = Node ->
|
||||
lists:foldl(fun({Sub, SubId}, Acc2) ->
|
||||
[{Node, Sub, SubId, J} | Acc2];
|
||||
(S, Acc2) ->
|
||||
[{Node, S, J} | Acc2]
|
||||
end, Acc, Ss);
|
||||
_ -> Acc
|
||||
end
|
||||
end, [], States),
|
||||
@ -621,26 +669,115 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
|
||||
get_node_subscriptions(NodeId) ->
|
||||
{result, States} = get_states(NodeId),
|
||||
Tr = fun(#pubsub_state{stateid = {J, _}, subscription = S}) ->
|
||||
{J, S}
|
||||
Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) ->
|
||||
%% TODO: get rid of cases to handle non-list subscriptions
|
||||
case Subscriptions of
|
||||
[_|_] ->
|
||||
lists:foldl(fun({S, SubId}, Acc) ->
|
||||
[{J, S, SubId} | Acc];
|
||||
(S, Acc) ->
|
||||
[{J, S} | Acc]
|
||||
end, [], Subscriptions);
|
||||
[] ->
|
||||
[];
|
||||
_ ->
|
||||
[{J, none}]
|
||||
end
|
||||
end,
|
||||
{result, lists:map(Tr, States)}.
|
||||
{result, lists:flatmap(Tr, States)}.
|
||||
|
||||
get_subscription(NodeId, Owner) ->
|
||||
get_subscriptions(NodeId, Owner) ->
|
||||
SubKey = jlib:short_prepd_jid(Owner),
|
||||
SubState = get_state(NodeId, SubKey),
|
||||
{result, SubState#pubsub_state.subscription}.
|
||||
{result, SubState#pubsub_state.subscriptions}.
|
||||
|
||||
set_subscription(NodeId, Owner, Subscription) ->
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
SubKey = jlib:short_prepd_jid(Owner),
|
||||
SubState = get_state(NodeId, SubKey),
|
||||
case {Subscription, SubState#pubsub_state.affiliation} of
|
||||
{none, none} ->
|
||||
del_state(NodeId, SubKey);
|
||||
case {SubId, SubState#pubsub_state.subscriptions} of
|
||||
{_, []} ->
|
||||
{error, 'item-not-found'};
|
||||
{"", [{_, SID}]} ->
|
||||
case Subscription of
|
||||
none -> unsub_with_subid(NodeId, SID, SubState);
|
||||
_ -> replace_subscription({Subscription, SID}, SubState)
|
||||
end;
|
||||
{"", [_|_]} ->
|
||||
{error, ?ERR_EXTENDED('bad_request', "subid-required")};
|
||||
_ ->
|
||||
set_state(SubState#pubsub_state{subscription = Subscription})
|
||||
end,
|
||||
ok.
|
||||
case Subscription of
|
||||
none -> unsub_with_subid(NodeId, 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]).
|
||||
|
||||
unsub_with_subid(NodeId, SubId, SubState) ->
|
||||
pubsub_subscription:unsubscribe_node(SubState#pubsub_state.stateid,
|
||||
NodeId, SubId),
|
||||
NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID end,
|
||||
SubState#pubsub_state.subscriptions),
|
||||
case {NewSubs, SubState#pubsub_state.affiliation} of
|
||||
{[], none} ->
|
||||
del_state(NodeId, element(1, SubState#pubsub_state.stateid));
|
||||
_ ->
|
||||
set_state(SubState#pubsub_state{subscriptions = NewSubs})
|
||||
end.
|
||||
%% @spec (Host, Owner) -> {result, [Node]} | {error, Reason}
|
||||
%% Host = host()
|
||||
%% Owner = jid()
|
||||
%% Node = pubsubNode()
|
||||
%% @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,
|
||||
_ = '_'}),
|
||||
NodeIDs = [ID || #pubsub_state{stateid = {_, ID}} <- States],
|
||||
NodeTree = case ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
|
||||
[{nodetree, N}] -> N;
|
||||
_ -> nodetree_tree
|
||||
end,
|
||||
Reply = mnesia:foldl(fun(#pubsub_state{stateid = {_, NID}} = S, Acc) ->
|
||||
case lists:member(NID, NodeIDs) 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.
|
||||
|
||||
%% @spec (NodeId) -> [States] | []
|
||||
%% NodeId = mod_pubsub:pubsubNodeId()
|
||||
@ -655,8 +792,11 @@ set_subscription(NodeId, Owner, Subscription) ->
|
||||
%% ```get_states(NodeId) ->
|
||||
%% node_default:get_states(NodeId).'''</p>
|
||||
get_states(NodeId) ->
|
||||
States = mnesia:match_object(
|
||||
#pubsub_state{stateid = {'_', NodeId}, _ = '_'}),
|
||||
States = case catch mnesia:match_object(
|
||||
#pubsub_state{stateid = {'_', NodeId}, _ = '_'}) of
|
||||
List when is_list(List) -> List;
|
||||
_ -> []
|
||||
end,
|
||||
{result, States}.
|
||||
|
||||
%% @spec (NodeId, JID) -> [State] | []
|
||||
@ -666,7 +806,7 @@ get_states(NodeId) ->
|
||||
%% @doc <p>Returns a state (one state list), given its reference.</p>
|
||||
get_state(NodeId, JID) ->
|
||||
StateId = {JID, NodeId},
|
||||
case mnesia:read({pubsub_state, StateId}) of
|
||||
case catch mnesia:read({pubsub_state, StateId}) of
|
||||
[State] when is_record(State, pubsub_state) -> State;
|
||||
_ -> #pubsub_state{stateid=StateId}
|
||||
end.
|
||||
@ -702,16 +842,18 @@ get_items(NodeId, _From) ->
|
||||
Items = mnesia:match_object(#pubsub_item{itemid = {'_', NodeId}, _ = '_'}),
|
||||
{result, lists:reverse(lists:keysort(#pubsub_item.modification, Items))}.
|
||||
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
|
||||
SubKey = jlib:short_prepd_jid(JID),
|
||||
GenKey = jlib:short_prepd_bare_jid(JID),
|
||||
GenState = get_state(NodeId, GenKey),
|
||||
SubState = get_state(NodeId, SubKey),
|
||||
Affiliation = GenState#pubsub_state.affiliation,
|
||||
Subscription = GenState#pubsub_state.subscription,
|
||||
Whitelisted = can_fetch_item(Affiliation, Subscription),
|
||||
Subscriptions = SubState#pubsub_state.subscriptions,
|
||||
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
|
||||
if
|
||||
%%SubID == "", ?? ->
|
||||
%%SubId == "", ?? ->
|
||||
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
|
||||
%{error, ?ERR_EXTENDED('bad-request', "subid-required")};
|
||||
%%InvalidSubID ->
|
||||
%%InvalidSubId ->
|
||||
%% Entity is subscribed but specifies an invalid subscription ID
|
||||
%{error, ?ERR_EXTENDED('not-acceptable', "invalid-subid")};
|
||||
Affiliation == outcast ->
|
||||
@ -752,10 +894,10 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
|
||||
GenKey = jlib:short_prepd_bare_jid(JID),
|
||||
GenState = get_state(NodeId, GenKey),
|
||||
Affiliation = GenState#pubsub_state.affiliation,
|
||||
Subscription = GenState#pubsub_state.subscription,
|
||||
Whitelisted = can_fetch_item(Affiliation, Subscription),
|
||||
Subscriptions = GenState#pubsub_state.subscriptions,
|
||||
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
|
||||
if
|
||||
%%SubID == "", ?? ->
|
||||
%%SubId == "", ?? ->
|
||||
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
|
||||
%{error, ?ERR_EXTENDED('bad-request', "subid-required")};
|
||||
%%InvalidSubID ->
|
||||
@ -816,7 +958,19 @@ can_fetch_item(owner, _) -> true;
|
||||
can_fetch_item(member, _) -> true;
|
||||
can_fetch_item(publisher, _) -> true;
|
||||
can_fetch_item(outcast, _) -> false;
|
||||
can_fetch_item(none, subscribed) -> true;
|
||||
can_fetch_item(none, none) -> 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).
|
||||
|
||||
%% Returns the first item where Pred() is true in List
|
||||
first_in_list(_Pred, []) ->
|
||||
false;
|
||||
first_in_list(Pred, [H | T]) ->
|
||||
case Pred(H) of
|
||||
true -> {value, H};
|
||||
_ -> first_in_list(Pred, T)
|
||||
end.
|
||||
|
@ -50,7 +50,7 @@
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/7,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
@ -61,8 +61,9 @@
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_node_subscriptions/1,
|
||||
get_subscription/2,
|
||||
set_subscription/3,
|
||||
get_subscriptions/2,
|
||||
set_subscriptions/4,
|
||||
get_pending_nodes/2,
|
||||
get_states/1,
|
||||
get_state/2,
|
||||
set_state/1,
|
||||
@ -82,8 +83,7 @@ terminate(Host, ServerHost) ->
|
||||
ok.
|
||||
|
||||
options() ->
|
||||
[{node_type, pep},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, false},
|
||||
@ -127,10 +127,10 @@ delete_node(Removed) ->
|
||||
node_pep:delete_node(Removed).
|
||||
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup) ->
|
||||
SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
node_pep:subscribe_node(
|
||||
NodeId, Sender, Subscriber, AccessModel, SendLast,
|
||||
PresenceSubscription, RosterGroup).
|
||||
PresenceSubscription, RosterGroup, Options).
|
||||
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
|
||||
node_pep:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
|
||||
@ -165,11 +165,14 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
get_node_subscriptions(NodeId) ->
|
||||
node_pep:get_node_subscriptions(NodeId).
|
||||
|
||||
get_subscription(NodeId, Owner) ->
|
||||
node_pep:get_subscription(NodeId, Owner).
|
||||
get_subscriptions(NodeId, Owner) ->
|
||||
node_pep:get_subscriptions(NodeId, Owner).
|
||||
|
||||
set_subscription(NodeId, Owner, Subscription) ->
|
||||
node_pep:set_subscription(NodeId, Owner, Subscription).
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
node_pep:set_subscriptions(NodeId, Owner, Subscription, SubId).
|
||||
|
||||
get_pending_nodes(Host, Owner) ->
|
||||
node_hometree:get_pending_nodes(Host, Owner).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_pep:get_states(NodeId).
|
||||
|
@ -43,7 +43,7 @@
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/7,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
@ -54,8 +54,9 @@
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_node_subscriptions/1,
|
||||
get_subscription/2,
|
||||
set_subscription/3,
|
||||
get_subscriptions/2,
|
||||
set_subscriptions/4,
|
||||
get_pending_nodes/2,
|
||||
get_states/1,
|
||||
get_state/2,
|
||||
set_state/1,
|
||||
@ -77,8 +78,7 @@ terminate(Host, ServerHost) ->
|
||||
ok.
|
||||
|
||||
options() ->
|
||||
[{node_type, pep},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, false},
|
||||
@ -146,10 +146,10 @@ delete_node(Removed) ->
|
||||
end.
|
||||
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup) ->
|
||||
SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
node_hometree:subscribe_node(
|
||||
NodeId, Sender, Subscriber, AccessModel, SendLast,
|
||||
PresenceSubscription, RosterGroup).
|
||||
PresenceSubscription, RosterGroup, Options).
|
||||
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
|
||||
case node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of
|
||||
@ -176,7 +176,7 @@ get_entity_affiliations(_Host, Owner) ->
|
||||
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
|
||||
NodeTree = case ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
|
||||
[{nodetree, N}] -> N;
|
||||
_ -> nodetree_default
|
||||
_ -> nodetree_tree
|
||||
end,
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
|
||||
case NodeTree:get_node(N) of
|
||||
@ -208,11 +208,18 @@ get_entity_subscriptions(_Host, Owner) ->
|
||||
end,
|
||||
NodeTree = case ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
|
||||
[{nodetree, N}] -> N;
|
||||
_ -> nodetree_default
|
||||
_ -> nodetree_tree
|
||||
end,
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscription = S}, Acc) ->
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
|
||||
case NodeTree:get_node(N) of
|
||||
#pubsub_node{nodeid = {{_, D, _}, _}} = Node -> [{Node, S, J}|Acc];
|
||||
#pubsub_node{nodeid = {{_, D, _}, _}} = Node ->
|
||||
lists:foldl(fun({subscribed, SubID}, Acc2) ->
|
||||
[{Node, subscribed, SubID, J} | Acc2];
|
||||
({pending, _SubID}, Acc2) ->
|
||||
[{Node, pending, J} | Acc2];
|
||||
(S, Acc2) ->
|
||||
[{Node, S, J} | Acc2]
|
||||
end, Acc, Ss);
|
||||
_ -> Acc
|
||||
end
|
||||
end, [], States),
|
||||
@ -226,11 +233,14 @@ get_node_subscriptions(NodeId) ->
|
||||
%% DO NOT REMOVE
|
||||
node_hometree:get_node_subscriptions(NodeId).
|
||||
|
||||
get_subscription(NodeId, Owner) ->
|
||||
node_hometree:get_subscription(NodeId, Owner).
|
||||
get_subscriptions(NodeId, Owner) ->
|
||||
node_hometree:get_subscriptions(NodeId, Owner).
|
||||
|
||||
set_subscription(NodeId, Owner, Subscription) ->
|
||||
node_hometree:set_subscription(NodeId, Owner, Subscription).
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
|
||||
|
||||
get_pending_nodes(Host, Owner) ->
|
||||
node_hometree:get_pending_nodes(Host, Owner).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_hometree:get_states(NodeId).
|
||||
@ -279,4 +289,3 @@ complain_if_modcaps_disabled(ServerHost) ->
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/7,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
@ -58,8 +58,9 @@
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_node_subscriptions/1,
|
||||
get_subscription/2,
|
||||
set_subscription/3,
|
||||
get_subscriptions/2,
|
||||
set_subscriptions/4,
|
||||
get_pending_nodes/2,
|
||||
get_states/1,
|
||||
get_state/2,
|
||||
set_state/1,
|
||||
@ -79,8 +80,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, private},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
@ -123,9 +123,10 @@ delete_node(Removed) ->
|
||||
node_hometree:delete_node(Removed).
|
||||
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast,
|
||||
PresenceSubscription, RosterGroup) ->
|
||||
PresenceSubscription, RosterGroup, Options) ->
|
||||
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup).
|
||||
SendLast, PresenceSubscription, RosterGroup,
|
||||
Options).
|
||||
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
|
||||
node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
|
||||
@ -160,11 +161,14 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
get_node_subscriptions(NodeId) ->
|
||||
node_hometree:get_node_subscriptions(NodeId).
|
||||
|
||||
get_subscription(NodeId, Owner) ->
|
||||
node_hometree:get_subscription(NodeId, Owner).
|
||||
get_subscriptions(NodeId, Owner) ->
|
||||
node_hometree:get_subscriptions(NodeId, Owner).
|
||||
|
||||
set_subscription(NodeId, Owner, Subscription) ->
|
||||
node_hometree:set_subscription(NodeId, Owner, Subscription).
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
|
||||
|
||||
get_pending_nodes(Host, Owner) ->
|
||||
node_hometree:get_pending_nodes(Host, Owner).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_hometree:get_states(NodeId).
|
||||
|
249
src/mod_pubsub/nodetree_dag.erl
Normal file
249
src/mod_pubsub/nodetree_dag.erl
Normal file
@ -0,0 +1,249 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% @author Brian Cully <bjc@kublai.com>
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
-module(nodetree_dag).
|
||||
-author('bjc@kublai.com').
|
||||
|
||||
%% API
|
||||
-export([init/3,
|
||||
terminate/2,
|
||||
options/0,
|
||||
set_node/1,
|
||||
get_node/3,
|
||||
get_node/2,
|
||||
get_node/1,
|
||||
get_nodes/2,
|
||||
get_nodes/1,
|
||||
get_parentnodes/3,
|
||||
get_parentnodes_tree/3,
|
||||
get_subnodes/3,
|
||||
get_subnodes_tree/3,
|
||||
create_node/5,
|
||||
delete_node/2]).
|
||||
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("pubsub.hrl").
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-behaviour(gen_pubsub_nodetree).
|
||||
|
||||
-define(DEFAULT_NODETYPE, leaf).
|
||||
-define(DEFAULT_PARENTS, []).
|
||||
-define(DEFAULT_CHILDREN, []).
|
||||
|
||||
-compile(export_all).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
init(Host, ServerHost, Opts) ->
|
||||
nodetree_tree:init(Host, ServerHost, Opts),
|
||||
mnesia:transaction(fun create_node/5,
|
||||
[Host, [], "default", service_jid(ServerHost), []]).
|
||||
|
||||
terminate(Host, ServerHost) ->
|
||||
nodetree_tree:terminate(Host, ServerHost).
|
||||
|
||||
create_node(Key, NodeID, Type, Owner, Options) ->
|
||||
OwnerJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
||||
case find_node(Key, NodeID) of
|
||||
false ->
|
||||
ID = pubsub_index:new(node),
|
||||
N = #pubsub_node{nodeid = oid(Key, NodeID),
|
||||
id = ID,
|
||||
type = Type,
|
||||
owners = [OwnerJID],
|
||||
options = Options},
|
||||
case set_node(N) of
|
||||
ok -> {ok, ID};
|
||||
Other -> Other
|
||||
end;
|
||||
_ ->
|
||||
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'conflict')}
|
||||
end.
|
||||
|
||||
set_node(#pubsub_node{nodeid = {Key, _},
|
||||
owners = Owners,
|
||||
options = Options} = Node) ->
|
||||
Parents = find_opt(collection, ?DEFAULT_PARENTS, Options),
|
||||
case validate_parentage(Key, Owners, Parents) of
|
||||
true ->
|
||||
%% Update parents whenever the config changes.
|
||||
mnesia:write(Node#pubsub_node{parents = Parents});
|
||||
Other ->
|
||||
Other
|
||||
end.
|
||||
|
||||
delete_node(Key, NodeID) ->
|
||||
case find_node(Key, NodeID) of
|
||||
false ->
|
||||
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')};
|
||||
Node ->
|
||||
%% Find all of N's children, update their configs to
|
||||
%% remove N from the collection setting.
|
||||
lists:foreach(fun (#pubsub_node{options = Opts} = Child) ->
|
||||
NewOpts = remove_config_parent(NodeID, Opts),
|
||||
Parents = find_opt(collection, ?DEFAULT_PARENTS, NewOpts),
|
||||
ok = mnesia:write(pubsub_node,
|
||||
Child#pubsub_node{
|
||||
parents = Parents,
|
||||
options = NewOpts},
|
||||
write)
|
||||
end, get_subnodes(Key, NodeID)),
|
||||
|
||||
%% Remove and return the requested node.
|
||||
pubsub_index:free(node, Node#pubsub_node.id),
|
||||
mnesia:delete_object(pubsub_node, Node, write),
|
||||
[Node]
|
||||
end.
|
||||
|
||||
options() ->
|
||||
nodetree_tree:options().
|
||||
|
||||
get_node(Host, NodeID, _From) ->
|
||||
get_node(Host, NodeID).
|
||||
|
||||
get_node(Host, NodeID) ->
|
||||
case find_node(Host, NodeID) of
|
||||
false -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')};
|
||||
Node -> Node
|
||||
end.
|
||||
|
||||
get_node(NodeId) ->
|
||||
nodetree_tree:get_node(NodeId).
|
||||
|
||||
get_nodes(Key, From) ->
|
||||
nodetree_tree:get_nodes(Key, From).
|
||||
|
||||
get_nodes(Key) ->
|
||||
nodetree_tree:get_nodes(Key).
|
||||
|
||||
get_parentnodes(Host, NodeID, _From) ->
|
||||
case find_node(Host, NodeID) of
|
||||
false -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')};
|
||||
#pubsub_node{parents = Parents} ->
|
||||
Q = qlc:q([N || #pubsub_node{nodeid = {NHost, NNode}} = N <- mnesia:table(pubsub_node),
|
||||
Parent <- Parents,
|
||||
Host == NHost,
|
||||
Parent == NNode]),
|
||||
qlc:e(Q)
|
||||
end.
|
||||
|
||||
get_parentnodes_tree(Host, NodeID, _From) ->
|
||||
Pred = fun (NID, #pubsub_node{nodeid = {_, NNodeID}}) ->
|
||||
NID == NNodeID
|
||||
end,
|
||||
Tr = fun (#pubsub_node{parents = Parents}) -> Parents end,
|
||||
traversal_helper(Pred, Tr, Host, [NodeID]).
|
||||
|
||||
get_subnodes(Host, NodeID, _From) ->
|
||||
get_subnodes(Host, NodeID).
|
||||
|
||||
get_subnodes(Host, NodeID) ->
|
||||
case find_node(Host, NodeID) of
|
||||
false -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')};
|
||||
_ ->
|
||||
Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _},
|
||||
parents = Parents} = Node <- mnesia:table(pubsub_node),
|
||||
Host == NHost,
|
||||
lists:member(NodeID, Parents)]),
|
||||
qlc:e(Q)
|
||||
end.
|
||||
|
||||
get_subnodes_tree(Host, NodeID, From) ->
|
||||
Pred = fun (NID, #pubsub_node{parents = Parents}) ->
|
||||
lists:member(NID, Parents)
|
||||
end,
|
||||
Tr = fun (#pubsub_node{nodeid = {_, N}}) -> [N] end,
|
||||
traversal_helper(Pred, Tr, 1, Host, [NodeID],
|
||||
[{0, [get_node(Host, NodeID, From)]}]).
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
oid(Key, Name) -> {Key, Name}.
|
||||
|
||||
%% Key = jlib:jid() | host()
|
||||
%% NodeID = string()
|
||||
find_node(Key, NodeID) ->
|
||||
case mnesia:read(pubsub_node, oid(Key, NodeID), read) of
|
||||
[] -> false;
|
||||
[Node] -> Node
|
||||
end.
|
||||
|
||||
%% Key = jlib:jid() | host()
|
||||
%% Default = term()
|
||||
%% Options = [{Key = atom(), Value = term()}]
|
||||
find_opt(Key, Default, Options) ->
|
||||
case lists:keysearch(Key, 1, Options) of
|
||||
{value, {Key, Val}} -> Val;
|
||||
_ -> Default
|
||||
end.
|
||||
|
||||
traversal_helper(Pred, Tr, Host, NodeIDs) ->
|
||||
traversal_helper(Pred, Tr, 0, Host, NodeIDs, []).
|
||||
|
||||
traversal_helper(_Pred, _Tr, _Depth, _Host, [], Acc) ->
|
||||
Acc;
|
||||
traversal_helper(Pred, Tr, Depth, Host, NodeIDs, Acc) ->
|
||||
Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _}} = Node <- mnesia:table(pubsub_node),
|
||||
NodeID <- NodeIDs,
|
||||
Host == NHost,
|
||||
Pred(NodeID, Node)]),
|
||||
Nodes = qlc:e(Q),
|
||||
IDs = lists:flatmap(Tr, Nodes),
|
||||
traversal_helper(Pred, Tr, Depth + 1, Host, IDs, [{Depth, Nodes} | Acc]).
|
||||
|
||||
remove_config_parent(NodeID, Options) ->
|
||||
remove_config_parent(NodeID, Options, []).
|
||||
|
||||
remove_config_parent(_NodeID, [], Acc) ->
|
||||
lists:reverse(Acc);
|
||||
remove_config_parent(NodeID, [{collection, Parents} | T], Acc) ->
|
||||
remove_config_parent(NodeID, T,
|
||||
[{collection, lists:delete(NodeID, Parents)} | Acc]);
|
||||
remove_config_parent(NodeID, [H | T], Acc) ->
|
||||
remove_config_parent(NodeID, T, [H | Acc]).
|
||||
|
||||
validate_parentage(_Key, _Owners, []) ->
|
||||
true;
|
||||
validate_parentage(Key, Owners, [[] | T]) ->
|
||||
validate_parentage(Key, Owners, T);
|
||||
validate_parentage(Key, Owners, [ParentID | T]) ->
|
||||
case find_node(Key, ParentID) of
|
||||
false -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item_not_found')};
|
||||
#pubsub_node{owners = POwners, options = POptions} ->
|
||||
NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
|
||||
MutualOwners = [O || O <- Owners, PO <- POwners,
|
||||
O == PO],
|
||||
case {MutualOwners, NodeType} of
|
||||
{[], _} -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')};
|
||||
{_, collection} -> validate_parentage(Key, Owners, T);
|
||||
{_, _} -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-allowed')}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (Host) -> jid()
|
||||
%% Host = host()
|
||||
%% @doc <p>Generate pubsub service JID.</p>
|
||||
service_jid(Host) ->
|
||||
case Host of
|
||||
{U,S,_} -> exmpp_jid:make(U, S);
|
||||
_ -> exmpp_jid:make(Host)
|
||||
end.
|
@ -36,6 +36,7 @@
|
||||
-module(nodetree_tree).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
@ -51,6 +52,8 @@
|
||||
get_node/1,
|
||||
get_nodes/2,
|
||||
get_nodes/1,
|
||||
get_parentnodes/3,
|
||||
get_parentnodes_tree/3,
|
||||
get_subnodes/3,
|
||||
get_subnodes_tree/3,
|
||||
create_node/5,
|
||||
@ -125,6 +128,29 @@ get_nodes(Host, _From) ->
|
||||
get_nodes(Host) ->
|
||||
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}).
|
||||
|
||||
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% From = mod_pubsub:jid()
|
||||
%% Depth = integer()
|
||||
%% Record = pubsubNode()
|
||||
%% @doc <p>Default node tree does not handle parents, return empty list.</p>
|
||||
get_parentnodes(_Host, _Node, _From) ->
|
||||
[].
|
||||
|
||||
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% From = mod_pubsub:jid()
|
||||
%% Depth = integer()
|
||||
%% Record = pubsubNode()
|
||||
%% @doc <p>Default node tree does not handle parents, return a list
|
||||
%% containing just this node.</p>
|
||||
get_parentnodes_tree(Host, Node, From) ->
|
||||
case get_node(Host, Node, From) of
|
||||
N when is_record(N, pubsub_node) -> [{0, [N]}];
|
||||
Error -> Error
|
||||
end.
|
||||
%% @spec (Host, Node, From) -> [pubsubNode()] | {error, Reason}
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
@ -132,9 +158,13 @@ get_nodes(Host) ->
|
||||
get_subnodes(Host, Node, _From) ->
|
||||
get_subnodes(Host, Node).
|
||||
get_subnodes(Host, Node) ->
|
||||
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, parent = Node, _ = '_'}).
|
||||
Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _},
|
||||
parents = Parents} = N <- mnesia:table(pubsub_node),
|
||||
Host == NHost,
|
||||
lists:member(Node, Parents)]),
|
||||
qlc:e(Q).
|
||||
|
||||
%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
|
||||
%% @spec (Host, Index) -> [pubsubNodeIdx()] | {error, Reason}
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% From = mod_pubsub:jid()
|
||||
@ -172,7 +202,7 @@ create_node(Host, Node, Type, Owner, Options) ->
|
||||
_ ->
|
||||
case mnesia:read({pubsub_node, {Host, Parent}}) of
|
||||
[] -> {Parent, false};
|
||||
_ -> {Parent, true}
|
||||
_ -> {Parent, lists:member(BJID, Parent#pubsub_node.owners)}
|
||||
end
|
||||
end
|
||||
end,
|
||||
@ -181,7 +211,7 @@ create_node(Host, Node, Type, Owner, Options) ->
|
||||
NodeId = pubsub_index:new(node),
|
||||
mnesia:write(#pubsub_node{nodeid = {Host, Node},
|
||||
id = NodeId,
|
||||
parent = ParentNode,
|
||||
parents = [ParentNode],
|
||||
type = Type,
|
||||
owners = [BJID],
|
||||
options = Options}),
|
||||
|
@ -91,7 +91,7 @@
|
||||
%%% <p>The <tt>parentid</tt> and <tt>type</tt> fields are indexed.</p>
|
||||
-record(pubsub_node, {nodeid,
|
||||
id,
|
||||
parent,
|
||||
parents = [],
|
||||
type = "flat",
|
||||
owners = [],
|
||||
options = []
|
||||
@ -101,13 +101,13 @@
|
||||
%%% stateid = {ljid(), nodeidx()}},
|
||||
%%% items = [ItemId::string()],
|
||||
%%% affiliation = affiliation(),
|
||||
%%% subscription = subscription()}.
|
||||
%%% subscriptions = [subscription()]}.
|
||||
%%% <p>This is the format of the <tt>affiliations</tt> table. The type of the
|
||||
%%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
|
||||
-record(pubsub_state, {stateid,
|
||||
items = [],
|
||||
affiliation = none,
|
||||
subscription = none
|
||||
subscriptions = none
|
||||
}).
|
||||
|
||||
%%% @type pubsubItem() = #pubsub_item{
|
||||
@ -123,3 +123,11 @@
|
||||
payload = []
|
||||
}).
|
||||
|
||||
%% @type pubsubSubscription() = #pubsub_subscription{
|
||||
%% subid = string(),
|
||||
%% state_key = {ljid(), pubsubNodeId()},
|
||||
%% options = [{atom(), term()}]
|
||||
%% }.
|
||||
%% <p>This is the format of the <tt>subscriptions</tt> table. The type of the
|
||||
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
|
||||
-record(pubsub_subscription, {subid, options}).
|
||||
|
333
src/mod_pubsub/pubsub_subscription.erl
Normal file
333
src/mod_pubsub/pubsub_subscription.erl
Normal file
@ -0,0 +1,333 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% The Initial Developer of the Original Code is ProcessOne.
|
||||
%%% Portions created by ProcessOne are Copyright 2006-2009, ProcessOne
|
||||
%%% All Rights Reserved.''
|
||||
%%% This software is copyright 2006-2009, ProcessOne.
|
||||
%%%
|
||||
%%% @author Brian Cully <bjc@kublai.com>
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
-module(pubsub_subscription).
|
||||
-author("bjc@kublai.com").
|
||||
|
||||
%% API
|
||||
-export([init/0,
|
||||
subscribe_node/3,
|
||||
unsubscribe_node/3,
|
||||
get_subscription/3,
|
||||
set_subscription/4,
|
||||
get_options_xform/2,
|
||||
parse_options_xform/1]).
|
||||
|
||||
% Internal function also exported for use in transactional bloc from pubsub plugins
|
||||
-export([add_subscription/3,
|
||||
delete_subscription/3,
|
||||
read_subscription/3,
|
||||
write_subscription/4]).
|
||||
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-define(PUBSUB_DELIVER, "pubsub#deliver").
|
||||
-define(PUBSUB_DIGEST, "pubsub#digest").
|
||||
-define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency").
|
||||
-define(PUBSUB_EXPIRE, "pubsub#expire").
|
||||
-define(PUBSUB_INCLUDE_BODY, "pubsub#include_body").
|
||||
-define(PUBSUB_SHOW_VALUES, "pubsub#show-values").
|
||||
-define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type").
|
||||
-define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth").
|
||||
|
||||
-define(DELIVER_LABEL,
|
||||
"Whether an entity wants to receive or disable notifications").
|
||||
-define(DIGEST_LABEL,
|
||||
"Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually").
|
||||
-define(DIGEST_FREQUENCY_LABEL,
|
||||
"The minimum number of milliseconds between sending any two notification digests").
|
||||
-define(EXPIRE_LABEL,
|
||||
"The DateTime at which a leased subscription will end or has ended").
|
||||
-define(INCLUDE_BODY_LABEL,
|
||||
"Whether an entity wants to receive an XMPP message body in addition to the payload format").
|
||||
-define(SHOW_VALUES_LABEL,
|
||||
"The presence states for which an entity wants to receive notifications").
|
||||
-define(SUBSCRIPTION_TYPE_LABEL,
|
||||
"Type of notification to receive").
|
||||
-define(SUBSCRIPTION_DEPTH_LABEL,
|
||||
"Depth from subscription for which to receive notifications").
|
||||
|
||||
-define(SHOW_VALUE_AWAY_LABEL, "XMPP Show Value of Away").
|
||||
-define(SHOW_VALUE_CHAT_LABEL, "XMPP Show Value of Chat").
|
||||
-define(SHOW_VALUE_DND_LABEL, "XMPP Show Value of DND (Do Not Disturb)").
|
||||
-define(SHOW_VALUE_ONLINE_LABEL, "Mere Availability in XMPP (No Show Value)").
|
||||
-define(SHOW_VALUE_XA_LABEL, "XMPP Show Value of XA (Extended Away)").
|
||||
|
||||
-define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL,
|
||||
"Receive notification of new items only").
|
||||
-define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL,
|
||||
"Receive notification of new nodes only").
|
||||
|
||||
-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL,
|
||||
"Receive notification from direct child nodes only").
|
||||
-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL,
|
||||
"Receive notification from all descendent nodes").
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
init() ->
|
||||
ok = create_table().
|
||||
|
||||
subscribe_node(JID, NodeID, Options) ->
|
||||
try mnesia:sync_dirty(fun add_subscription/3,
|
||||
[JID, NodeID, Options]) of
|
||||
Result -> {result, Result}
|
||||
catch
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
unsubscribe_node(JID, NodeID, SubID) ->
|
||||
try mnesia:sync_dirty(fun delete_subscription/3,
|
||||
[JID, NodeID, SubID]) of
|
||||
Result -> {result, Result}
|
||||
catch
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
get_subscription(JID, NodeID, SubID) ->
|
||||
try mnesia:sync_dirty(fun read_subscription/3,
|
||||
[JID, NodeID, SubID]) of
|
||||
Result -> {result, Result}
|
||||
catch
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
set_subscription(JID, NodeID, SubID, Options) ->
|
||||
try mnesia:sync_dirty(fun write_subscription/4,
|
||||
[JID, NodeID, SubID, Options]) of
|
||||
Result -> {result, Result}
|
||||
catch
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
get_options_xform(Lang, Options) ->
|
||||
Keys = [deliver, show_values, subscription_type, subscription_depth],
|
||||
XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
|
||||
|
||||
{result, #xmlel{ns = ?NS_DATA_FORMS, name = 'x', children =
|
||||
[#xmlel{ns = ?NS_DATA_FORMS,
|
||||
name = 'field',
|
||||
attrs = [?XMLATTR('var', <<"FORM_TYPE">>), ?XMLATTR('type', <<"hidden">>)],
|
||||
children = [#xmlel{ns = ?NS_DATA_FORMS,
|
||||
name = 'value',
|
||||
children = [?XMLCDATA(?NS_PUBSUB_SUBSCRIBE_OPTIONS_s)]}]}] ++ XFields}}.
|
||||
|
||||
parse_options_xform(XFields) ->
|
||||
case exmpp_xml:get_child_elements(XFields) of
|
||||
[] -> {result, []};
|
||||
[#xmlel{name = 'x'} = XEl] ->
|
||||
case jlib:parse_xdata_submit(XEl) of
|
||||
XData when is_list(XData) ->
|
||||
case set_xoption(XData, []) of
|
||||
Opts when is_list(Opts) -> {result, Opts};
|
||||
Other -> Other
|
||||
end;
|
||||
Other ->
|
||||
Other
|
||||
end;
|
||||
Other ->
|
||||
Other
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
create_table() ->
|
||||
case mnesia:create_table(pubsub_subscription,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, pubsub_subscription)},
|
||||
{type, set}]) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, {already_exists, _}} -> ok;
|
||||
Other -> Other
|
||||
end.
|
||||
|
||||
add_subscription(_JID, _NodeID, Options) ->
|
||||
SubID = make_subid(),
|
||||
Record = #pubsub_subscription{subid = SubID, options = Options},
|
||||
mnesia:write(Record),
|
||||
SubID.
|
||||
|
||||
delete_subscription(JID, NodeID, SubID) ->
|
||||
Sub = read_subscription(JID, NodeID, SubID),
|
||||
mnesia:delete({pubsub_subscription, SubID}),
|
||||
Sub.
|
||||
|
||||
read_subscription(_JID, _NodeID, SubID) ->
|
||||
Q = qlc:q([Sub || Sub <- mnesia:table(pubsub_subscription),
|
||||
Sub#pubsub_subscription.subid == SubID]),
|
||||
case qlc:e(Q) of
|
||||
[Sub] -> Sub;
|
||||
[] -> mnesia:abort({error, notfound})
|
||||
end.
|
||||
|
||||
write_subscription(JID, NodeID, SubID, Options) ->
|
||||
Sub = read_subscription(JID, NodeID, SubID),
|
||||
mnesia:write(Sub#pubsub_subscription{options = Options}).
|
||||
|
||||
make_subid() ->
|
||||
{T1, T2, T3} = now(),
|
||||
lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
|
||||
|
||||
%%
|
||||
%% Subscription XForm processing.
|
||||
%%
|
||||
|
||||
%% Return processed options, with types converted and so forth, using
|
||||
%% Opts as defaults.
|
||||
set_xoption([], Opts) ->
|
||||
Opts;
|
||||
set_xoption([{Var, Value} | T], Opts) ->
|
||||
NewOpts = case var_xfield(Var) of
|
||||
{error, _} ->
|
||||
Opts;
|
||||
Key ->
|
||||
Val = val_xfield(Key, Value),
|
||||
lists:keystore(Key, 1, Opts, {Key, Val})
|
||||
end,
|
||||
set_xoption(T, NewOpts).
|
||||
|
||||
%% Return the options list's key for an XForm var.
|
||||
var_xfield(?PUBSUB_DELIVER) -> deliver;
|
||||
var_xfield(?PUBSUB_DIGEST) -> digest;
|
||||
var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency;
|
||||
var_xfield(?PUBSUB_EXPIRE) -> expire;
|
||||
var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body;
|
||||
var_xfield(?PUBSUB_SHOW_VALUES) -> show_values;
|
||||
var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
|
||||
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
|
||||
var_xfield(_) -> {error, badarg}.
|
||||
|
||||
%% Convert Values for option list's Key.
|
||||
val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
|
||||
val_xfield(digest, [Val]) -> xopt_to_bool(Val);
|
||||
val_xfield(digest_frequency, [Val]) -> list_to_integer(Val);
|
||||
val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
|
||||
val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
|
||||
val_xfield(show_values, Vals) -> Vals;
|
||||
val_xfield(subscription_type, ["items"]) -> items;
|
||||
val_xfield(subscription_type, ["nodes"]) -> nodes;
|
||||
val_xfield(subscription_depth, ["all"]) -> all;
|
||||
val_xfield(subscription_depth, [Depth]) ->
|
||||
case catch list_to_integer(Depth) of
|
||||
N when is_integer(N) -> N;
|
||||
_ -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-acceptable')}
|
||||
end.
|
||||
|
||||
%% Convert XForm booleans to Erlang booleans.
|
||||
xopt_to_bool("0") -> false;
|
||||
xopt_to_bool("1") -> true;
|
||||
xopt_to_bool("false") -> false;
|
||||
xopt_to_bool("true") -> true;
|
||||
xopt_to_bool(_) -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-acceptable')}.
|
||||
|
||||
%% Return a field for an XForm for Key, with data filled in, if
|
||||
%% applicable, from Options.
|
||||
get_option_xfield(Lang, Key, Options) ->
|
||||
Var = xfield_var(Key),
|
||||
Label = xfield_label(Key),
|
||||
{Type, OptEls} = type_and_options(xfield_type(Key), Lang),
|
||||
Vals = case lists:keysearch(Key, 1, Options) of
|
||||
{value, {_, Val}} ->
|
||||
[tr_xfield_values(Vals) || Vals <- xfield_val(Key, Val)];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
#xmlel{ns = ?NS_DATA_FORMS,
|
||||
name = 'field',
|
||||
attrs = [?XMLATTR('var', Var), ?XMLATTR('type', Type), ?XMLATTR('label', translate:translate(Lang, Label))],
|
||||
children = OptEls ++ Vals}.
|
||||
|
||||
type_and_options({Type, Options}, Lang) ->
|
||||
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
|
||||
type_and_options(Type, _Lang) ->
|
||||
{Type, []}.
|
||||
|
||||
tr_xfield_options({Value, Label}, Lang) ->
|
||||
#xmlel{ns = ?NS_DATA_FORMS,
|
||||
name = 'option',
|
||||
attrs = [?XMLATTR('label', transalte:translate(Lang, Label))],
|
||||
children = [#xmlel{ns = ?NS_DATA_FORMS,
|
||||
name = 'value',
|
||||
children = [?XMLCDATA(Value)]}]}.
|
||||
|
||||
tr_xfield_values(Value) ->
|
||||
#xmlel{ns = ?NS_DATA_FORMS, name ='value', children = [?XMLCDATA(Value)]}.
|
||||
|
||||
%% Return the XForm variable name for a subscription option key.
|
||||
xfield_var(deliver) -> ?PUBSUB_DELIVER;
|
||||
xfield_var(digest) -> ?PUBSUB_DIGEST;
|
||||
xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY;
|
||||
xfield_var(expire) -> ?PUBSUB_EXPIRE;
|
||||
xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
|
||||
xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
|
||||
xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
|
||||
xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
|
||||
|
||||
%% Return the XForm variable type for a subscription option key.
|
||||
xfield_type(deliver) -> "boolean";
|
||||
xfield_type(digest) -> "boolean";
|
||||
xfield_type(digest_frequency) -> "text-single";
|
||||
xfield_type(expire) -> "text-single";
|
||||
xfield_type(include_body) -> "boolean";
|
||||
xfield_type(show_values) ->
|
||||
{"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL},
|
||||
{"chat", ?SHOW_VALUE_CHAT_LABEL},
|
||||
{"dnd", ?SHOW_VALUE_DND_LABEL},
|
||||
{"online", ?SHOW_VALUE_ONLINE_LABEL},
|
||||
{"xa", ?SHOW_VALUE_XA_LABEL}]};
|
||||
xfield_type(subscription_type) ->
|
||||
{"list-single", [{"items", ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
|
||||
{"nodes", ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
|
||||
xfield_type(subscription_depth) ->
|
||||
{"list-single", [{"1", ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
|
||||
{"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
|
||||
|
||||
%% Return the XForm variable label for a subscription option key.
|
||||
xfield_label(deliver) -> ?DELIVER_LABEL;
|
||||
xfield_label(digest) -> ?DIGEST_LABEL;
|
||||
xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL;
|
||||
xfield_label(expire) -> ?EXPIRE_LABEL;
|
||||
xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
|
||||
xfield_label(show_values) -> ?SHOW_VALUES_LABEL;
|
||||
xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL;
|
||||
xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
|
||||
|
||||
%% Return the XForm value for a subscription option key.
|
||||
xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
|
||||
xfield_val(digest, Val) -> [bool_to_xopt(Val)];
|
||||
xfield_val(digest_frequency, Val) -> [integer_to_list(Val)];
|
||||
xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)];
|
||||
xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
|
||||
xfield_val(show_values, Val) -> Val;
|
||||
xfield_val(subscription_type, items) -> ["items"];
|
||||
xfield_val(subscription_type, nodes) -> ["nodes"];
|
||||
xfield_val(subscription_depth, all) -> ["all"];
|
||||
xfield_val(subscription_depth, N) -> [integer_to_list(N)].
|
||||
|
||||
%% Convert erlang booleans to XForms.
|
||||
bool_to_xopt(false) -> "false";
|
||||
bool_to_xopt(true) -> "true".
|
Loading…
Reference in New Issue
Block a user