aboutsummaryrefslogtreecommitdiff
path: root/src/mod_pubsub/node_default.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_pubsub/node_default.erl')
-rw-r--r--src/mod_pubsub/node_default.erl712
1 files changed, 712 insertions, 0 deletions
diff --git a/src/mod_pubsub/node_default.erl b/src/mod_pubsub/node_default.erl
new file mode 100644
index 000000000..02a6983f1
--- /dev/null
+++ b/src/mod_pubsub/node_default.erl
@@ -0,0 +1,712 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%% [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @todo The item table should be handled by the plugin, but plugin that do
+%%% not want to manage it should be able to use the default behaviour.
+%%% @todo Plugin modules should be able to register to receive presence update
+%%% send to pubsub.
+
+%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
+%%% <p>It is used as a default for all unknown PubSub node type. It can serve
+%%% as a developer basis and reference to build its own custom pubsub node
+%%% types.</p>
+%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
+%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
+%%% development is still a work in progress. However, the system is already
+%%% useable and useful as is. Please, send us comments, feedback and
+%%% improvements.</p>
+
+-module(node_default).
+-author('christophe.romain@process-one.net').
+
+-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/3,
+ delete_node/2,
+ purge_node/3,
+ subscribe_node/8,
+ unsubscribe_node/5,
+ publish_item/7,
+ delete_item/4,
+ remove_extra_items/4,
+ get_entity_affiliations/2,
+ get_node_affiliations/2,
+ get_affiliation/3,
+ set_affiliation/4,
+ get_entity_subscriptions/2,
+ get_node_subscriptions/2,
+ get_subscription/3,
+ set_subscription/4,
+ get_states/2,
+ get_state/3,
+ set_state/1,
+ get_items/2,
+ get_item/3,
+ set_item/1
+ ]).
+
+%% ================
+%% API definition
+%% ================
+
+%% @spec (Host) -> any()
+%% Host = mod_pubsub:host()
+%% ServerHost = host()
+%% Opts = list()
+%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
+%% implement this function. It can return anything.</p>
+%% <p>This function is mainly used to trigger the setup task necessary for the
+%% 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) ->
+ 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)}]),
+ ItemsFields = record_info(fields, pubsub_item),
+ case mnesia:table_info(pubsub_item, attributes) of
+ ItemsFields -> ok;
+ _ ->
+ mnesia:transform_table(pubsub_item, ignore, ItemsFields)
+ end,
+ ok.
+
+%% @spec (Host) -> any()
+%% Host = mod_pubsub:host()
+%% @doc <p>Called during pubsub modules termination. Any pubsub plugin must
+%% implement this function. It can return anything.</p>
+terminate(_Host, _ServerHost) ->
+ ok.
+
+%% @spec () -> [Option]
+%% Option = mod_pubsub:nodeOption()
+%% @doc Returns the default pubsub node options.
+%% <p>Example of function return value:</p>
+%% ```
+%% [{deliver_payloads, true},
+%% {notify_config, false},
+%% {notify_delete, false},
+%% {notify_retract, true},
+%% {persist_items, true},
+%% {max_items, 10},
+%% {subscribe, true},
+%% {access_model, open},
+%% {publish_model, publishers},
+%% {max_payload_size, 100000},
+%% {send_last_published_item, never},
+%% {presence_based_delivery, false}]'''
+options() ->
+ [{node_type, default},
+ {deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {persist_items, true},
+ {max_items, ?MAXITEMS div 2},
+ {subscribe, true},
+ {access_model, open},
+ {access_roster_groups, []},
+ {publish_model, publishers},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, never},
+ {deliver_notifications, true},
+ {presence_based_delivery, false}].
+
+%% @spec () -> []
+%% @doc Returns the node features
+features() ->
+ ["create-nodes",
+ "auto-create",
+ "delete-nodes",
+ "instant-nodes",
+ "item-ids",
+ "manage-subscriptions",
+ "outcast-affiliation",
+ "persistent-items",
+ "publish",
+ "purge-nodes",
+ "retract-items",
+ "retrieve-affiliations",
+ "retrieve-items",
+ "retrieve-subscriptions",
+ "subscribe",
+ "subscription-notifications"
+ ].
+
+%% @spec (Host, Node, Owner, Access) -> bool()
+%% Host = mod_pubsub:host()
+%% ServerHost = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% Owner = mod_pubsub:jid()
+%% Access = all | atom()
+%% @doc Checks if the current user has the permission to create the requested node
+%% <p>In {@link node_default}, the permission is decided by the place in the
+%% hierarchy where the user is creating the node. The access parameter is also
+%% checked in the default module. This parameter depends on the value of the
+%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
+%% <p>This function also check that node can be created a a children of its
+%% parent node</p>
+%% <p>PubSub plugins can redefine the PubSub node creation rights as they
+%% which. They can simply delegate this check to the {@link node_default}
+%% module by implementing this function like this:
+%% ```check_create_user_permission(Host, Node, Owner, Access) ->
+%% node_default:check_create_user_permission(Host, Node, Owner, Access).'''</p>
+create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
+ LOwner = jlib:jid_tolower(Owner),
+ {User, Server, _Resource} = LOwner,
+ Allowed = case acl:match_rule(ServerHost, Access, LOwner) of
+ allow ->
+ if Server == Host -> %% Server == ServerHost ??
+ true;
+ true ->
+ case Node of
+ ["home", Server, User | _] -> true;
+ _ -> false
+ end
+ end;
+ _ ->
+ case Owner of
+ ?PUBSUB_JID -> true;
+ _ -> false
+ end
+ end,
+ ChildOK = true, %% TODO test with ParentNode
+ {result, Allowed and ChildOK}.
+
+%% @spec (Host, Node, Owner) ->
+%% {result, Result} | exit
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% Owner = mod_pubsub:jid()
+%% @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}),
+ {result, {default, broadcast}}.
+
+
+%% @spec (Host, Removed) -> ok
+%% Host = mod_pubsub:host()
+%% Removed = [mod_pubsub:pubsubNode()]
+%% @doc <p>purge items of deleted nodes after effective deletion.</p>
+delete_node(Host, Removed) ->
+ lists:foreach(
+ 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})
+ end,
+ mnesia:match_object(
+ #pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}))
+ end, Removed),
+ {result, {default, broadcast, Removed}}.
+
+%% @spec (Host, Node, Sender, Subscriber, AccessModel, SendLast) ->
+%% {error, Reason} | {result, Result}
+%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
+%% <p>The mechanism works as follow:
+%% <ul>
+%% <li>The main PubSub module prepares the subscription and passes the
+%% result of the preparation as a record.</li>
+%% <li>This function gets the prepared record and several other parameters and
+%% can decide to:<ul>
+%% <li>reject the subscription;</li>
+%% <li>allow it as is, letting the main module perform the database
+%% persistance;</li>
+%% <li>allow it, modifying the record. The main module will store the
+%% modified record;</li>
+%% <li>allow it, but perform the needed persistance operations.</li></ul>
+%% </li></ul></p>
+%% <p>The selected behaviour depends on the return parameter:
+%% <ul>
+%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
+%% subscription will actually be performed.</li>
+%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
+%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
+%% parameter contains an error, no subscription will be performed.</li>
+%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
+%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
+%% passed in parameter <tt>SubscribeResult</tt>.</li>
+%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
+%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
+%% no further persistance operation will be performed. This case is used,
+%% when the plugin module is doing the persistance by itself or when it want
+%% to completly disable persistance.</li></ul>
+%% </p>
+%% <p>In the default plugin module, the record is unchanged.</p>
+subscribe_node(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,
+ #pubsub_state{affiliation = Affiliation,
+ subscription = Subscription} = State,
+ if
+ not Authorized ->
+ %% JIDs do not match
+ {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")};
+ Subscription == pending ->
+ %% Requesting entity has pending subscription
+ {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")};
+ Affiliation == outcast ->
+ %% Requesting entity is blocked
+ {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and (not PresenceSubscription) ->
+ %% Entity is not authorized to create a subscription (presence subscription required)
+ {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
+ (AccessModel == roster) and (not RosterGroup) ->
+ %% Entity is not authorized to create a subscription (not in roster group)
+ {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
+ (AccessModel == whitelist) -> % TODO: to be done
+ %% Node has whitelist access model
+ {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
+ (AccessModel == authorize) -> % TODO: to be done
+ %% Node has authorize access model
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ %%ForbiddenAnonymous ->
+ %% % Requesting entity is anonymous
+ %% {error, ?ERR_FORBIDDEN};
+ true ->
+ NewSubscription =
+ if
+ AccessModel == authorize ->
+ pending;
+ %%TODO Affiliation == none -> ?
+ %%NeedConfiguration ->
+ %% unconfigured
+ true ->
+ subscribed
+ end,
+ set_state(State#pubsub_state{subscription = NewSubscription}),
+ case NewSubscription of
+ subscribed ->
+ case SendLast of
+ never -> {result, {default, NewSubscription}};
+ _ -> {result, {default, NewSubscription, send_last}}
+ end;
+ _ ->
+ {result, {default, NewSubscription}}
+ end
+ end.
+
+%% @spec (Host, Node, Sender, Subscriber, SubID) ->
+%% {error, Reason} | {result, []}
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% Sender = mod_pubsub:jid()
+%% Subscriber = mod_pubsub:jid()
+%% SubID = string()
+%% 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
+ {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};
+ true ->
+ set_state(State#pubsub_state{subscription = none}),
+ {result, default}
+ end
+ end.
+
+%% @spec (Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
+%% {true, PubsubItem} | {result, Reply}
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% Publisher = mod_pubsub:jid()
+%% PublishModel = atom()
+%% MaxItems = integer()
+%% ItemId = string()
+%% Payload = term()
+%% @doc <p>Publishes the item passed as parameter.</p>
+%% <p>The mechanism works as follow:
+%% <ul>
+%% <li>The main PubSub module prepares the item to publish and passes the
+%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
+%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
+%% <li>reject the publication;</li>
+%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
+%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
+%% <li>allow it, but perform the needed persistance operations.</li></ul>
+%% </li></ul></p>
+%% <p>The selected behaviour depends on the return parameter:
+%% <ul>
+%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
+%% publication is actually performed.</li>
+%% <li><tt>true</tt>: Publication operation is allowed, based on the
+%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
+%% parameter contains an error, no subscription will actually be
+%% performed.</li>
+%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
+%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
+%% in parameter <tt>Item</tt>. The persistance will be performed by the main
+%% module.</li>
+%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
+%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
+%% no further persistance operation will be performed. This case is used,
+%% when the plugin module is doing the persistance by itself or when it want
+%% to completly disable persistance.</li></ul>
+%% </p>
+%% <p>In the default plugin module, the record is unchanged.</p>
+publish_item(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,
+ #pubsub_state{affiliation = Affiliation,
+ subscription = Subscription} = State,
+ if
+ not ((PublishModel == open)
+ or ((PublishModel == publishers)
+ and ((Affiliation == owner) or (Affiliation == publisher)))
+ or ((PublishModel == subscribers)
+ and (Subscription == subscribed))) ->
+ %% Entity does not have sufficient privileges to publish to node
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ PubId = {PublisherKey, now()},
+ Item = case get_item(Host, Node, ItemId) of
+ {error, ?ERR_ITEM_NOT_FOUND} ->
+ #pubsub_item{itemid = {ItemId, {Host, Node}},
+ creation = PubId,
+ modification = PubId,
+ payload = Payload};
+ {result, OldItem} ->
+ OldItem#pubsub_item{modification = PubId,
+ payload = Payload}
+ end,
+ Items = [ItemId | State#pubsub_state.items],
+ {result, {NI, OI}} = remove_extra_items(
+ Host, Node, MaxItems, Items),
+ set_item(Item),
+ set_state(State#pubsub_state{items = NI}),
+ {result, {default, broadcast, OI}}
+ end.
+
+%% @spec (Host, Node, MaxItems, ItemIds) -> {NewItemIds,OldItemIds}
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% MaxItems = integer() | unlimited
+%% ItemIds = [ItemId::string()]
+%% NewItemIds = [ItemId::string()]
+%% @doc <p>This function is used to remove extra items, most notably when the
+%% maximum number of items has been reached.</p>
+%% <p>This function is used internally by the core PubSub module, as no
+%% permission check is performed.</p>
+%% <p>In the default plugin module, the oldest items are removed, but other
+%% rules can be used.</p>
+%% <p>If another PubSub plugin wants to delegate the item removal (and if the
+%% plugin is using the default pubsub storage), it can implements this function like this:
+%% ```remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+%% node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).'''</p>
+remove_extra_items(_Host, _Node, unlimited, ItemIds) ->
+ {result, {ItemIds, []}};
+remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+ 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),
+ %% Return the new items list:
+ {result, {NewItems, OldItems}}.
+
+%% @spec (Host, Node, JID, ItemId) ->
+%% {error, Reason::stanzaError()} |
+%% {result, []}
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% JID = mod_pubsub:jid()
+%% ItemId = string()
+%% @doc <p>Triggers item deletion.</p>
+%% <p>Default plugin: The user performing the deletion must be the node owner
+%% or a node publisher e item 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,
+ #pubsub_state{affiliation = Affiliation, items = Items} = State,
+ Allowed = (Affiliation == publisher) orelse (Affiliation == owner)
+ orelse case get_item(Host, Node, ItemId) of
+ {result, #pubsub_item{creation = {PublisherKey, _}}} -> true;
+ _ -> false
+ end,
+ if
+ not Allowed ->
+ %% Requesting entity does not have sufficient privileges
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ case get_item(Host, Node, ItemId) of
+ {result, _} ->
+ mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}),
+ NewItems = lists:delete(ItemId, Items),
+ set_state(State#pubsub_state{items = NewItems}),
+ {result, {default, broadcast}};
+ _ ->
+ %% Non-existent node or item
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end
+ end.
+
+%% @spec (TODO)
+purge_node(Host, Node, Owner) ->
+ OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ case get_state(Host, Node, OwnerKey) of
+ {error, ?ERR_ITEM_NOT_FOUND} ->
+ %% This should not append (case node does not exists)
+ {error, ?ERR_ITEM_NOT_FOUND};
+ {result, #pubsub_state{items = Items, affiliation = owner}} ->
+ lists:foreach(fun(ItemId) ->
+ mnesia:delete({pubsub_item, {ItemId, {Host, Node}}})
+ end, Items),
+ {result, {default, broadcast}};
+ _ ->
+ %% Entity is not an owner
+ {error, ?ERR_FORBIDDEN}
+ end.
+
+%% @spec (Host, JID) -> [{Node,Affiliation}]
+%% Host = host()
+%% JID = mod_pubsub:jid()
+%% @doc <p>Return the current affiliations for the given user</p>
+%% <p>The default module reads affiliations in the main Mnesia
+%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
+%% table, it should return an empty list, as the affiliation will be read by
+%% the default PubSub module. Otherwise, it should return its own affiliation,
+%% that will be added to the affiliation stored in the main
+%% <tt>pubsub_state</tt> table.</p>
+get_entity_affiliations(Host, Owner) ->
+ OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ States = mnesia:match_object(
+ #pubsub_state{stateid = {OwnerKey, {Host, '_'}},
+ _ = '_'}),
+ Tr = fun(#pubsub_state{stateid = {_, {_, N}}, affiliation = A}) ->
+ {N, A}
+ end,
+ {result, lists:map(Tr, States)}.
+
+get_node_affiliations(Host, Node) ->
+ States = mnesia:match_object(
+ #pubsub_state{stateid = {'_', {Host, Node}},
+ _ = '_'}),
+ Tr = fun(#pubsub_state{stateid = {J, {_, _}}, affiliation = A}) ->
+ {J, A}
+ end,
+ {result, lists:map(Tr, States)}.
+
+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;
+ _ -> unknown
+ end,
+ {result, 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),
+ ok.
+
+%% @spec (Host) -> [{Node,Subscription}]
+%% Host = host()
+%% JID = mod_pubsub:jid()
+%% @doc <p>Return the current subscriptions for the given user</p>
+%% <p>The default module reads subscriptions in the main Mnesia
+%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
+%% table, it should return an empty list, as the affiliation will be read by
+%% the default PubSub module. Otherwise, it should return its own affiliation,
+%% that will be added to the affiliation stored in the main
+%% <tt>pubsub_state</tt> table.</p>
+get_entity_subscriptions(Host, Owner) ->
+ OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ States = mnesia:match_object(
+ #pubsub_state{stateid = {OwnerKey, {Host, '_'}},
+ _ = '_'}),
+ Tr = fun(#pubsub_state{stateid = {_, {_, N}}, subscription = S}) ->
+ {N, S}
+ end,
+ {result, lists:map(Tr, States)}.
+
+get_node_subscriptions(Host, Node) ->
+ States = mnesia:match_object(
+ #pubsub_state{stateid = {'_', {Host, Node}},
+ _ = '_'}),
+ Tr = fun(#pubsub_state{stateid = {J, {_, _}}, subscription = S}) ->
+ {J, S}
+ end,
+ {result, lists:map(Tr, States)}.
+
+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;
+ _ -> unknown
+ end,
+ {result, 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),
+ ok.
+
+%% @spec (Host, Node) -> [States] | []
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% Item = mod_pubsub:pubsubItems()
+%% @doc Returns the list of stored states for a given node.
+%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
+%% <p>We can consider that the pubsub_state table have been created by the main
+%% mod_pubsub module.</p>
+%% <p>PubSub plugins can store the states where they wants (for example in a
+%% relational database).</p>
+%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
+%% they can implement this function like this:
+%% ```get_states(Host, Node) ->
+%% node_default:get_states(Host, Node).'''</p>
+get_states(Host, Node) ->
+ States = mnesia:match_object(
+ #pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}),
+ {result, States}.
+
+%% @spec (JID, Host, Node) -> [State] | []
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% JID = mod_pubsub:jid()
+%% 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}
+ end.
+
+%% @spec (State) -> ok | {error, ?ERR_INTERNAL_SERVER_ERROR}
+%% State = mod_pubsub:pubsubStates()
+%% @doc <p>Write a state into database.</p>
+set_state(State) when is_record(State, pubsub_state) ->
+ mnesia:write(State);
+set_state(_) ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR}.
+
+%% @spec (Host, Node) -> [Items] | []
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% Items = mod_pubsub:pubsubItems()
+%% @doc Returns the list of stored items for a given node.
+%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
+%% <p>We can consider that the pubsub_item table have been created by the main
+%% mod_pubsub module.</p>
+%% <p>PubSub plugins can store the items where they wants (for example in a
+%% relational database), or they can even decide not to persist any items.</p>
+%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
+%% they can implement this function like this:
+%% ```get_items(Host, Node) ->
+%% node_default:get_items(Host, Node).'''</p>
+get_items(Host, Node) ->
+ Items = mnesia:match_object(
+ #pubsub_item{itemid = {'_', {Host, Node}}, _ = '_'}),
+ {result, Items}.
+
+%% @spec (Host, Node, ItemId) -> [Item] | []
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% ItemId = string()
+%% Item = mod_pubsub:pubsubItems()
+%% @doc <p>Returns an item (one item list), given its reference.</p>
+get_item(Host, Node, ItemId) ->
+ case mnesia:read({pubsub_item, {ItemId, {Host, Node}}}) of
+ [Item] when is_record(Item, pubsub_item) ->
+ {result, Item};
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end.
+
+%% @spec (Item) -> ok | {error, ?ERR_INTERNAL_SERVER_ERROR}
+%% Item = mod_pubsub:pubsubItems()
+%% @doc <p>Write a state into database.</p>
+set_item(Item) when is_record(Item, pubsub_item) ->
+ mnesia:write(Item);
+set_item(_) ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR}.