fix dializer errors, improve documentation (thanks to Karim Gemayel)(EJAB-1260)

This commit is contained in:
Christophe Romain 2010-09-29 11:48:19 +02:00
parent f7dc4df784
commit 6b7d73dcd5
6 changed files with 1398 additions and 764 deletions

File diff suppressed because it is too large Load Diff

View File

@ -42,7 +42,8 @@
-behaviour(gen_pubsub_nodetree).
-export([init/3,
-export([
init/3,
terminate/2,
options/0,
set_node/1,
@ -60,6 +61,7 @@
]).
%% ================
%% API definition
%% ================
@ -73,6 +75,14 @@
%% <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>
-spec(init/3 ::
(
Host :: string(),
ServerHost :: string(),
Opts :: [{Key::atom(), Value::term()}])
-> 'ok'
).
init(_Host, _ServerHost, _Opts) ->
mnesia:create_table(pubsub_node,
[{disc_copies, [node()]},
@ -81,49 +91,101 @@ init(_Host, _ServerHost, _Opts) ->
NodesFields = record_info(fields, pubsub_node),
case mnesia:table_info(pubsub_node, attributes) of
NodesFields -> ok;
_ ->
ok
%% mnesia:transform_table(pubsub_state, ignore, StatesFields)
_ -> ok %% mnesia:transform_table(pubsub_state, ignore, StatesFields)
end,
ok.
-spec(terminate/2 ::
(
Host :: string(),
ServerHost :: string())
-> 'ok'
).
terminate(_Host, _ServerHost) ->
ok.
%% @spec () -> [Option]
%% Option = mod_pubsub:nodetreeOption()
%% @doc Returns the default pubsub node tree options.
options() ->
[{virtual_tree, false}].
-spec(options/0 :: () -> Options::[{'virtual_tree', 'false'}]).
options() -> [{'virtual_tree', 'false'}].
%% @spec (NodeRecord) -> ok | {error, Reason}
%% Record = mod_pubsub:pubsub_node()
set_node(Record) when is_record(Record, pubsub_node) ->
mnesia:write(Record);
set_node(_) ->
{error, 'internal-server-error'}.
%(
% Node :: pubsubNode() -> 'ok' | {'error', Reason::_};
% Node :: any() -> {'error', 'internal-server-error'}
%).
%% -spec breaks compilation
set_node(#pubsub_node{} = Node) -> mnesia:write(Node);
set_node(_) -> {error, 'internal-server-error'}.
%% @spec (Host, Node, From) -> pubsubNode() | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = node()
get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
case catch mnesia:read({pubsub_node, {Host, Node}}) of
[Record] when is_record(Record, pubsub_node) -> Record;
[] -> {error, 'item-not-found'};
Error -> Error
-spec(get_node/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
JID :: jidEntity())
-> pubsubNode() | {error, 'item-not-found'} | any()
).
get_node(Host, NodeId, _JID) ->
get_node(Host, NodeId).
-spec(get_node/2 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId())
-> pubsubNode() | {error, 'item-not-found'} | any()
).
get_node(Host, NodeId) ->
case catch mnesia:read({pubsub_node, {Host, NodeId}}) of
[#pubsub_node{} = Node] -> Node;
[] -> {error, 'item-not-found'};
Error -> Error
end.
get_node(Nidx) ->
case catch mnesia:index_read(pubsub_node, Nidx, #pubsub_node.idx) of
[Record] when is_record(Record, pubsub_node) -> Record;
[] -> {error, 'item-not-found'};
Error -> Error
-spec(get_node/1 ::
(
NodeIdx :: nodeIdx())
-> pubsubNode() | {error, 'item-not-found'} | any()
).
get_node(NodeIdx) ->
case catch mnesia:index_read(pubsub_node, NodeIdx, #pubsub_node.idx) of
[#pubsub_node{} = Node] -> Node;
[] -> {error, 'item-not-found'};
Error -> Error
end.
%% @spec (Host, From) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host() | ljid()
get_nodes(Host, _From) ->
-spec(get_nodes/2 ::
(
Host :: host(), % hostPubsub | hostPEP()
JID :: jidEntity())
-> Nodes :: [] | [Node::pubsubNode()]
).
get_nodes(Host, _JID) ->
get_nodes(Host).
-spec(get_nodes/1 ::
(
Host :: host()) % hostPubsub | hostPEP()
-> Nodes :: [] | [Node::pubsubNode()]
).
get_nodes(Host) ->
mnesia:match_object(#pubsub_node{id = {Host, '_'}, _ = '_'}).
@ -134,7 +196,15 @@ get_nodes(Host) ->
%% Depth = integer()
%% Record = pubsubNode()
%% @doc <p>Default node tree does not handle parents, return empty list.</p>
get_parentnodes(_Host, _Node, _From) ->
-spec(get_parentnodes/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
JID :: jidEntity())
-> ParentNodes :: []
).
get_parentnodes(_Host, _NodeId, _JID) ->
[].
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
@ -145,52 +215,98 @@ get_parentnodes(_Host, _Node, _From) ->
%% 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 -> []
-spec(get_parentnodes_tree/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
JID :: jidEntity())
-> ParentNodesTree :: [] | [{0, [ParentNode::pubsubNode()]}]
).
get_parentnodes_tree(Host, NodeId, JID) ->
case get_node(Host, NodeId, JID) of
#pubsub_node{} = ParentNode -> [{0, [ParentNode]}];
_Error -> []
end.
%% @spec (Host, Node, From) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = node()
%% From = ljid()
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, <<>>) ->
Q = qlc:q([N || #pubsub_node{id = {NHost, _},
parents = Parents} = N <- mnesia:table(pubsub_node),
Host == NHost,
Parents == []]),
-spec(get_subnodes/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
JID :: jidEntity())
-> SubNodes :: [] | [Node::pubsubNode()]
).
get_subnodes(Host, NodeId, _JID) ->
get_subnodes(Host, NodeId).
-spec(get_subnodes/2 ::
(
ParentNodeHost :: host(), % hostPubsub | hostPEP()
ParentNodeId :: nodeId())
-> SubNodes :: [] | [Node::pubsubNode()]
).
get_subnodes(ParentNodeHost, <<>>) ->
Q = qlc:q(
[Node
|| #pubsub_node{id = {Host, _}, parents = ParentNodeIds} = Node
<- mnesia:table(pubsub_node),
ParentNodeHost == Host,
ParentNodeIds == []]),
qlc:e(Q);
get_subnodes(Host, Node) ->
Q = qlc:q([N || #pubsub_node{id = {NHost, _},
parents = Parents} = N <- mnesia:table(pubsub_node),
Host == NHost,
lists:member(Node, Parents)]),
get_subnodes(ParentNodeHost, ParentNodeId) ->
Q = qlc:q(
[Node
|| #pubsub_node{id = {Host, _}, parents = ParentNodeIds} = Node
<- mnesia:table(pubsub_node),
ParentNodeHost == Host,
lists:member(ParentNodeId, ParentNodeIds)]),
qlc:e(Q).
%% @spec (Host, Index, From) -> [nodeidx()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = node()
%% From = ljid()
get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
get_subnodes_tree(Host, Node) ->
case get_node(Host, Node) of
{error, _} ->
[];
Rec ->
BasePlugin = list_to_atom("node_"++Rec#pubsub_node.type),
BasePath = BasePlugin:node_to_path(Node),
mnesia:foldl(fun(#pubsub_node{id = {H, N}} = R, Acc) ->
Plugin = list_to_atom("node_"++R#pubsub_node.type),
Path = Plugin:node_to_path(N),
case lists:prefix(BasePath, Path) and (H == Host) of
true -> [R | Acc];
false -> Acc
end
end, [], pubsub_node)
-spec(get_subnodes_tree/3 ::
(
Host :: host(), % hostPubsub | hostPEP()
ParentNodeId :: nodeId(),
JID :: jidEntity())
-> SubNodes :: [] | [Node::pubsubNode()]
).
get_subnodes_tree(Host, ParentNodeId, _JID) ->
get_subnodes_tree(Host, ParentNodeId).
-spec(get_subnodes_tree/2 ::
(
Host :: host(), % hostPubsub | hostPEP()
ParentNodeId :: nodeId())
-> SubNodes :: [] | [Node::pubsubNode()]
).
get_subnodes_tree(ParentNodeHost, ParentNodeId) ->
case get_node(ParentNodeHost, ParentNodeId) of
{error, _} -> [];
#pubsub_node{type = ParentNodeType} = _ParentNode ->
BasePlugin = list_to_atom("node_"++ParentNodeType),
BasePath = BasePlugin:node_to_path(ParentNodeId),
mnesia:foldl(fun
(#pubsub_node{id = {Host, NodeId}, type = Type} = Node, Acc) ->
Plugin = list_to_atom("node_"++Type),
Path = Plugin:node_to_path(NodeId),
case lists:prefix(BasePath, Path) and (Host == ParentNodeHost) of
true -> [Node | Acc];
false -> Acc
end
end, [], pubsub_node)
end.
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok | {error, Reason}
@ -199,56 +315,70 @@ get_subnodes_tree(Host, Node) ->
%% NodeType = nodeType()
%% Owner = ljid()
%% Options = list()
create_node(Host, Node, Type, Owner, Options, Parents) ->
BJID = jlib:short_prepd_bare_jid(Owner),
case mnesia:read({pubsub_node, {Host, Node}}) of
-spec(create_node/6 ::
(
Host :: host(), % hostPubsub | hostPEP()
NodeId :: nodeId(),
Type :: nodeType(),
JID :: jidEntity(),
Options :: [nodeOption()],
ParentNodeIds :: [] | [nodeId()])
-> {'ok', NodeIdx::nodeIdx()}
| {'error', 'conflict' | 'forbidden'}
).
create_node(Host, NodeId, Type, #jid{node = U, domain = S} = _JID, Options, ParentNodeIds) ->
Owner = {U,S,undefined},
case mnesia:read({pubsub_node, {Host, NodeId}}) of
[] ->
ParentExists =
case Host of
{_U, _S, _R} ->
%% This is special case for PEP handling
%% PEP does not uses hierarchy
true;
_ ->
case Parents of
[] -> true;
[Parent | _] ->
BHost = list_to_binary(Host),
case mnesia:read({pubsub_node, {Host, Parent}}) of
[#pubsub_node{owners = [{undefined, BHost, undefined}]}] -> true;
[#pubsub_node{owners = Owners}] -> lists:member(BJID, Owners);
_ -> false
end;
_ ->
false
end
end,
ParentExists = case Host of
%% This is special case for PEP handling, PEP does not uses hierarchy
{_, _, _} -> true;
_ ->
case ParentNodeIds of
[] -> true;
[ParentNodeId | _] ->
case mnesia:read({pubsub_node, {Host, ParentNodeId}}) of
[#pubsub_node{owners = [{undefined, Host, undefined}]}] -> true;
[#pubsub_node{owners = Owners}] -> lists:member(Owner, Owners);
_ -> false
end;
_ -> false
end
end,
case ParentExists of
true ->
Nidx = pubsub_index:new(node),
mnesia:write(#pubsub_node{id = {Host, Node},
idx = Nidx,
parents = Parents,
type = Type,
owners = [BJID],
options = Options}),
{ok, Nidx};
false ->
%% Requesting entity is prohibited from creating nodes
{error, 'forbidden'}
end;
_ ->
%% Node already exists
{error, 'conflict'}
NodeIdx = pubsub_index:new(node),
mnesia:write(
#pubsub_node{
id = {Host, NodeId},
idx = NodeIdx,
parents = ParentNodeIds,
type = Type,
owners = [Owner],
options = Options}),
{ok, NodeIdx};
false -> %% Requesting entity is prohibited from creating nodes
{error, 'forbidden'}
end;
_ -> %% Node already exists
{error, 'conflict'}
end.
%% @spec (Host, Node) -> [mod_pubsub:node()]
%% Host = mod_pubsub:host() | ljid()
%% Node = node()
delete_node(Host, Node) ->
Removed = get_subnodes_tree(Host, Node),
lists:foreach(fun(#pubsub_node{id = {_, N}, idx = I}) ->
pubsub_index:free(node, I),
mnesia:delete({pubsub_node, {Host, N}})
end, Removed),
Removed.
-spec(delete_node/2 ::
(
Host :: host(), % hostPubsub | hostPEP()
ParentNodeId :: nodeId())
-> DeletedNodes :: [] | [Node::pubsubNode()]
).
delete_node(Host, ParentNodeId) ->
DeletedNodes = get_subnodes_tree(Host, ParentNodeId),
lists:foreach(fun(#pubsub_node{id = {_, NodeId}, idx = NodeIdx}) ->
pubsub_index:free(node, NodeIdx),
mnesia:delete({pubsub_node, {Host, NodeId}})
end, DeletedNodes),
DeletedNodes.

View File

@ -42,14 +42,25 @@
%% Pubsub types
%% ------------
%%% @type host() = binary().
%%% @type hostPubsub() = binary().
%%%
%%% <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>
-type(host() :: binary()).
-type(hostPubsub() :: binary()).
%%% @type hostPEP() = {User::binary(), Server::binary, Resource::undefined}.
-type(hostPEP() :: {User::binary(), Server::binary, Resource::undefined}).
%%% @type host() = hostPubsub() | hostPEP().
-type(host() :: hostPubsub() | hostPEP()).
%% TODO : move upper in exmpp
%%% @type nodeId() = binary().
%%%
%%% <p>A <tt>nodeId</tt> is the name of a Node. It can be anything and may represent
@ -93,6 +104,58 @@
-type(ljid() :: {User::binary(), Server::binary(), Resource::binary()}).
%% TODO : move upper in exmpp
%%% @type jidComponent() =
%% #jid{raw::binary(), node::undefined, domain::binary(), resource::undefined}.
-type(jidComponent() ::
#jid{raw::binary(), node::undefined, domain::binary(), resource::undefined}).
%% TODO : move upper in exmpp
%%% @type jidContact() =
%% #jid{raw::binary(), node::binary(), domain::binary(), resource::undefined}.
-type(jidContact() ::
#jid{raw::binary(), node::binary(), domain::binary(), resource::undefined}).
%% TODO : move upper in exmpp
%%% @type jidEntity() =
%%% #jid{raw::binary(), node::binary(), domain::binary(), resource::undefined}
%%% #jid{raw::binary(), node::binary(), domain::binary(), resource::binary()}
%%% #jid{raw::binary(), node::undefined, domain::binary(), resource::undefined}
%%% #jid{raw::binary(), node::undefined, domain::binary(), resource::binary()}.
-type(jidEntity() ::
%% Contact bare JID
#jid{raw::binary(), node::binary(), domain::binary(), resource::undefined} |
%% Contact full JID
#jid{raw::binary(), node::binary(), domain::binary(), resource::binary()} |
%% Component bare JID
#jid{raw::binary(), node::undefined, domain::binary(), resource::undefined} |
%% Component full JID
#jid{raw::binary(), node::undefined, domain::binary(), resource::binary()}).
%%% @type bareUsr() = {User::binary(), Server::binary(), Resource::undefined}
%%% | {User::undefined, Server::binary(), Resource::undefined}.
-type(bareUsr() :: {User::binary(), Server::binary(), Resource::undefined}
| {User::undefined, Server::binary(), Resource::undefined}).
%%% @type fullUsr() = {User::binary(), Server::binary(), Resource::undefined}
%%% | {User::binary(), Server::binary(), Resource::binary()}
%%% | {User::undefined, Server::binary(), Resource::undefined}
%%% | {User::undefined, Server::binary(), Resource::binary()}.
-type(fullUsr() :: {User::binary(), Server::binary(), Resource::undefined}
| {User::binary(), Server::binary(), Resource::binary()}
| {User::undefined, Server::binary(), Resource::undefined}
| {User::undefined, Server::binary(), Resource::binary()}).
%%% @type nodeIdx() = integer().
-type(nodeIdx() :: integer()).
@ -103,19 +166,24 @@
-type(now() :: {Megaseconds::integer(), Seconds::integer(), Microseconds::integer()}).
%%% @type affiliation() = none | owner | publisher | member | outcast.
%%% @type affiliation() = 'none' | 'owner' | 'publisher' |'publish-only' | 'member' | 'outcast'.
-type(affiliation() :: none | owner | publisher | member | outcast).
-type(affiliation() :: 'none' | 'owner' | 'publisher' |'publish-only' | 'member' | 'outcast').
%%% @type subscription() = none | pending | unconfigured | subscribed.
%%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
-type(subscription() :: none | pending | unconfigured | subscribed).
-type(subscription() :: 'none' | 'pending' | 'unconfigured' | 'subscribed').
%%% @type payload() = string().
%%% @type accessModel() = 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist'.
-type(payload() :: string()).
-type(accessModel() :: 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist').
%%% @type payload() = [] | [#xmlel{}].
-type(payload() :: [] | [#xmlel{}]).
%%% @type stanzaError() = #xmlel{}.
@ -155,6 +223,10 @@
-type(pubsubIQResponse() :: #xmlel{}).
%%% @type features() = [Feature::string()].
-type(features() :: [Feature::string()]).
%%% @type nodeOption() = {Option, Value}.
%%% Option = atom()
%%% Value = term().
@ -164,6 +236,7 @@
-type(nodeOption() :: {Option::atom(), Value::term()}).
%%% @type subOption() = {Option, Value}.
%%% Option = atom()
%%% Value = term().
@ -179,11 +252,11 @@
%%% Internal pubsub index table.
-record(pubsub_index,
{
index :: atom(),
last :: integer(),
free :: [integer()]
}).
{
index :: atom(),
last :: integer(),
free :: [integer()]
}).
-type(pubsubIndex() :: #pubsub_index{}).
@ -193,7 +266,7 @@
%%% Idx = nodeIdx()
%%% Parents = [nodeId()]
%%% Type = nodeType()
%%% Owners = [ljid()]
%%% Owners = [bareUsr()]
%%% Options = [nodeOption()].
%%%
%%% <p>This is the format of the <tt>nodes</tt> table. The type of the table
@ -202,54 +275,54 @@
%%% <p><tt>idx</tt> is an integer.</p>
-record(pubsub_node,
{
id :: {host(), nodeId()},
idx :: nodeIdx(),
parents = [] :: [nodeId()],
type = "flat" :: nodeType(),
owners = [] :: [ljid()],
options = [] :: [nodeOption()]
}).
{
id :: {host(), nodeId()},
idx :: nodeIdx(),
parents = [] :: [nodeId()],
type = "flat" :: nodeType(),
owners = [] :: [bareUsr()],
options = [] :: [nodeOption()]
}).
-type(pubsubNode() :: #pubsub_node{}).
%%% @type pubsubState() = {pubsub_state, Id, Items, Affiliation, Subscriptions}
%%% Id = {ljid(), nodeIdx()}
%%% Id = {fullUsr(), nodeIdx()}
%%% Items = [itemId()]
%%% Affiliation = affiliation()
%%% Subscriptions = [subscription()].
%%% 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,
{
id :: {ljid(), nodeIdx()},
items = [] :: [itemId()],
affiliation = none :: affiliation(),
subscriptions = [] :: [subscription()]
}).
{
id :: {fullUsr(), nodeIdx()},
items = [] :: [itemId()],
affiliation = 'none' :: affiliation(),
subscriptions = [] :: [{subscription(), subId()}]
}).
-type(pubsubState() :: #pubsub_state{}).
%%% @type pubsubItem() = {pubsub_item, Id, Creation, Modification, Payload}
%%% Id = {itemId(), nodeIdx()}
%%% Creation = {now(), ljid()}
%%% Modification = {now(), ljid()}
%%% Creation = {now(), bareUsr()}
%%% Modification = {now(), fullUsr()}
%%% 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,
{
id :: {itemId(), nodeIdx()},
creation = {unknown,unknown} :: {now(), ljid()},
modification = {unknown,unknown} :: {now(), ljid()},
payload = [] :: payload()
}).
{
id :: {itemId(), nodeIdx()},
creation = {unknown,unknown} :: {now(), bareUsr()},
modification = {unknown,unknown} :: {now(), fullUsr()},
payload = [] :: payload()
}).
-type(pubsubItem() :: #pubsub_item{}).
@ -262,10 +335,10 @@
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
-record(pubsub_subscription,
{
subid :: subId(),
options :: [subOption()]
}).
{
subid :: subId(),
options :: [subOption()]
}).
-type(pubsubSubscription() :: #pubsub_subscription{}).
@ -273,18 +346,18 @@
%%% @type pubsubLastItem() = {pubsub_last_item, NodeId, ItemId, Creation, Payload}
%%% NodeId = nodeIdx()
%%% ItemId = itemId()
%%% Creation = {now(), ljid()}
%%% Creation = {now(), bareUsr()}
%%% Payload = payload().
%%%
%% <p>This is the format of the <tt>last items</tt> table. it stores last item payload
%% for every node</p>
-record(pubsub_last_item,
{
nodeid :: nodeIdx(),
itemid :: itemId(),
creation :: {now(), ljid()},
payload :: payload()
}).
{
nodeid :: nodeIdx(),
itemid :: itemId(),
creation :: {now(), bareUsr()},
payload :: payload()
}).
-type(pubsubLastItem() :: #pubsub_last_item{}).

View File

@ -31,35 +31,58 @@
-include("pubsub.hrl").
-export([init/3, new/1, free/2]).
-export([
init/3,
new/1,
free/2
]).
-spec(init/3 ::
(
Host :: string(),
ServerHost :: string(),
Opts :: [{Key::atom(), Value::term()}])
-> 'ok'
).
init(_Host, _ServerHost, _Opts) ->
mnesia:create_table(pubsub_index,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_index)}]).
-spec(new/1 ::
(
Index::atom())
-> Idx::integer()
).
new(Index) ->
case mnesia:read({pubsub_index, Index}) of
[I] ->
case I#pubsub_index.free of
[] ->
Id = I#pubsub_index.last + 1,
mnesia:write(I#pubsub_index{last = Id}),
Id;
[Id|Free] ->
mnesia:write(I#pubsub_index{free = Free}),
Id
end;
_ ->
mnesia:write(#pubsub_index{index = Index, last = 1, free = []}),
1
[#pubsub_index{free = [], last = Last} = PubsubIndex] ->
Idx = Last + 1,
mnesia:write(PubsubIndex#pubsub_index{last = Idx}),
Idx;
[#pubsub_index{free = [Idx|Free]} = PubsubIndex] ->
mnesia:write(PubsubIndex#pubsub_index{free = Free}),
Idx;
_ ->
mnesia:write(#pubsub_index{index = Index, last = 1, free = []}),
1
end.
free(Index, Id) ->
-spec(free/2 ::
(
Index :: atom(),
Idx :: integer())
-> 'ok'
).
free(Index, Idx) ->
case mnesia:read({pubsub_index, Index}) of
[I] ->
Free = I#pubsub_index.free,
mnesia:write(I#pubsub_index{free = [Id|Free]});
_ ->
ok
[#pubsub_index{free = Free} = PubsubIndex] ->
mnesia:write(PubsubIndex#pubsub_index{free = [Idx|Free]});
_ -> ok
end.

View File

@ -1,5 +1,5 @@
--- mod_pubsub.erl 2010-09-20 15:56:56.000000000 +0200
+++ mod_pubsub_odbc.erl 2010-09-20 16:50:49.000000000 +0200
--- mod_pubsub.erl 2010-09-23 10:43:43.000000000 +0200
+++ mod_pubsub_odbc.erl 2010-09-29 11:46:22.000000000 +0200
@@ -42,7 +42,7 @@
%%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see
%%% XEP-0060 section 12.18.

View File

@ -24,28 +24,32 @@
-author("bjc@kublai.com").
%% API
-export([init/0,
-export([
init/0,
subscribe_node/3,
unsubscribe_node/3,
get_subscription/3,
set_subscription/4,
get_options_xform/2,
parse_options_xform/1]).
parse_options_xform/1
]).
% Internal function also exported for use in transactional bloc from pubsub plugins
-export([add_subscription/3,
% 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]).
write_subscription/4
]).
-include("pubsub.hrl").
-define(PUBSUB_DELIVER, "pubsub#deliver").
-define(PUBSUB_DIGEST, "pubsub#digest").
-define(PUBSUB_DELIVER, "pubsub#deliver").
-define(PUBSUB_DIGEST, "pubsub#digest").
-define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency").
-define(PUBSUB_EXPIRE, "pubsub#expire").
-define(PUBSUB_EXPIRE, "pubsub#expire").
-define(PUBSUB_INCLUDE_BODY, "pubsub#include_body").
-define(PUBSUB_SHOW_VALUES, "pubsub#show-values").
-define(PUBSUB_SHOW_VALUES, "pubsub#show-values").
-define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type").
-define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth").
@ -54,11 +58,11 @@
-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").
"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").
"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,
@ -85,113 +89,194 @@
%%====================================================================
%% API
%%====================================================================
-spec(init/0 :: () -> 'ok').
init() ->
ok = create_table().
subscribe_node(JID, NodeId, Options) ->
try mnesia:sync_dirty(fun add_subscription/3,
[JID, NodeId, Options]) of
{error, Error} -> {error, Error};
Result -> {result, Result}
catch
Error -> Error
end.
-spec(subscribe_node/3 ::
(
Entity :: jidEntity() | fullUsr(),
NodeIdx :: nodeIdx(),
Options :: [nodeOption()])
-> {'result', SubId::subId()} | {'error', _}
).
unsubscribe_node(JID, NodeId, SubId) ->
try mnesia:sync_dirty(fun delete_subscription/3,
[JID, NodeId, SubId]) of
subscribe_node(#jid{node = U, domain = S, resource = R}, NodeIdx, Options) ->
subscribe_node({U,S,R}, NodeIdx, Options);
subscribe_node(Entity, NodeIdx, Options) ->
try mnesia:sync_dirty(fun add_subscription/3, [Entity, NodeIdx, Options]) of
{error, Error} -> {error, Error};
Result -> {result, Result}
Result -> {result, Result}
catch
Error -> Error
end.
get_subscription(JID, NodeId, SubId) ->
try mnesia:sync_dirty(fun read_subscription/3,
[JID, NodeId, SubId]) of
-spec(unsubscribe_node/3 ::
(
JID :: jidEntity(),
NodeIdx :: nodeIdx(),
SubId :: subId())
-> {'result','ok'} | {'error', _}
).
unsubscribe_node(#jid{node = U, domain = S, resource = R} = _JID, NodeIdx, SubId) ->
try mnesia:sync_dirty(fun delete_subscription/3, [{U,S,R}, NodeIdx, SubId]) of
{error, Error} -> {error, Error};
Result -> {result, Result}
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
-spec(get_subscription/3 ::
(
JID :: jidEntity(),
NodeIdx :: nodeIdx(),
SubId :: subId())
-> {'result', pubsubSubscription()} | {'error', _}
).
get_subscription(#jid{node = U, domain = S, resource = R} = _JID, NodeIdx, SubId) ->
try mnesia:sync_dirty(fun read_subscription/3, [{U,S,R}, NodeIdx, SubId]) of
{error, Error} -> {error, Error};
Result -> {result, Result}
Subscription -> {result, Subscription}
catch
Error -> Error
end.
-spec(set_subscription/4 ::
(
JID :: jidEntity(),
NodeIdx :: nodeIdx(),
SubId :: subId(),
Options :: [nodeOption()])
-> {'result', 'ok'} | {'error', _}
).
set_subscription(#jid{node = U, domain = S, resource = R} = _JID, NodeIdx, SubId, Options) ->
try mnesia:sync_dirty(fun write_subscription/4, [{U,S,R}, NodeIdx, SubId, Options]) of
{error, Error} -> {error, Error};
Result -> {result, Result}
catch
Error -> Error
end.
%% TODO : check input type data
get_options_xform(Lang, Options) ->
Keys = [deliver, digest, digest_frequency, expire, include_body, show_values, subscription_type, subscription_depth],
XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
{result, #xmlel{ns = ?NS_DATA_FORMS, name = 'x', attrs = [?XMLATTR('type', <<"form">>)], 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}}.
{result, #xmlel{ns = ?NS_DATA_FORMS, name = 'x', attrs = [?XMLATTR('type', <<"form">>)], 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}}.
%% TODO : check input type data
parse_options_xform(XFields) ->
case XFields of
[] -> {result, []};
_ -> case exmpp_xml:remove_cdata_from_list(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
[] -> {result, []};
_ -> case exmpp_xml:remove_cdata_from_list(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
end.
%%====================================================================
%% Internal functions
%%====================================================================
-spec(create_table/0 :: () -> 'ok').
create_table() ->
case mnesia:create_table(pubsub_subscription,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_subscription)},
{type, set}]) of
{atomic, ok} -> ok;
{type, set}])
of
{atomic, ok} -> ok;
{aborted, {already_exists, _}} -> ok;
Other -> Other
Other -> Other
end.
add_subscription(_JID, _NodeId, Options) ->
-spec(add_subscription/3 ::
(
Entity :: fullUsr(),
NodeIdx :: nodeIdx(),
Options :: [nodeOption()])
-> SubId::subId()
).
add_subscription(_Entity, _NodeIdx, Options) ->
SubId = make_subid(),
mnesia:write(#pubsub_subscription{subid = SubId, options = Options}),
SubId.
delete_subscription(_JID, _NodeId, SubId) ->
-spec(delete_subscription/3 ::
(
Entity :: fullUsr(),
NodeIdx :: nodeIdx(),
SubId :: subId())
-> 'ok'
).
delete_subscription(_Entity, _NodeIdx, SubId) ->
mnesia:delete({pubsub_subscription, SubId}).
read_subscription(_JID, _NodeId, SubId) ->
-spec(read_subscription/3 ::
(
Entity :: fullUsr(),
NodeIdx :: nodeIdx(),
SubId :: subId())
-> pubsubSubscription() | {'error', 'notfound'}
).
read_subscription(_Entity, _NodeIdx, SubId) ->
case mnesia:read({pubsub_subscription, SubId}) of
[Sub] -> Sub;
_ -> {error, notfound}
[Subscription] -> Subscription;
_ -> {'error', 'notfound'}
end.
write_subscription(JID, NodeId, SubId, Options) ->
case read_subscription(JID, NodeId, SubId) of
{error, notfound} -> {error, notfound};
Sub -> mnesia:write(Sub#pubsub_subscription{options = Options})
-spec(write_subscription/4 ::
(
Entity :: fullUsr(),
NodeIdx :: nodeIdx(),
SubId :: subId(),
Options :: [nodeOption()])
-> 'ok' | {'error', 'notfound'}
).
write_subscription(Entity, NodeIdx, SubId, Options) ->
case read_subscription(Entity, NodeIdx, SubId) of
{error, 'notfound'} -> {error, 'notfound'};
Subscription -> mnesia:write(Subscription#pubsub_subscription{options = Options})
end.
-spec(make_subid/0 :: () -> SubId::subId()).
make_subid() ->
{T1, T2, T3} = now(),
lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
list_to_binary(lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]))).
%%
%% Subscription XForm processing.
@ -199,6 +284,7 @@ make_subid() ->
%% Return processed options, with types converted and so forth, using
%% Opts as defaults.
%% TODO : check input type data
set_xoption([], Opts) ->
Opts;
set_xoption([{Var, Value} | T], Opts) ->
@ -211,33 +297,36 @@ set_xoption([{Var, Value} | T], Opts) ->
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}.
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]) ->
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')}
Integer when is_integer(Integer) -> Integer;
_ -> {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;
@ -247,6 +336,7 @@ xopt_to_bool(_) -> {error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-acce
%% Return a field for an XForm for Key, with data filled in, if
%% applicable, from Options.
%% TODO : check input type data
get_option_xfield(Lang, Key, Options) ->
Var = xfield_var(Key),
Label = xfield_label(Key),
@ -262,73 +352,76 @@ get_option_xfield(Lang, Key, Options) ->
attrs = [?XMLATTR('var', Var), ?XMLATTR('type', Type), ?XMLATTR('label', translate:translate(Lang, Label))],
children = OptEls ++ Vals}.
%% TODO : check input type data
type_and_options({Type, Options}, Lang) ->
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
type_and_options(Type, _Lang) ->
{Type, []}.
%% TODO : check input type data
tr_xfield_options({Value, Label}, Lang) ->
#xmlel{ns = ?NS_DATA_FORMS,
name = 'option',
attrs = [?XMLATTR('label', translate:translate(Lang, Label))],
children = [#xmlel{ns = ?NS_DATA_FORMS,
name = 'value',
children = [?XMLCDATA(Value)]}]}.
name = 'option',
attrs = [?XMLATTR('label', translate:translate(Lang, Label))],
children = [#xmlel{ns = ?NS_DATA_FORMS,
name = 'value',
children = [?XMLCDATA(Value)]}]}.
%% TODO : check input type data
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.
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) ->
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) ->
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},
{"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.
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)].
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', Depth) -> [integer_to_list(Depth)].
%% Convert erlang booleans to XForms.
bool_to_xopt(false) -> "false";
bool_to_xopt(true) -> "true".
bool_to_xopt('false') -> "false";
bool_to_xopt('true') -> "true".