24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-26 22:35:31 +02:00

Merge branch '2.1.x' of git+ssh://gitorious.process-one.net/ejabberd/mainline into 2.1.x

This commit is contained in:
Evgeniy Khramtsov 2010-10-24 15:30:37 +10:00
commit a6858a6ce4
7 changed files with 430 additions and 316 deletions

View File

@ -819,11 +819,12 @@ use STARTTLS for s2s connections.
file containing a SSL certificate. file containing a SSL certificate.
</DD><DT CLASS="dt-description"><B><TT>{domain_certfile, Domain, Path}</TT></B></DT><DD CLASS="dd-description"> </DD><DT CLASS="dt-description"><B><TT>{domain_certfile, Domain, Path}</TT></B></DT><DD CLASS="dd-description">
Full path to the file containing the SSL certificate for a specific domain. Full path to the file containing the SSL certificate for a specific domain.
</DD><DT CLASS="dt-description"><B><TT>{outgoing_s2s_options, Methods, Timeout}</TT></B></DT><DD CLASS="dd-description"> </DD><DT CLASS="dt-description"><B><TT>{outgoing_s2s_options, [Family, ...], Timeout}</TT></B></DT><DD CLASS="dd-description">
Specify which address families to try, in what order, and connect timeout in milliseconds. Specify which address families to try, in what order, and connect timeout in milliseconds.
By default it first tries connecting with IPv4, if that fails it tries using IPv6, By default it first tries connecting with IPv4, if that fails it tries using IPv6,
with a timeout of 10000 milliseconds. with a timeout of 10000 milliseconds:
</DD><DT CLASS="dt-description"><B><TT>{s2s_dns_options, [ {Property, Value}, ...]}</TT></B></DT><DD CLASS="dd-description"> <PRE CLASS="verbatim">{outgoing_s2s_options, [ipv4, ipv6], 10000}.
</PRE></DD><DT CLASS="dt-description"><B><TT>{s2s_dns_options, [ {Property, Value}, ...]}</TT></B></DT><DD CLASS="dd-description">
Define properties to use for DNS resolving. Define properties to use for DNS resolving.
Allowed Properties are: <TT>timeout</TT> in seconds which default value is <TT>10</TT> Allowed Properties are: <TT>timeout</TT> in seconds which default value is <TT>10</TT>
and <TT>retries</TT> which default value is <TT>2</TT>. and <TT>retries</TT> which default value is <TT>2</TT>.

View File

@ -962,7 +962,7 @@ There are some additional global options that can be specified in the ejabberd c
file containing a SSL certificate. file containing a SSL certificate.
\titem{\{domain\_certfile, Domain, Path\}} \ind{options!domain\_certfile} \titem{\{domain\_certfile, Domain, Path\}} \ind{options!domain\_certfile}
Full path to the file containing the SSL certificate for a specific domain. Full path to the file containing the SSL certificate for a specific domain.
\titem{\{outgoing\_s2s\_options, Methods, Timeout\}} \ind{options!outgoing\_s2s\_options} \titem{\{outgoing\_s2s\_options, [Family, ...], Timeout\}} \ind{options!outgoing\_s2s\_options}
Specify which address families to try, in what order, and connect timeout in milliseconds. Specify which address families to try, in what order, and connect timeout in milliseconds.
By default it first tries connecting with IPv4, if that fails it tries using IPv6, By default it first tries connecting with IPv4, if that fails it tries using IPv6,
with a timeout of 10000 milliseconds. with a timeout of 10000 milliseconds.
@ -1053,6 +1053,7 @@ However, the c2s and s2s connections to the domain \term{example.com} use the fi
{s2s_use_starttls, true}. {s2s_use_starttls, true}.
{s2s_certfile, "/etc/ejabberd/server.pem"}. {s2s_certfile, "/etc/ejabberd/server.pem"}.
{domain_certfile, "example.com", "/etc/ejabberd/example_com.pem"}. {domain_certfile, "example.com", "/etc/ejabberd/example_com.pem"}.
{outgoing_s2s_options, [ipv4, ipv6], 10000}.
\end{verbatim} \end{verbatim}
In this example, the following configuration defines that: In this example, the following configuration defines that:

View File

@ -114,6 +114,10 @@ HOME=$SPOOLDIR
# create the home dir with the proper user if doesn't exist, because it stores cookie file # create the home dir with the proper user if doesn't exist, because it stores cookie file
[ -d $HOME ] || $EXEC_CMD "mkdir -p $HOME" [ -d $HOME ] || $EXEC_CMD "mkdir -p $HOME"
# Change to a directory readable by INSTALLUSER to
# prevent "File operation error: eacces." messages
cd $HOME
# export global variables # export global variables
export EJABBERD_CONFIG_PATH export EJABBERD_CONFIG_PATH
export EJABBERD_MSGS_PATH export EJABBERD_MSGS_PATH

View File

@ -766,13 +766,15 @@ out_subscription(User, Server, JID, subscribed) ->
[] -> user_resources(PUser, PServer); [] -> user_resources(PUser, PServer);
_ -> [PResource] _ -> [PResource]
end, end,
presence(Server, {presence, PUser, PServer, PResources, Owner}); presence(Server, {presence, PUser, PServer, PResources, Owner}),
true;
out_subscription(_,_,_,_) -> out_subscription(_,_,_,_) ->
ok. true.
in_subscription(_, User, Server, Owner, unsubscribed, _) -> in_subscription(_, User, Server, Owner, unsubscribed, _) ->
unsubscribe_user(jlib:make_jid(User, Server, ""), Owner); unsubscribe_user(jlib:make_jid(User, Server, ""), Owner),
true;
in_subscription(_, _, _, _, _, _) -> in_subscription(_, _, _, _, _, _) ->
ok. true.
unsubscribe_user(Entity, Owner) -> unsubscribe_user(Entity, Owner) ->
BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),

View File

@ -84,16 +84,16 @@
%% API definition %% API definition
%% ================ %% ================
%% @spec (Host, ServerHost, Opts) -> any() %% @spec (Host, ServerHost, Options) -> ok
%% Host = mod_pubsub:host() %% Host = string()
%% ServerHost = mod_pubsub:host() %% ServerHost = string()
%% Opts = list() %% Options = [{atom(), term()}]
%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must %% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
%% implement this function. It can return anything.</p> %% implement this function. It can return anything.</p>
%% <p>This function is mainly used to trigger the setup task necessary for the %% <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 %% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p> %% module database schema if it does not exists yet.</p>
init(_Host, _ServerHost, _Opts) -> init(_Host, _ServerHost, _Options) ->
pubsub_subscription:init(), pubsub_subscription:init(),
mnesia:create_table(pubsub_state, mnesia:create_table(pubsub_state,
[{disc_copies, [node()]}, [{disc_copies, [node()]},
@ -109,16 +109,16 @@ init(_Host, _ServerHost, _Opts) ->
end, end,
ok. ok.
%% @spec (Host, ServerHost) -> any() %% @spec (Host, ServerHost) -> ok
%% Host = mod_pubsub:host() %% Host = string()
%% ServerHost = host() %% ServerHost = string()
%% @doc <p>Called during pubsub modules termination. Any pubsub plugin must %% @doc <p>Called during pubsub modules termination. Any pubsub plugin must
%% implement this function. It can return anything.</p> %% implement this function. It can return anything.</p>
terminate(_Host, _ServerHost) -> terminate(_Host, _ServerHost) ->
ok. ok.
%% @spec () -> [Option] %% @spec () -> Options
%% Option = mod_pubsub:nodeOption() %% Options = [mod_pubsub:nodeOption()]
%% @doc Returns the default pubsub node options. %% @doc Returns the default pubsub node options.
%% <p>Example of function return value:</p> %% <p>Example of function return value:</p>
%% ``` %% ```
@ -152,7 +152,8 @@ options() ->
{deliver_notifications, true}, {deliver_notifications, true},
{presence_based_delivery, false}]. {presence_based_delivery, false}].
%% @spec () -> [] %% @spec () -> Features
%% Features = [string()]
%% @doc Returns the node features %% @doc Returns the node features
features() -> features() ->
["create-nodes", ["create-nodes",
@ -178,13 +179,14 @@ features() ->
"subscription-options" "subscription-options"
]. ].
%% @spec (Host, ServerHost, Node, ParentNode, Owner, Access) -> bool() %% @spec (Host, ServerHost, NodeId, ParentNodeId, Owner, Access) -> {result, Allowed}
%% Host = mod_pubsub:host() %% Host = mod_pubsub:hostPubsub()
%% ServerHost = mod_pubsub:host() %% ServerHost = string()
%% Node = mod_pubsub:pubsubNode() %% NodeId = mod_pubsub:nodeId()
%% ParentNode = mod_pubsub:pubsubNode() %% ParentNodeId = mod_pubsub:nodeId()
%% Owner = mod_pubsub:jid() %% Owner = mod_pubsub:jid()
%% Access = all | atom() %% Access = all | atom()
%% Allowed = boolean()
%% @doc Checks if the current user has the permission to create the requested node %% @doc Checks if the current user has the permission to create the requested node
%% <p>In {@link node_default}, the permission is decided by the place in the %% <p>In {@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 %% hierarchy where the user is creating the node. The access parameter is also
@ -195,9 +197,9 @@ features() ->
%% <p>PubSub plugins can redefine the PubSub node creation rights as they %% <p>PubSub plugins can redefine the PubSub node creation rights as they
%% which. They can simply delegate this check to the {@link node_default} %% which. They can simply delegate this check to the {@link node_default}
%% module by implementing this function like this: %% module by implementing this function like this:
%% ```check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> %% ```check_create_user_permission(Host, ServerHost, NodeId, ParentNodeId, Owner, Access) ->
%% node_default:check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access).'''</p> %% node_default:check_create_user_permission(Host, ServerHost, NodeId, ParentNodeId, Owner, Access).'''</p>
create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) -> create_node_permission(Host, ServerHost, NodeId, _ParentNodeId, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner), LOwner = jlib:jid_tolower(Owner),
{User, Server, _Resource} = LOwner, {User, Server, _Resource} = LOwner,
Allowed = case LOwner of Allowed = case LOwner of
@ -206,7 +208,7 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
_ -> _ ->
case acl:match_rule(ServerHost, Access, LOwner) of case acl:match_rule(ServerHost, Access, LOwner) of
allow -> allow ->
case node_to_path(Node) of case node_to_path(NodeId) of
["home", Server, User | _] -> true; ["home", Server, User | _] -> true;
_ -> false _ -> false
end; end;
@ -216,20 +218,21 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
end, end,
{result, Allowed}. {result, Allowed}.
%% @spec (NodeId, Owner) -> %% @spec (NodeIdx, Owner) -> {result, {default, broadcast}}
%% {result, Result} | exit %% NodeIdx = mod_pubsub:nodeIdx()
%% NodeId = mod_pubsub:pubsubNodeId() %% Owner = mod_pubsub:jid()
%% Owner = mod_pubsub:jid()
%% @doc <p></p> %% @doc <p></p>
create_node(NodeId, Owner) -> create_node(NodeIdx, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
set_state(#pubsub_state{stateid = {OwnerKey, NodeId}, affiliation = owner}), set_state(#pubsub_state{stateid = {OwnerKey, NodeIdx}, affiliation = owner}),
{result, {default, broadcast}}. {result, {default, broadcast}}.
%% @spec (Removed) -> ok %% @spec (Nodes) -> {result, {default, broadcast, Reply}}
%% Removed = [mod_pubsub:pubsubNode()] %% Nodes = [mod_pubsub:pubsubNode()]
%% Reply = [{mod_pubsub:pubsubNode(),
%% [{mod_pubsub:ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]}]}]
%% @doc <p>purge items of deleted nodes after effective deletion.</p> %% @doc <p>purge items of deleted nodes after effective deletion.</p>
delete_node(Removed) -> delete_node(Nodes) ->
Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Ss}) -> Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
lists:map(fun(S) -> lists:map(fun(S) ->
{J, S} {J, S}
@ -244,11 +247,23 @@ delete_node(Removed) ->
del_state(NodeId, LJID) del_state(NodeId, LJID)
end, States), end, States),
{PubsubNode, lists:flatmap(Tr, States)} {PubsubNode, lists:flatmap(Tr, States)}
end, Removed), end, Nodes),
{result, {default, broadcast, Reply}}. {result, {default, broadcast, Reply}}.
%% @spec (NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> %% @spec (NodeIdx, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> {error, Reason} | {result, Result}
%% {error, Reason} | {result, Result} %% NodeIdx = mod_pubsub:nodeIdx()
%% Sender = mod_pubsub:jid()
%% Subscriber = mod_pubsub:jid()
%% AccessModel = mod_pubsub:accessModel()
%% SendLast = atom()
%% PresenceSubscription = boolean()
%% RosterGroup = boolean()
%% Options = [mod_pubsub:nodeOption()]
%% Reason = mod_pubsub:stanzaError()
%% Result = {result, {default, subscribed, mod_pubsub:subId()}}
%% | {result, {default, subscribed, mod_pubsub:subId(), send_last}}
%% | {result, {default, pending, mod_pubsub:subId()}}
%%
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p> %% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
%% <p>The mechanism works as follow: %% <p>The mechanism works as follow:
%% <ul> %% <ul>
@ -280,15 +295,15 @@ delete_node(Removed) ->
%% to completly disable persistance.</li></ul> %% to completly disable persistance.</li></ul>
%% </p> %% </p>
%% <p>In the default plugin module, the record is unchanged.</p> %% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(NodeId, Sender, Subscriber, AccessModel, subscribe_node(NodeIdx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) -> SendLast, PresenceSubscription, RosterGroup, Options) ->
SubKey = jlib:jid_tolower(Subscriber), SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey), GenKey = jlib:jid_remove_resource(SubKey),
Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey), Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
GenState = get_state(NodeId, GenKey), GenState = get_state(NodeIdx, GenKey),
SubState = case SubKey of SubState = case SubKey of
GenKey -> GenState; GenKey -> GenState;
_ -> get_state(NodeId, SubKey) _ -> get_state(NodeIdx, SubKey)
end, end,
Affiliation = GenState#pubsub_state.affiliation, Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = SubState#pubsub_state.subscriptions, Subscriptions = SubState#pubsub_state.subscriptions,
@ -322,7 +337,7 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
%% % Requesting entity is anonymous %% % Requesting entity is anonymous
%% {error, ?ERR_FORBIDDEN}; %% {error, ?ERR_FORBIDDEN};
true -> true ->
case pubsub_subscription:add_subscription(Subscriber, NodeId, Options) of case pubsub_subscription:add_subscription(Subscriber, NodeIdx, Options) of
SubId when is_list(SubId) -> SubId when is_list(SubId) ->
NewSub = case AccessModel of NewSub = case AccessModel of
authorize -> pending; authorize -> pending;
@ -342,22 +357,21 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
end end
end. end.
%% @spec (NodeId, Sender, Subscriber, SubId) -> %% @spec (NodeIdx, Sender, Subscriber, SubId) -> {error, Reason} | {result, default}
%% {error, Reason} | {result, []} %% NodeIdx = mod_pubsub:nodeIdx()
%% NodeId = mod_pubsub:pubsubNodeId() %% Sender = mod_pubsub:jid()
%% Sender = mod_pubsub:jid()
%% Subscriber = mod_pubsub:jid() %% Subscriber = mod_pubsub:jid()
%% SubId = mod_pubsub:subid() %% SubId = mod_pubsub:subId()
%% Reason = mod_pubsub:stanzaError() %% Reason = mod_pubsub:stanzaError()
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p> %% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(NodeId, Sender, Subscriber, SubId) -> unsubscribe_node(NodeIdx, Sender, Subscriber, SubId) ->
SubKey = jlib:jid_tolower(Subscriber), SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey), GenKey = jlib:jid_remove_resource(SubKey),
Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey), Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
GenState = get_state(NodeId, GenKey), GenState = get_state(NodeIdx, GenKey),
SubState = case SubKey of SubState = case SubKey of
GenKey -> GenState; GenKey -> GenState;
_ -> get_state(NodeId, SubKey) _ -> get_state(NodeIdx, SubKey)
end, end,
Subscriptions = lists:filter(fun({_Sub, _SubId}) -> true; Subscriptions = lists:filter(fun({_Sub, _SubId}) -> true;
(_SubId) -> false (_SubId) -> false
@ -390,46 +404,48 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
end, SubState#pubsub_state.subscriptions), end, SubState#pubsub_state.subscriptions),
case Sub of case Sub of
{value, S} -> {value, S} ->
delete_subscriptions(SubKey, NodeId, [S], SubState), delete_subscriptions(SubKey, NodeIdx, [S], SubState),
{result, default}; {result, default};
false -> false ->
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")} {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")}
end; end;
%% Asking to remove all subscriptions to the given node %% Asking to remove all subscriptions to the given node
SubId == all -> SubId == all ->
delete_subscriptions(SubKey, NodeId, Subscriptions, SubState), delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState),
{result, default}; {result, default};
%% No subid supplied, but there's only one matching subscription %% No subid supplied, but there's only one matching subscription
length(Subscriptions) == 1 -> length(Subscriptions) == 1 ->
delete_subscriptions(SubKey, NodeId, Subscriptions, SubState), delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState),
{result, default}; {result, default};
%% No subid and more than one possible subscription match. %% No subid and more than one possible subscription match.
true -> true ->
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")} {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}
end. end.
delete_subscriptions(SubKey, NodeId, Subscriptions, SubState) -> delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState) ->
NewSubs = lists:foldl(fun({Subscription, SubId}, Acc) -> NewSubs = lists:foldl(fun({Subscription, SubId}, Acc) ->
pubsub_subscription:delete_subscription(SubKey, NodeId, SubId), pubsub_subscription:delete_subscription(SubKey, NodeIdx, SubId),
Acc -- [{Subscription, SubId}] Acc -- [{Subscription, SubId}]
end, SubState#pubsub_state.subscriptions, Subscriptions), end, SubState#pubsub_state.subscriptions, Subscriptions),
case {SubState#pubsub_state.affiliation, NewSubs} of case {SubState#pubsub_state.affiliation, NewSubs} of
{none, []} -> {none, []} ->
% Just a regular subscriber, and this is final item, so % Just a regular subscriber, and this is final item, so
% delete the state. % delete the state.
del_state(NodeId, SubKey); del_state(NodeIdx, SubKey);
_ -> _ ->
set_state(SubState#pubsub_state{subscriptions = NewSubs}) set_state(SubState#pubsub_state{subscriptions = NewSubs})
end. end.
%% @spec (NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) -> %% @spec (NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
%% {true, PubsubItem} | {result, Reply} %% {result, {default, broadcast, ItemIds}} | {error, Reason}
%% NodeId = mod_pubsub:pubsubNodeId() %% NodeIdx = mod_pubsub:nodeIdx()
%% Publisher = mod_pubsub:jid() %% Publisher = mod_pubsub:jid()
%% PublishModel = atom() %% PublishModel = atom()
%% MaxItems = integer() %% MaxItems = integer()
%% ItemId = string() %% ItemId = mod_pubsub:itemId()
%% Payload = term() %% Payload = mod_pubsub:payload()
%% ItemIds = [mod_pubsub:itemId()] | []
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Publishes the item passed as parameter.</p> %% @doc <p>Publishes the item passed as parameter.</p>
%% <p>The mechanism works as follow: %% <p>The mechanism works as follow:
%% <ul> %% <ul>
@ -460,13 +476,13 @@ delete_subscriptions(SubKey, NodeId, Subscriptions, SubState) ->
%% to completly disable persistance.</li></ul> %% to completly disable persistance.</li></ul>
%% </p> %% </p>
%% <p>In the default plugin module, the record is unchanged.</p> %% <p>In the default plugin module, the record is unchanged.</p>
publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) -> publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
SubKey = jlib:jid_tolower(Publisher), SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey), GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeId, GenKey), GenState = get_state(NodeIdx, GenKey),
SubState = case SubKey of SubState = case SubKey of
GenKey -> GenState; GenKey -> GenState;
_ -> get_state(NodeId, SubKey) _ -> get_state(NodeIdx, SubKey)
end, end,
Affiliation = GenState#pubsub_state.affiliation, Affiliation = GenState#pubsub_state.affiliation,
Subscribed = case PublishModel of Subscribed = case PublishModel of
@ -485,18 +501,18 @@ publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
if MaxItems > 0 -> if MaxItems > 0 ->
Now = now(), Now = now(),
PubId = {Now, SubKey}, PubId = {Now, SubKey},
Item = case get_item(NodeId, ItemId) of Item = case get_item(NodeIdx, ItemId) of
{result, OldItem} -> {result, OldItem} ->
OldItem#pubsub_item{modification = PubId, OldItem#pubsub_item{modification = PubId,
payload = Payload}; payload = Payload};
_ -> _ ->
#pubsub_item{itemid = {ItemId, NodeId}, #pubsub_item{itemid = {ItemId, NodeIdx},
creation = {Now, GenKey}, creation = {Now, GenKey},
modification = PubId, modification = PubId,
payload = Payload} payload = Payload}
end, end,
Items = [ItemId | GenState#pubsub_state.items--[ItemId]], Items = [ItemId | GenState#pubsub_state.items--[ItemId]],
{result, {NI, OI}} = remove_extra_items(NodeId, MaxItems, Items), {result, {NI, OI}} = remove_extra_items(NodeIdx, MaxItems, Items),
set_item(Item), set_item(Item),
set_state(GenState#pubsub_state{items = NI}), set_state(GenState#pubsub_state{items = NI}),
{result, {default, broadcast, OI}}; {result, {default, broadcast, OI}};
@ -505,11 +521,12 @@ publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
end end
end. end.
%% @spec (NodeId, MaxItems, ItemIds) -> {NewItemIds,OldItemIds} %% @spec (NodeIdx, MaxItems, ItemIds) -> {result, {NewItemIds,OldItemIds}}
%% NodeId = mod_pubsub:pubsubNodeId() %% NodeIdx = mod_pubsub:nodeIdx()
%% MaxItems = integer() | unlimited %% MaxItems = integer() | unlimited
%% ItemIds = [ItemId::string()] %% ItemIds = [mod_pubsub:itemId()]
%% NewItemIds = [ItemId::string()] %% NewItemIds = [mod_pubsub:itemId()]
%% OldItemIds = [mod_pubsub:itemId()] | []
%% @doc <p>This function is used to remove extra items, most notably when the %% @doc <p>This function is used to remove extra items, most notably when the
%% maximum number of items has been reached.</p> %% maximum number of items has been reached.</p>
%% <p>This function is used internally by the core PubSub module, as no %% <p>This function is used internally by the core PubSub module, as no
@ -518,36 +535,36 @@ publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
%% rules can be used.</p> %% rules can be used.</p>
%% <p>If another PubSub plugin wants to delegate the item removal (and if the %% <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: %% plugin is using the default pubsub storage), it can implements this function like this:
%% ```remove_extra_items(NodeId, MaxItems, ItemIds) -> %% ```remove_extra_items(NodeIdx, MaxItems, ItemIds) ->
%% node_default:remove_extra_items(NodeId, MaxItems, ItemIds).'''</p> %% node_default:remove_extra_items(NodeIdx, MaxItems, ItemIds).'''</p>
remove_extra_items(_NodeId, unlimited, ItemIds) -> remove_extra_items(_NodeIdx, unlimited, ItemIds) ->
{result, {ItemIds, []}}; {result, {ItemIds, []}};
remove_extra_items(NodeId, MaxItems, ItemIds) -> remove_extra_items(NodeIdx, MaxItems, ItemIds) ->
NewItems = lists:sublist(ItemIds, MaxItems), NewItems = lists:sublist(ItemIds, MaxItems),
OldItems = lists:nthtail(length(NewItems), ItemIds), OldItems = lists:nthtail(length(NewItems), ItemIds),
%% Remove extra items: %% Remove extra items:
del_items(NodeId, OldItems), del_items(NodeIdx, OldItems),
%% Return the new items list: %% Return the new items list:
{result, {NewItems, OldItems}}. {result, {NewItems, OldItems}}.
%% @spec (NodeId, Publisher, PublishModel, ItemId) -> %% @spec (NodeIdx, Publisher, PublishModel, ItemId) ->
%% {error, Reason::stanzaError()} | %% {result, {default, broadcast}} | {error, Reason}
%% {result, []} %% NodeIdx = mod_pubsub:nodeIdx()
%% NodeId = mod_pubsub:pubsubNodeId() %% Publisher = mod_pubsub:jid()
%% Publisher = mod_pubsub:jid()
%% PublishModel = atom() %% PublishModel = atom()
%% ItemId = string() %% ItemId = mod_pubsub:itemId()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Triggers item deletion.</p> %% @doc <p>Triggers item deletion.</p>
%% <p>Default plugin: The user performing the deletion must be the node owner %% <p>Default plugin: The user performing the deletion must be the node owner
%% or a publisher, or PublishModel being open.</p> %% or a publisher, or PublishModel being open.</p>
delete_item(NodeId, Publisher, PublishModel, ItemId) -> delete_item(NodeIdx, Publisher, PublishModel, ItemId) ->
SubKey = jlib:jid_tolower(Publisher), SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey), GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeId, GenKey), GenState = get_state(NodeIdx, GenKey),
#pubsub_state{affiliation = Affiliation, items = Items} = GenState, #pubsub_state{affiliation = Affiliation, items = Items} = GenState,
Allowed = (Affiliation == publisher) orelse (Affiliation == owner) Allowed = (Affiliation == publisher) orelse (Affiliation == owner)
orelse (PublishModel == open) orelse (PublishModel == open)
orelse case get_item(NodeId, ItemId) of orelse case get_item(NodeIdx, ItemId) of
{result, #pubsub_item{creation = {_, GenKey}}} -> true; {result, #pubsub_item{creation = {_, GenKey}}} -> true;
_ -> false _ -> false
end, end,
@ -558,19 +575,19 @@ delete_item(NodeId, Publisher, PublishModel, ItemId) ->
true -> true ->
case lists:member(ItemId, Items) of case lists:member(ItemId, Items) of
true -> true ->
del_item(NodeId, ItemId), del_item(NodeIdx, ItemId),
set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}), set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}),
{result, {default, broadcast}}; {result, {default, broadcast}};
false -> false ->
case Affiliation of case Affiliation of
owner -> owner ->
%% Owner can delete other publishers items as well %% Owner can delete other publishers items as well
{result, States} = get_states(NodeId), {result, States} = get_states(NodeIdx),
lists:foldl( lists:foldl(
fun(#pubsub_state{items = PI, affiliation = publisher} = S, Res) -> fun(#pubsub_state{items = PI, affiliation = publisher} = S, Res) ->
case lists:member(ItemId, PI) of case lists:member(ItemId, PI) of
true -> true ->
del_item(NodeId, ItemId), del_item(NodeIdx, ItemId),
set_state(S#pubsub_state{items = lists:delete(ItemId, PI)}), set_state(S#pubsub_state{items = lists:delete(ItemId, PI)}),
{result, {default, broadcast}}; {result, {default, broadcast}};
false -> false ->
@ -586,23 +603,22 @@ delete_item(NodeId, Publisher, PublishModel, ItemId) ->
end end
end. end.
%% @spec (NodeId, Owner) -> %% @spec (NodeIdx, Owner) -> {error, Reason} | {result, {default, broadcast}}
%% {error, Reason::stanzaError()} | %% NodeIdx = mod_pubsub:nodeIdx()
%% {result, {default, broadcast}} %% Owner = mod_pubsub:jid()
%% NodeId = mod_pubsub:pubsubNodeId() %% Reason = mod_pubsub:stanzaError()
%% Owner = mod_pubsub:jid() purge_node(NodeIdx, Owner) ->
purge_node(NodeId, Owner) ->
SubKey = jlib:jid_tolower(Owner), SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey), GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeId, GenKey), GenState = get_state(NodeIdx, GenKey),
case GenState of case GenState of
#pubsub_state{affiliation = owner} -> #pubsub_state{affiliation = owner} ->
{result, States} = get_states(NodeId), {result, States} = get_states(NodeIdx),
lists:foreach( lists:foreach(
fun(#pubsub_state{items = []}) -> fun(#pubsub_state{items = []}) ->
ok; ok;
(#pubsub_state{items = Items} = S) -> (#pubsub_state{items = Items} = S) ->
del_items(NodeId, Items), del_items(NodeIdx, Items),
set_state(S#pubsub_state{items = []}) set_state(S#pubsub_state{items = []})
end, States), end, States),
{result, {default, broadcast}}; {result, {default, broadcast}};
@ -611,9 +627,10 @@ purge_node(NodeId, Owner) ->
{error, ?ERR_FORBIDDEN} {error, ?ERR_FORBIDDEN}
end. end.
%% @spec (Host, JID) -> [{Node,Affiliation}] %% @spec (Host, Owner) -> {result, Reply}
%% Host = host() %% Host = mod_pubsub:hostPubsub()
%% JID = mod_pubsub:jid() %% Owner = mod_pubsub:jid()
%% Reply = [] | [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]
%% @doc <p>Return the current affiliations for the given user</p> %% @doc <p>Return the current affiliations for the given user</p>
%% <p>The default module reads affiliations in the main Mnesia %% <p>The default module reads affiliations in the main Mnesia
%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same %% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
@ -661,9 +678,16 @@ set_affiliation(NodeId, Owner, Affiliation) ->
set_state(GenState#pubsub_state{affiliation = Affiliation}) set_state(GenState#pubsub_state{affiliation = Affiliation})
end. end.
%% @spec (Host, Owner) -> [{Node,Subscription}] %% @spec (Host, Owner) ->
%% Host = host() %% {'result', []
%% Owner = mod_pubsub:jid() %% | [{Node, Subscription, SubId, Entity}]
%% | [{Node, Subscription, Entity}]}
%% Host = mod_pubsub:hostPubsub()
%% Owner = mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% Subscription = mod_pubsub:subscription()
%% SubId = mod_pubsub:subId()
%% Entity = mod_pubsub:ljid()
%% @doc <p>Return the current subscriptions for the given user</p> %% @doc <p>Return the current subscriptions for the given user</p>
%% <p>The default module reads subscriptions in the main Mnesia %% <p>The default module reads subscriptions in the main Mnesia
%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same %% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
@ -774,10 +798,11 @@ unsub_with_subid(NodeId, SubId, SubState) ->
set_state(SubState#pubsub_state{subscriptions = NewSubs}) set_state(SubState#pubsub_state{subscriptions = NewSubs})
end. end.
%% @spec (Host, Owner) -> {result, [Node]} | {error, Reason} %% TODO : doc
%% Host = host() %% @spec (Host, Owner) -> {result, Reply} | {error, Reason}
%% Owner = jid() %% Host = mod_pubsub:hostPubsub()
%% Node = pubsubNode() %% Owner = mod_pubsub:jid()
%% Reply = [] | [mod_pubsub:nodeId()]
%% @doc <p>Returns a list of Owner's nodes on Host with pending %% @doc <p>Returns a list of Owner's nodes on Host with pending
%% subscriptions.</p> %% subscriptions.</p>
get_pending_nodes(Host, Owner) -> get_pending_nodes(Host, Owner) ->
@ -821,8 +846,9 @@ get_nodes_helper(NodeTree,
false false
end. end.
%% @spec (NodeId) -> [States] | [] %% @spec (NodeIdx) -> {result, States}
%% NodeId = mod_pubsub:pubsubNodeId() %% NodeIdx = mod_pubsub:nodeIdx()
%% States = [] | [mod_pubsub:pubsubState()]
%% @doc Returns the list of stored states for a given node. %% @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>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 %% <p>We can consider that the pubsub_state table have been created by the main
@ -831,46 +857,49 @@ get_nodes_helper(NodeTree,
%% relational database).</p> %% relational database).</p>
%% <p>If a PubSub plugin wants to delegate the states storage to the default node, %% <p>If a PubSub plugin wants to delegate the states storage to the default node,
%% they can implement this function like this: %% they can implement this function like this:
%% ```get_states(NodeId) -> %% ```get_states(NodeIdx) ->
%% node_default:get_states(NodeId).'''</p> %% node_default:get_states(NodeIdx).'''</p>
get_states(NodeId) -> get_states(NodeIdx) ->
States = case catch mnesia:match_object( States = case catch mnesia:match_object(
#pubsub_state{stateid = {'_', NodeId}, _ = '_'}) of #pubsub_state{stateid = {'_', NodeIdx}, _ = '_'}) of
List when is_list(List) -> List; List when is_list(List) -> List;
_ -> [] _ -> []
end, end,
{result, States}. {result, States}.
%% @spec (NodeId, JID) -> [State] | [] %% @spec (NodeIdx, JID) -> State
%% NodeId = mod_pubsub:pubsubNodeId() %% NodeIdx = mod_pubsub:nodeIdx()
%% JID = mod_pubsub:jid() %% JID = mod_pubsub:jid()
%% State = mod_pubsub:pubsubItems() %% State = mod_pubsub:pubsubState()
%% @doc <p>Returns a state (one state list), given its reference.</p> %% @doc <p>Returns a state (one state list), given its reference.</p>
get_state(NodeId, JID) -> get_state(NodeIdx, JID) ->
StateId = {JID, NodeId}, StateId = {JID, NodeIdx},
case catch mnesia:read({pubsub_state, StateId}) of case catch mnesia:read({pubsub_state, StateId}) of
[State] when is_record(State, pubsub_state) -> State; [State] when is_record(State, pubsub_state) -> State;
_ -> #pubsub_state{stateid=StateId} _ -> #pubsub_state{stateid=StateId}
end. end.
%% @spec (State) -> ok | {error, Reason::stanzaError()} %% @spec (State) -> ok | {error, Reason}
%% State = mod_pubsub:pubsubStates() %% State = mod_pubsub:pubsubState()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Write a state into database.</p> %% @doc <p>Write a state into database.</p>
set_state(State) when is_record(State, pubsub_state) -> set_state(State) when is_record(State, pubsub_state) ->
mnesia:write(State); mnesia:write(State);
set_state(_) -> set_state(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}. {error, ?ERR_INTERNAL_SERVER_ERROR}.
%% @spec (NodeId, JID) -> ok | {error, Reason::stanzaError()} %% @spec (NodeIdx, JID) -> ok | {error, Reason}
%% NodeId = mod_pubsub:pubsubNodeId() %% NodeIdx = mod_pubsub:nodeIdx()
%% JID = mod_pubsub:jid() %% JID = mod_pubsub:jid()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Delete a state from database.</p> %% @doc <p>Delete a state from database.</p>
del_state(NodeId, JID) -> del_state(NodeIdx, JID) ->
mnesia:delete({pubsub_state, {JID, NodeId}}). mnesia:delete({pubsub_state, {JID, NodeIdx}}).
%% @spec (NodeId, From) -> [Items] | [] %% @spec (NodeIdx, From) -> {result, Items}
%% NodeId = mod_pubsub:pubsubNodeId() %% NodeIdx = mod_pubsub:nodeIdx()
%% Items = mod_pubsub:pubsubItems() %% From = mod_pubsub:jid()
%% Items = [] | [mod_pubsub:pubsubItem()]
%% @doc Returns the list of stored items for a given node. %% @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>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 %% <p>We can consider that the pubsub_item table have been created by the main
@ -879,16 +908,17 @@ del_state(NodeId, JID) ->
%% relational database), or they can even decide not to persist any items.</p> %% 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, %% <p>If a PubSub plugin wants to delegate the item storage to the default node,
%% they can implement this function like this: %% they can implement this function like this:
%% ```get_items(NodeId, From) -> %% ```get_items(NodeIdx, From) ->
%% node_default:get_items(NodeId, From).'''</p> %% node_default:get_items(NodeIdx, From).'''</p>
get_items(NodeId, _From) -> get_items(NodeIdx, _From) ->
Items = mnesia:match_object(#pubsub_item{itemid = {'_', NodeId}, _ = '_'}), Items = mnesia:match_object(#pubsub_item{itemid = {'_', NodeIdx}, _ = '_'}),
{result, lists:reverse(lists:keysort(#pubsub_item.modification, Items))}. {result, lists:reverse(lists:keysort(#pubsub_item.modification, Items))}.
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
SubKey = jlib:jid_tolower(JID), SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey), GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeId, GenKey), GenState = get_state(NodeIdx, GenKey),
SubState = get_state(NodeId, SubKey), SubState = get_state(NodeIdx, SubKey),
Affiliation = GenState#pubsub_state.affiliation, Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = SubState#pubsub_state.subscriptions, Subscriptions = SubState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, Subscriptions), Whitelisted = can_fetch_item(Affiliation, Subscriptions),
@ -918,25 +948,37 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
%% % Payment is required for a subscription %% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED}; %% {error, ?ERR_PAYMENT_REQUIRED};
true -> true ->
get_items(NodeId, JID) get_items(NodeIdx, JID)
end. end.
%% @spec (NodeId, ItemId) -> [Item] | [] %% @spec (NodeIdx, ItemId) -> {result, Item} | {error, 'item-not-found'}
%% NodeId = mod_pubsub:pubsubNodeId() %% NodeIdx = mod_pubsub:nodeIdx()
%% ItemId = string() %% ItemId = mod_pubsub:itemId()
%% Item = mod_pubsub:pubsubItems() %% Item = mod_pubsub:pubsubItem()
%% @doc <p>Returns an item (one item list), given its reference.</p> %% @doc <p>Returns an item (one item list), given its reference.</p>
get_item(NodeId, ItemId) -> get_item(NodeIdx, ItemId) ->
case mnesia:read({pubsub_item, {ItemId, NodeId}}) of case mnesia:read({pubsub_item, {ItemId, NodeIdx}}) of
[Item] when is_record(Item, pubsub_item) -> [Item] when is_record(Item, pubsub_item) ->
{result, Item}; {result, Item};
_ -> _ ->
{error, ?ERR_ITEM_NOT_FOUND} {error, ?ERR_ITEM_NOT_FOUND}
end. end.
get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
%% @spec (NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> {result, Item} | {error, Reason}
%% NodeIdx = mod_pubsub:nodeIdx()
%% ItemId = mod_pubsub:itemId()
%% JID = mod_pubsub:jid()
%% AccessModel = mod_pubsub:accessModel()
%% PresenceSubscription = boolean()
%% RosterGroup = boolean()
%% SubId = mod_pubsub:subId()
%% Item = mod_pubsub:pubsubItem()
%% Reason = mod_pubsub:stanzaError() | 'item-not-found'
get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
SubKey = jlib:jid_tolower(JID), SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey), GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeId, GenKey), GenState = get_state(NodeIdx, GenKey),
Affiliation = GenState#pubsub_state.affiliation, Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = GenState#pubsub_state.subscriptions, Subscriptions = GenState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, Subscriptions), Whitelisted = can_fetch_item(Affiliation, Subscriptions),
@ -966,26 +1008,29 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
%% % Payment is required for a subscription %% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED}; %% {error, ?ERR_PAYMENT_REQUIRED};
true -> true ->
get_item(NodeId, ItemId) get_item(NodeIdx, ItemId)
end. end.
%% @spec (Item) -> ok | {error, Reason::stanzaError()} %% @spec (Item) -> ok | {error, Reason}
%% Item = mod_pubsub:pubsubItems() %% Item = mod_pubsub:pubsubItem()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Write an item into database.</p> %% @doc <p>Write an item into database.</p>
set_item(Item) when is_record(Item, pubsub_item) -> set_item(Item) when is_record(Item, pubsub_item) ->
mnesia:write(Item); mnesia:write(Item);
set_item(_) -> set_item(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}. {error, ?ERR_INTERNAL_SERVER_ERROR}.
%% @spec (NodeId, ItemId) -> ok | {error, Reason::stanzaError()} %% @spec (NodeIdx, ItemId) -> ok | {error, Reason}
%% NodeId = mod_pubsub:pubsubNodeId() %% NodeIdx = mod_pubsub:nodeIdx()
%% ItemId = string() %% ItemId = mod_pubsub:itemId()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Delete an item from database.</p> %% @doc <p>Delete an item from database.</p>
del_item(NodeId, ItemId) -> del_item(NodeIdx, ItemId) ->
mnesia:delete({pubsub_item, {ItemId, NodeId}}). mnesia:delete({pubsub_item, {ItemId, NodeIdx}}).
del_items(NodeId, ItemIds) ->
del_items(NodeIdx, ItemIds) ->
lists:foreach(fun(ItemId) -> lists:foreach(fun(ItemId) ->
del_item(NodeId, ItemId) del_item(NodeIdx, ItemId)
end, ItemIds). end, ItemIds).
%% @doc <p>Return the name of the node if known: Default is to return %% @doc <p>Return the name of the node if known: Default is to return

View File

@ -65,16 +65,16 @@
%% API definition %% API definition
%% ================ %% ================
%% @spec (Host, ServerHost, Opts) -> any() %% @spec (Host, ServerHost, Options) -> ok
%% Host = mod_pubsub:host() %% Host = string()
%% ServerHost = host() %% ServerHost = string()
%% Opts = list() %% Options = [{atom(), term()}]
%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must %% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
%% implement this function. It can return anything.</p> %% implement this function. It can return anything.</p>
%% <p>This function is mainly used to trigger the setup task necessary for the %% <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 %% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p> %% module database schema if it does not exists yet.</p>
init(_Host, _ServerHost, _Opts) -> init(_Host, _ServerHost, _Options) ->
mnesia:create_table(pubsub_node, mnesia:create_table(pubsub_node,
[{disc_copies, [node()]}, [{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_node)}]), {attributes, record_info(fields, pubsub_node)}]),
@ -87,34 +87,41 @@ init(_Host, _ServerHost, _Opts) ->
%% mnesia:transform_table(pubsub_state, ignore, StatesFields) %% mnesia:transform_table(pubsub_state, ignore, StatesFields)
end, end,
ok. ok.
%% @spec (Host, ServerHost) -> ok
%% Host = string()
%% ServerHost = string()
terminate(_Host, _ServerHost) -> terminate(_Host, _ServerHost) ->
ok. ok.
%% @spec () -> [Option] %% @spec () -> Options
%% Option = mod_pubsub:nodetreeOption() %% Options = [mod_pubsub:nodeOption()]
%% @doc Returns the default pubsub node tree options. %% @doc Returns the default pubsub node tree options.
options() -> options() ->
[{virtual_tree, false}]. [{virtual_tree, false}].
%% @spec (NodeRecord) -> ok | {error, Reason} %% @spec (Node) -> ok | {error, Reason}
%% Record = mod_pubsub:pubsub_node() %% Node = mod_pubsub:pubsubNode()
set_node(Record) when is_record(Record, pubsub_node) -> %% Reason = mod_pubsub:stanzaError()
mnesia:write(Record); set_node(Node) when is_record(Node, pubsub_node) ->
mnesia:write(Node);
set_node(_) -> set_node(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}. {error, ?ERR_INTERNAL_SERVER_ERROR}.
get_node(Host, Node, _From) -> get_node(Host, Node, _From) ->
get_node(Host, Node). get_node(Host, Node).
%% @spec (Host, Node) -> pubsubNode() | {error, Reason} %% @spec (Host, NodeId) -> Node | {error, Reason}
%% Host = mod_pubsub:host() %% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode() %% NodeId = mod_pubsub:nodeId()
get_node(Host, Node) -> %% Node = mod_pubsub:pubsubNode()
case catch mnesia:read({pubsub_node, {Host, Node}}) of %% Reason = mod_pubsub:stanzaError()
get_node(Host, NodeId) ->
case catch mnesia:read({pubsub_node, {Host, NodeId}}) of
[Record] when is_record(Record, pubsub_node) -> Record; [Record] when is_record(Record, pubsub_node) -> Record;
[] -> {error, ?ERR_ITEM_NOT_FOUND}; [] -> {error, ?ERR_ITEM_NOT_FOUND};
Error -> Error Error -> Error
end. end.
get_node(NodeId) -> get_node(NodeId) ->
case catch mnesia:index_read(pubsub_node, NodeId, #pubsub_node.id) of case catch mnesia:index_read(pubsub_node, NodeId, #pubsub_node.id) of
[Record] when is_record(Record, pubsub_node) -> Record; [Record] when is_record(Record, pubsub_node) -> Record;
@ -125,41 +132,43 @@ get_node(NodeId) ->
get_nodes(Host, _From) -> get_nodes(Host, _From) ->
get_nodes(Host). get_nodes(Host).
%% @spec (Host) -> [pubsubNode()] | {error, Reason} %% @spec (Host) -> Nodes | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid() %% Host = mod_pubsub:host()
%% Nodes = [mod_pubsub:pubsubNode()]
%% Reason = {aborted, atom()}
get_nodes(Host) -> get_nodes(Host) ->
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}). mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}).
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason} %% @spec (Host, Node, From) -> []
%% Host = mod_pubsub:host() | mod_pubsub:jid() %% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode() %% NodeId = mod_pubsub:nodeId()
%% From = mod_pubsub:jid() %% From = mod_pubsub:jid()
%% Depth = integer()
%% Record = pubsubNode()
%% @doc <p>Default node tree does not handle parents, return empty list.</p> %% @doc <p>Default node tree does not handle parents, return empty list.</p>
get_parentnodes(_Host, _Node, _From) -> get_parentnodes(_Host, _NodeId, _From) ->
[]. [].
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason} %% @spec (Host, NodeId, From) -> [{Depth, Node}] | []
%% Host = mod_pubsub:host() | mod_pubsub:jid() %% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode() %% NodeId = mod_pubsub:nodeId()
%% From = mod_pubsub:jid() %% From = mod_pubsub:jid()
%% Depth = integer() %% Depth = integer()
%% Record = pubsubNode() %% Node = mod_pubsub:pubsubNode()
%% @doc <p>Default node tree does not handle parents, return a list %% @doc <p>Default node tree does not handle parents, return a list
%% containing just this node.</p> %% containing just this node.</p>
get_parentnodes_tree(Host, Node, From) -> get_parentnodes_tree(Host, NodeId, From) ->
case get_node(Host, Node, From) of case get_node(Host, NodeId, From) of
N when is_record(N, pubsub_node) -> [{0, [N]}]; Node when is_record(Node, pubsub_node) -> [{0, [Node]}];
_Error -> [] _Error -> []
end. end.
%% @spec (Host, Node, From) -> [pubsubNode()] | {error, Reason} %% @spec (Host, NodeId, From) -> Nodes
%% Host = mod_pubsub:host() %% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode() %% NodeId = mod_pubsub:nodeId()
%% From = mod_pubsub:jid() %% From = mod_pubsub:jid()
get_subnodes(Host, Node, _From) -> %% Nodes = [mod_pubsub:pubsubNode()]
get_subnodes(Host, Node). get_subnodes(Host, NodeId, _From) ->
get_subnodes(Host, NodeId).
get_subnodes(Host, <<>>) -> get_subnodes(Host, <<>>) ->
Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _}, Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _},
parents = Parents} = N <- mnesia:table(pubsub_node), parents = Parents} = N <- mnesia:table(pubsub_node),
@ -176,17 +185,17 @@ get_subnodes(Host, Node) ->
get_subnodes_tree(Host, Node, _From) -> get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node). get_subnodes_tree(Host, Node).
%% @spec (Host, Index) -> [pubsubNodeIdx()] | {error, Reason} %% @spec (Host, NodeId) -> Nodes
%% Host = mod_pubsub:host() %% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode() %% NodeId = mod_pubsub:nodeId()
%% From = mod_pubsub:jid() %% Nodes = [] | [mod_pubsub:pubsubNode()]
get_subnodes_tree(Host, Node) -> get_subnodes_tree(Host, NodeId) ->
case get_node(Host, Node) of case get_node(Host, NodeId) of
{error, _} -> {error, _} ->
[]; [];
Rec -> Rec ->
BasePlugin = list_to_atom("node_"++Rec#pubsub_node.type), BasePlugin = list_to_atom("node_"++Rec#pubsub_node.type),
BasePath = BasePlugin:node_to_path(Node), BasePath = BasePlugin:node_to_path(NodeId),
mnesia:foldl(fun(#pubsub_node{nodeid = {H, N}} = R, Acc) -> mnesia:foldl(fun(#pubsub_node{nodeid = {H, N}} = R, Acc) ->
Plugin = list_to_atom("node_"++R#pubsub_node.type), Plugin = list_to_atom("node_"++R#pubsub_node.type),
Path = Plugin:node_to_path(N), Path = Plugin:node_to_path(N),
@ -197,15 +206,19 @@ get_subnodes_tree(Host, Node) ->
end, [], pubsub_node) end, [], pubsub_node)
end. end.
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok | {error, Reason} %% @spec (Host, NodeId, Type, Owner, Options, Parents) ->
%% Host = mod_pubsub:host() | mod_pubsub:jid() %% {ok, NodeIdx} | {error, Reason}
%% Node = mod_pubsub:pubsubNode() %% Host = mod_pubsub:host()
%% NodeType = mod_pubsub:nodeType() %% NodeId = mod_pubsub:nodeId()
%% Owner = mod_pubsub:jid() %% Type = mod_pubsub:nodeType()
%% Options = list() %% Owner = mod_pubsub:jid()
create_node(Host, Node, Type, Owner, Options, Parents) -> %% Options = [mod_pubsub:nodeOption()]
%% Parents = [] | [mod_pubsub:nodeId()]
%% NodeIdx = mod_pubsub:nodeIdx()
%% Reason = mod_pubsub:stanzaError()
create_node(Host, NodeId, Type, Owner, Options, Parents) ->
BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
case catch mnesia:read({pubsub_node, {Host, Node}}) of case catch mnesia:read({pubsub_node, {Host, NodeId}}) of
[] -> [] ->
ParentExists = ParentExists =
case Host of case Host of
@ -228,14 +241,14 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
end, end,
case ParentExists of case ParentExists of
true -> true ->
NodeId = pubsub_index:new(node), NodeIdx = pubsub_index:new(node),
mnesia:write(#pubsub_node{nodeid = {Host, Node}, mnesia:write(#pubsub_node{nodeid = {Host, NodeId},
id = NodeId, id = NodeIdx,
parents = Parents, parents = Parents,
type = Type, type = Type,
owners = [BJID], owners = [BJID],
options = Options}), options = Options}),
{ok, NodeId}; {ok, NodeIdx};
false -> false ->
%% Requesting entity is prohibited from creating nodes %% Requesting entity is prohibited from creating nodes
{error, ?ERR_FORBIDDEN} {error, ?ERR_FORBIDDEN}
@ -245,13 +258,14 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
{error, ?ERR_CONFLICT} {error, ?ERR_CONFLICT}
end. end.
%% @spec (Host, Node) -> [mod_pubsub:node()] %% @spec (Host, NodeId) -> Removed
%% Host = mod_pubsub:host() | mod_pubsub:jid() %% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode() %% NodeId = mod_pubsub:nodeId()
delete_node(Host, Node) -> %% Removed = [mod_pubsub:pubsubNode()]
Removed = get_subnodes_tree(Host, Node), delete_node(Host, NodeId) ->
lists:foreach(fun(#pubsub_node{nodeid = {_, N}, id = I}) -> Removed = get_subnodes_tree(Host, NodeId),
pubsub_index:free(node, I), lists:foreach(fun(#pubsub_node{nodeid = {_, SubNodeId}, id = SubNodeIdx}) ->
mnesia:delete({pubsub_node, {Host, N}}) pubsub_index:free(node, SubNodeIdx),
mnesia:delete({pubsub_node, {Host, SubNodeId}})
end, Removed), end, Removed),
Removed. Removed.

View File

@ -35,112 +35,159 @@
%% ------------------------------- %% -------------------------------
%% Pubsub types %% Pubsub types
%%% @type host() = string(). %% @type hostPubsub() = string().
%%% <p><tt>host</tt> is the name of the PubSub service. For example, it can be %% <p><tt>hostPubsub</tt> is the name of the PubSub service. For example, it can be
%%% <tt>"pubsub.localhost"</tt>.</p> %% <tt>"pubsub.localhost"</tt>.</p>
%%% @type pubsubNode() = [string()]. %% @type hostPEP() = {User, Server, Resource}
%%% <p>A node is defined by a list of its ancestors. The last element is the name %% User = string()
%%% of the current node. For example: %% Server = string()
%%% ```["home", "localhost", "cromain", "node1"]'''</p> %% Resource = [].
%% <p>For example, it can be :
%% ```{"bob", "example.org", []}'''.</p>
%%% @type stanzaError() = #xmlelement{}. %% @type host() = hostPubsub() | hostPEP().
%%% Example:
%%% ```{xmlelement, "error",
%%% [{"code", Code}, {"type", Type}],
%%% [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}'''
%%% @type pubsubIQResponse() = #xmlelement{}. %% @type nodeId() = binary().
%%% Example: %% <p>A node is defined by a list of its ancestors. The last element is the name
%%% ```{xmlelement, "pubsub", %% of the current node. For example:
%%% [{"xmlns", ?NS_PUBSUB_EVENT}], %% ```<<"/home/localhost/user">>'''</p>
%%% [{xmlelement, "affiliations", [],
%%% []}]}'''
%%% @type nodeOption() = {Option::atom(), Value::term()}. %% @type nodeIdx() = integer().
%%% Example:
%%% ```{deliver_payloads, true}'''
%%% @type nodeType() = string(). %% @type itemId() = string().
%%% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub
%%% plugin to use to manage a given node. For example, it can be
%%% <tt>"flat"</tt>, <tt>"hometree"</tt> or <tt>"blog"</tt>.</p>
%%% @type jid() = #jid{ %% @type subId() = string().
%%% user = string(),
%%% server = string(),
%%% resource = string(),
%%% luser = string(),
%%% lserver = string(),
%%% lresource = string()}.
%%% @type ljid() = {User::string(), Server::string(), Resource::string()}. %% @type payload() = [#xmlelement{} | #xmlcdata{}].
%%% @type affiliation() = none | owner | publisher | outcast. %% @type stanzaError() = #xmlelement{}.
%%% @type subscription() = none | pending | unconfigured | subscribed. %% Example:
%% ```{xmlelement, "error",
%% [{"code", Code}, {"type", Type}],
%% [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}'''
%% @type pubsubIQResponse() = #xmlelement{}.
%% Example:
%% ```{xmlelement, "pubsub",
%% [{"xmlns", ?NS_PUBSUB_EVENT}],
%% [{xmlelement, "affiliations", [],
%% []}]}'''
%%% internal pubsub index table %% @type nodeOption() = {Option, Value}
-record(pubsub_index, {index, last, free}). %% Option = atom()
%% Value = term().
%% Example:
%% ```{deliver_payloads, true}'''
%%% @type pubsubNode() = #pubsub_node{ %% @type nodeType() = string().
%%% nodeid = {Host::host(), Node::pubsubNode()}, %% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub
%%% parentid = Node::pubsubNode(), %% plugin to use to manage a given node. For example, it can be
%%% nodeidx = int(), %% <tt>"flat"</tt>, <tt>"hometree"</tt> or <tt>"blog"</tt>.</p>
%%% type = nodeType(),
%%% options = [nodeOption()]}.
%%% <p>This is the format of the <tt>nodes</tt> table. The type of the table
%%% is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
%%% <p>The <tt>parentid</tt> and <tt>type</tt> fields are indexed.</p>
%%% nodeidx can be anything you want.
-record(pubsub_node, {nodeid,
id,
parents = [],
type = "flat",
owners = [],
options = []
}).
%%% @type pubsubState() = #pubsub_state{ %% @type jid() = {jid, User, Server, Resource, LUser, LServer, LResource}
%%% stateid = {ljid(), nodeidx()}, %% User = string()
%%% items = [ItemId::string()], %% Server = string()
%%% affiliation = affiliation(), %% Resource = string()
%%% subscriptions = [subscription()]}. %% LUser = string()
%%% <p>This is the format of the <tt>affiliations</tt> table. The type of the %% LServer = string()
%%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p> %% LResource = string().
-record(pubsub_state, {stateid,
items = [], %% @type ljid() = {User, Server, Resource}
affiliation = none, %% User = string()
subscriptions = [] %% Server = string()
%% Resource = string().
%% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'.
%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
%% @type accessModel() = 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist'.
%% @type pubsubIndex() = {pubsub_index, Index, Last, Free}
%% Index = atom()
%% Last = integer()
%% Free = [integer()].
%% internal pubsub index table
-record(pubsub_index,
{
index,
last,
free
}). }).
%%% @type pubsubItem() = #pubsub_item{ %% @type pubsubNode() = {pubsub_node, NodeId, Id, Parents, Type, Owners, Options}
%%% itemid = {ItemId::string(), nodeidx()}, %% NodeId = {host() | ljid(), nodeId()}
%%% creation = {now(), ljid()}, %% Id = nodeIdx()
%%% modification = {now(), ljid()}, %% Parents = [nodeId()]
%%% payload = XMLContent::string()}. %% Type = nodeType()
%%% <p>This is the format of the <tt>published items</tt> table. The type of the %% Owners = [ljid()]
%%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p> %% Options = [nodeOption()].
-record(pubsub_item, {itemid, %% <p>This is the format of the <tt>nodes</tt> table. The type of the table
creation = {unknown,unknown}, %% is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
modification = {unknown,unknown}, %% <p>The <tt>Parents</tt> and <tt>type</tt> fields are indexed.</p>
payload = [] %% <tt>id</tt> can be anything you want.
}). -record(pubsub_node,
{
nodeid,
id,
parents = [],
type = "flat",
owners = [],
options = []
}).
%% @type pubsubState() = {pubsub_state, StateId, Items, Affiliation, Subscriptions}
%% StateId = {ljid(), nodeIdx()}
%% Items = [itemId()]
%% Affiliation = affiliation()
%% Subscriptions = [{subscription(), subId()}].
%% <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',
subscriptions = []
}).
%% @type pubsubSubscription() = #pubsub_subscription{ %% @type pubsubItem() = {pubsub_item, ItemId, Creation, Modification, Payload}
%% subid = string(), %% ItemId = {itemId(), nodeIdx()}
%% state_key = {ljid(), pubsubNodeId()}, %% Creation = {now(), ljid()}
%% options = [{atom(), term()}] %% Modification = {now(), ljid()}
%% }. %% Payload = payload().
%% <p>This is the format of the <tt>published items</tt> table. The type of the
%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p>
-record(pubsub_item,
{
itemid,
creation = {'unknown','unknown'},
modification = {'unknown','unknown'},
payload = []
}).
%% @type pubsubSubscription() = {pubsub_subscription, SubId, Options}
%% SubId = subId()
%% Options = [nodeOption()].
%% <p>This is the format of the <tt>subscriptions</tt> table. The type of the %% <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> %% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
-record(pubsub_subscription, {subid, options}). -record(pubsub_subscription,
{
subid,
options
}).
%% @type pubsubLastItem() = #pubsub_last_item{ %% @type pubsubLastItem() = {pubsub_last_item, NodeId, ItemId, Creation, Payload}
%% nodeid = nodeidx(), %% NodeId = nodeIdx()
%% itemid = string(), %% ItemId = itemId()
%% payload = XMLContent::string()}. %% Creation = {now(),ljid()}
%% Payload = payload().
%% <p>This is the format of the <tt>last items</tt> table. it stores last item payload %% <p>This is the format of the <tt>last items</tt> table. it stores last item payload
%% for every node</p> %% for every node</p>
-record(pubsub_last_item, {nodeid, itemid, creation, payload}). -record(pubsub_last_item,
{
nodeid,
itemid,
creation,
payload
}).