summaryrefslogtreecommitdiff
path: root/src/mod_pubsub/node_hometree_odbc.erl
diff options
context:
space:
mode:
authorBadlop <badlop@process-one.net>2013-03-14 10:33:02 +0100
committerBadlop <badlop@process-one.net>2013-03-14 10:33:02 +0100
commit9deb294328bb3f9eb6bd2c0e7cd500732e9b5830 (patch)
tree7e1066c130250627ee0abab44a135f583a28d07f /src/mod_pubsub/node_hometree_odbc.erl
parentlist_to_integer/2 only works in OTP R14 and newer (diff)
Accumulated patch to binarize and indent code
Diffstat (limited to 'src/mod_pubsub/node_hometree_odbc.erl')
-rw-r--r--src/mod_pubsub/node_hometree_odbc.erl1982
1 files changed, 1154 insertions, 828 deletions
diff --git a/src/mod_pubsub/node_hometree_odbc.erl b/src/mod_pubsub/node_hometree_odbc.erl
index 0deb9d1a..a5f8668e 100644
--- a/src/mod_pubsub/node_hometree_odbc.erl
+++ b/src/mod_pubsub/node_hometree_odbc.erl
@@ -5,11 +5,13 @@
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
+%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
+%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
@@ -39,9 +41,11 @@
%%% improvements.</p>
-module(node_hometree_odbc).
+
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
+
-include("jlib.hrl").
-define(PUBSUB, mod_pubsub_odbc).
@@ -49,52 +53,25 @@
-behaviour(gen_pubsub_node).
%% API definition
--export([init/3, terminate/2,
- options/0, features/0,
- create_node_permission/6,
- create_node/2,
- delete_node/1,
- purge_node/2,
- subscribe_node/8,
- unsubscribe_node/4,
- publish_item/6,
- delete_item/4,
- remove_extra_items/3,
- get_entity_affiliations/2,
- get_node_affiliations/1,
- get_affiliation/2,
- set_affiliation/3,
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2,
get_entity_subscriptions_for_send_last/2,
- get_node_subscriptions/1,
- get_subscriptions/2,
- set_subscriptions/4,
- get_pending_nodes/2,
- get_states/1,
- get_state/2,
- set_state/1,
- get_items/7,
- get_items/6,
- get_items/3,
- get_items/2,
- get_item/7,
- get_item/2,
- set_item/1,
- get_item_name/3,
- get_last_items/3,
- path_to_node/1,
- node_to_path/1
- ]).
-
--export([
- decode_jid/1,
- decode_node/1,
- decode_affiliation/1,
- decode_subscriptions/1,
- encode_jid/1,
- encode_affiliation/1,
- encode_subscriptions/1
- ]).
+ get_node_subscriptions/1, get_subscriptions/2,
+ set_subscriptions/4, get_pending_nodes/2, get_states/1,
+ get_state/2, set_state/1, get_items/7, get_items/6,
+ get_items/3, get_items/2, get_item/7, get_item/2,
+ set_item/1, get_item_name/3, get_last_items/3,
+ path_to_node/1, node_to_path/1]).
+
+-export([decode_jid/1, decode_node/1,
+ decode_affiliation/1, decode_subscriptions/1,
+ encode_jid/1, encode_affiliation/1,
+ encode_subscriptions/1]).
%% ================
%% API definition
@@ -110,16 +87,14 @@
%% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p>
init(_Host, _ServerHost, _Opts) ->
- pubsub_subscription_odbc:init(),
- ok.
+ pubsub_subscription_odbc:init(), ok.
%% @spec (Host, ServerHost) -> any()
%% Host = mod_pubsub:host()
%% ServerHost = host()
%% @doc <p>Called during pubsub modules termination. Any pubsub plugin must
%% implement this function. It can return anything.</p>
-terminate(_Host, _ServerHost) ->
- ok.
+terminate(_Host, _ServerHost) -> ok.
%% @spec () -> [Option]
%% Option = mod_pubsub:nodeOption()
@@ -138,53 +113,25 @@ terminate(_Host, _ServerHost) ->
%% {max_payload_size, 100000},
%% {send_last_published_item, never},
%% {presence_based_delivery, false}]'''
+-spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()).
options() ->
- [{deliver_payloads, true},
- {notify_config, false},
- {notify_delete, false},
- {notify_retract, true},
- {purge_offline, false},
- {persist_items, true},
- {max_items, ?MAXITEMS},
- {subscribe, true},
- {access_model, open},
- {roster_groups_allowed, []},
+ [{deliver_payloads, true}, {notify_config, false},
+ {notify_delete, false}, {notify_retract, true},
+ {purge_offline, false}, {persist_items, true},
+ {max_items, ?MAXITEMS}, {subscribe, true},
+ {access_model, open}, {roster_groups_allowed, []},
{publish_model, publishers},
{notification_type, headline},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, false},
- {odbc, true},
+ {presence_based_delivery, false}, {odbc, true},
{rsm, true}].
%% @spec () -> []
%% @doc Returns the node features
+-spec(features/0 :: () -> Features::[Feature::binary(),...]).
features() ->
- ["create-nodes",
- "auto-create",
- "access-authorize",
- "delete-nodes",
- "delete-items",
- "get-pending",
- "instant-nodes",
- "manage-subscriptions",
- "modify-affiliations",
- "multi-subscribe",
- "outcast-affiliation",
- "persistent-items",
- "publish",
- "purge-nodes",
- "retract-items",
- "retrieve-affiliations",
- "retrieve-items",
- "retrieve-subscriptions",
- "subscribe",
- "subscription-notifications",
- "subscription-options",
- "rsm"
- ].
-
%% @spec (Host, ServerHost, Node, ParentNode, Owner, Access) -> bool()
%% Host = mod_pubsub:host()
%% ServerHost = mod_pubsub:host()
@@ -204,23 +151,44 @@ features() ->
%% module by implementing this function like this:
%% ```check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
%% node_default:check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access).'''</p>
+ [<<"create-nodes">>, <<"auto-create">>,
+ <<"access-authorize">>, <<"delete-nodes">>,
+ <<"delete-items">>, <<"get-pending">>,
+ <<"instant-nodes">>, <<"manage-subscriptions">>,
+ <<"modify-affiliations">>, <<"multi-subscribe">>,
+ <<"outcast-affiliation">>, <<"persistent-items">>,
+ <<"publish">>, <<"purge-nodes">>, <<"retract-items">>,
+ <<"retrieve-affiliations">>, <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>, <<"subscribe">>,
+ <<"subscription-notifications">>,
+ <<"subscription-options">>, <<"rsm">>].
+
+-spec(create_node_permission/6 ::
+(
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: mod_pubsub:nodeId(),
+ _ParentNode :: _,
+ Owner :: jid(),
+ Access :: atom())
+ -> {result, boolean()}
+).
create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case LOwner of
- {"", Host, ""} ->
- true; % pubsub service always allowed
- _ ->
- case acl:match_rule(ServerHost, Access, LOwner) of
- allow ->
- case node_to_path(Node) of
- ["home", Server, User | _] -> true;
- _ -> false
- end;
+ {<<"">>, Host, <<"">>} ->
+ true; % pubsub service always allowed
_ ->
- false
- end
- end,
+ case acl:match_rule(ServerHost, Access, LOwner) of
+ allow ->
+ case node_to_path(Node) of
+ [<<"home">>, Server, User | _] -> true;
+ _ -> false
+ end;
+ _ -> false
+ end
+ end,
{result, Allowed}.
%% @spec (NodeId, Owner) ->
@@ -228,36 +196,53 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
%% NodeId = mod_pubsub:pubsubNodeId()
%% Owner = mod_pubsub:jid()
%% @doc <p></p>
-create_node(NodeId, Owner) ->
+-spec(create_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, {default, broadcast}}
+).
+create_node(NodeIdx, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
- State = #pubsub_state{stateid = {OwnerKey, NodeId}, affiliation = owner},
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values(", state_to_raw(NodeId, State), ");"]),
+ State = #pubsub_state{stateid = {OwnerKey, NodeIdx}, affiliation = owner},
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, "
+ "affiliation, subscriptions) values(">>,
+ state_to_raw(NodeIdx, State), <<");">>]),
{result, {default, broadcast}}.
%% @spec (Removed) -> ok
%% Removed = [mod_pubsub:pubsubNode()]
%% @doc <p>purge items of deleted nodes after effective deletion.</p>
+-spec(delete_node/1 ::
+(
+ Removed :: [mod_pubsub:pubsubNode(),...])
+ -> {result, {default, broadcast, _}}
+).
delete_node(Removed) ->
- Reply = lists:map(
- fun(#pubsub_node{id = NodeId} = PubsubNode) ->
- Subscriptions = case catch ejabberd_odbc:sql_query_t(
- ["select jid, subscriptions "
- "from pubsub_state "
- "where nodeid='", NodeId, "';"]) of
- {selected, ["jid", "subscriptions"], RItems} ->
- lists:map(fun({SJID, Subscriptions}) ->
- {decode_jid(SJID), decode_subscriptions(Subscriptions)}
- end, RItems);
- _ ->
- []
- end,
- %% state and item remove already done thanks to DELETE CASCADE
- %% but here we get nothing in States, making notify_retract unavailable !
- %% TODO, remove DELETE CASCADE from schema
- {PubsubNode, Subscriptions}
- end, Removed),
+ Reply = lists:map(fun (#pubsub_node{id = NodeId} =
+ PubsubNode) ->
+ Subscriptions = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
+ "where nodeid='">>,
+ NodeId,
+ <<"';">>])
+ of
+ {selected,
+ [<<"jid">>,
+ <<"subscriptions">>],
+ RItems} ->
+ lists:map(fun ({SJID,
+ Subscriptions}) ->
+ {decode_jid(SJID),
+ decode_subscriptions(Subscriptions)}
+ end,
+ RItems);
+ _ -> []
+ end,
+ {PubsubNode, Subscriptions}
+ end,
+ Removed),
{result, {default, broadcast, Reply}}.
%% @spec (NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
@@ -293,60 +278,81 @@ delete_node(Removed) ->
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
+-spec(subscribe_node/8 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: ljid(),
+ AccessModel :: mod_pubsub:accessModel(),
+ SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence',
+ PresenceSubscription :: boolean(),
+ RosterGroup :: boolean(),
+ Options :: mod_pubsub:subOptions())
+ -> {result, {default, subscribed, mod_pubsub:subId()}}
+ | {result, {default, subscribed, mod_pubsub:subId(), send_last}}
+ | {result, {default, pending, mod_pubsub:subId()}}
+ %%%
+ | {error, _}
+ | {error, _, binary()}
+).
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey),
- Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
- PendingSubscription = lists:any(fun({pending, _}) -> true;
- (_) -> false
- end, Subscriptions),
- if
- not Authorized ->
- %% JIDs do not match
- {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")};
- Affiliation == outcast ->
- %% Requesting entity is blocked
- {error, ?ERR_FORBIDDEN};
- PendingSubscription ->
- %% Requesting entity has pending subscription
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")};
- (AccessModel == presence) and (not PresenceSubscription) ->
- %% Entity is not authorized to create a subscription (presence subscription required)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
- (AccessModel == roster) and (not RosterGroup) ->
- %% Entity is not authorized to create a subscription (not in roster group)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
- (AccessModel == whitelist) and (not Whitelisted) ->
- %% Node has whitelist access model and entity lacks required affiliation
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- %%ForbiddenAnonymous ->
- %% % Requesting entity is anonymous
- %% {error, ?ERR_FORBIDDEN};
- true ->
- case pubsub_subscription_odbc:subscribe_node(Subscriber, NodeId, Options) of
- {result, SubId} ->
- NewSub = case AccessModel of
- authorize -> pending;
- _ -> subscribed
- end,
- update_subscription(NodeId, SubKey, [{NewSub, SubId} | Subscriptions]),
- case {NewSub, SendLast} of
- {subscribed, never} ->
- {result, {default, subscribed, SubId}};
- {subscribed, _} ->
- {result, {default, subscribed, SubId, send_last}};
- {_, _} ->
- {result, {default, pending, SubId}}
- end;
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
- end
+ Authorized =
+ jlib:jid_tolower(jlib:jid_remove_resource(Sender)) ==
+ GenKey,
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeId, GenKey,
+ SubKey),
+ Whitelisted = lists:member(Affiliation,
+ [member, publisher, owner]),
+ PendingSubscription = lists:any(fun ({pending, _}) ->
+ true;
+ (_) -> false
+ end,
+ Subscriptions),
+ if not Authorized ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)};
+ Affiliation == outcast -> {error, ?ERR_FORBIDDEN};
+ PendingSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"pending-subscription">>)};
+ (AccessModel == presence) and
+ not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ %%ForbiddenAnonymous ->
+ %% % Requesting entity is anonymous
+ %% {error, ?ERR_FORBIDDEN};
+ true ->
+ {result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, NodeId, Options),
+ NewSub = case AccessModel of
+ authorize -> pending;
+ _ -> subscribed
+ end,
+ update_subscription(NodeId, SubKey,
+ [{NewSub, SubId} | Subscriptions]),
+ case {NewSub, SendLast} of
+ {subscribed, never} ->
+ {result, {default, subscribed, SubId}};
+ {subscribed, _} ->
+ {result, {default, subscribed, SubId, send_last}};
+ {_, _} -> {result, {default, pending, SubId}}
+ end
end.
%% @spec (NodeId, Sender, Subscriber, SubId) ->
@@ -357,68 +363,97 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
%% SubId = mod_pubsub:subid()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
+-spec(unsubscribe_node/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Sender :: jid(),
+ Subscriber :: jid(),
+ SubId :: subId())
+ -> {result, default}
+ %
+ | {error, _}
+ | {error, _, binary()}
+).
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
- Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, SubKey),
+ Authorized =
+ jlib:jid_tolower(jlib:jid_remove_resource(Sender)) ==
+ GenKey,
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeId, SubKey),
SubIdExists = case SubId of
- [] -> false;
- List when is_list(List) -> true;
- _ -> false
+ [] -> false;
+ List when is_binary(List) -> true;
+ _ -> false
end,
if
- %% Requesting entity is prohibited from unsubscribing entity
- not Authorized ->
- {error, ?ERR_FORBIDDEN};
- %% Entity did not specify SubId
- %%SubId == "", ?? ->
- %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %% Invalid subscription identifier
- %%InvalidSubId ->
- %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- %% Requesting entity is not a subscriber
- Subscriptions == [] ->
- {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")};
- %% Subid supplied, so use that.
- SubIdExists ->
- Sub = first_in_list(fun(S) ->
- case S of
- {_Sub, SubId} -> true;
- _ -> false
- end
- end, Subscriptions),
- case Sub of
- {value, S} ->
- delete_subscription(SubKey, NodeId, S, Affiliation, Subscriptions),
- {result, default};
- false ->
- {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")}
- end;
- %% Asking to remove all subscriptions to the given node
- SubId == all ->
- [delete_subscription(SubKey, NodeId, S, Affiliation, Subscriptions) || S <- Subscriptions],
- {result, default};
- %% No subid supplied, but there's only one matching
- %% subscription, so use that.
- length(Subscriptions) == 1 ->
- delete_subscription(SubKey, NodeId, hd(Subscriptions), Affiliation, Subscriptions),
- {result, default};
- %% No subid and more than one possible subscription match.
- true ->
- {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}
+ %% Requesting entity is prohibited from unsubscribing entity
+ not Authorized -> {error, ?ERR_FORBIDDEN};
+ %% Entity did not specify SubId
+ %%SubId == "", ?? ->
+ %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %% Invalid subscription identifier
+ %%InvalidSubId ->
+ %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ %% Requesting entity is not a subscriber
+ Subscriptions == [] ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL),
+ <<"not-subscribed">>)};
+ %% Subid supplied, so use that.
+ SubIdExists ->
+ Sub = first_in_list(fun (S) ->
+ case S of
+ {_Sub, SubId} -> true;
+ _ -> false
+ end
+ end,
+ Subscriptions),
+ case Sub of
+ {value, S} ->
+ delete_subscription(SubKey, NodeId, S, Affiliation,
+ Subscriptions),
+ {result, default};
+ false ->
+ {error,
+ ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL),
+ <<"not-subscribed">>)}
+ end;
+ %% Asking to remove all subscriptions to the given node
+ SubId == all ->
+ [delete_subscription(SubKey, NodeId, S, Affiliation,
+ Subscriptions)
+ || S <- Subscriptions],
+ {result, default};
+ %% No subid supplied, but there's only one matching
+ %% subscription, so use that.
+ length(Subscriptions) == 1 ->
+ delete_subscription(SubKey, NodeId, hd(Subscriptions),
+ Affiliation, Subscriptions),
+ {result, default};
+ %% No subid and more than one possible subscription match.
+ true ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}
end.
-delete_subscription(SubKey, NodeId, {Subscription, SubId}, Affiliation, Subscriptions) ->
+%-spec(delete_subscriptions/5 ::
+%(
+% SubKey :: ljid(),
+% NodeIdx :: mod_pubsub:nodeIdx(),
+% _ :: {mod_pubsub:subscription(), mod_pubsub:subId()},
+% SubState :: mod_pubsub:pubsubState(),
+% Subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}])
+% -> ok
+%).
+delete_subscription(SubKey, NodeIdx,
+ {Subscription, SubId}, Affiliation, Subscriptions) ->
NewSubs = Subscriptions -- [{Subscription, SubId}],
- pubsub_subscription_odbc:unsubscribe_node(SubKey, NodeId, SubId),
+ pubsub_subscription_odbc:unsubscribe_node(SubKey, NodeIdx, SubId),
case {Affiliation, NewSubs} of
- {none, []} ->
- % Just a regular subscriber, and this is final item, so
- % delete the state.
- del_state(NodeId, SubKey);
- _ ->
- update_subscription(NodeId, SubKey, NewSubs)
+ {none, []} -> del_state(NodeIdx, SubKey);
+ _ -> update_subscription(NodeIdx, SubKey, NewSubs)
end.
%% @spec (NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
@@ -459,38 +494,46 @@ delete_subscription(SubKey, NodeId, {Subscription, SubId}, Affiliation, Subscrip
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
-publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
+
+-spec(publish_item/6 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ Max_Items :: non_neg_integer(),
+ ItemId :: <<>> | mod_pubsub:itemId(),
+ Payload :: mod_pubsub:payload())
+ -> {result, {default, broadcast, [mod_pubsub:itemId()]}}
+ %%%
+ | {error, _}
+).
+publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey),
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeIdx, GenKey, SubKey),
Subscribed = case PublishModel of
- subscribers -> is_subscribed(Subscriptions);
- _ -> undefined
- end,
- if
- not ((PublishModel == open)
- or ((PublishModel == publishers)
- and ((Affiliation == owner) or (Affiliation == publisher)))
- or (Subscribed == true)) ->
- %% Entity does not have sufficient privileges to publish to node
- {error, ?ERR_FORBIDDEN};
- true ->
- %% TODO: check creation, presence, roster
- if MaxItems > 0 ->
- %% Note: this works cause set_item tries an update before
- %% the insert, and the update just ignore creation field.
- PubId = {now(), SubKey},
- set_item(#pubsub_item{itemid = {ItemId, NodeId},
+ subscribers -> is_subscribed(Subscriptions);
+ _ -> undefined
+ end,
+ if not
+ ((PublishModel == open) or
+ (PublishModel == publishers) and
+ ((Affiliation == owner) or (Affiliation == publisher))
+ or (Subscribed == true)) ->
+ {error, ?ERR_FORBIDDEN};
+ true ->
+ if MaxItems > 0 ->
+ PubId = {now(), SubKey},
+ set_item(#pubsub_item{itemid = {ItemId, NodeIdx},
creation = {now(), GenKey},
modification = PubId,
payload = Payload}),
- Items = [ItemId | itemids(NodeId, GenKey)--[ItemId]],
- {result, {_, OI}} = remove_extra_items(NodeId, MaxItems, Items),
- %% set new item list use useless
- {result, {default, broadcast, OI}};
- true ->
- {result, {default, broadcast, []}}
- end
+ Items = [ItemId | itemids(NodeIdx, GenKey) -- [ItemId]],
+ {result, {_, OI}} = remove_extra_items(NodeIdx, MaxItems, Items),
+ {result, {default, broadcast, OI}};
+ true -> {result, {default, broadcast, []}}
+ end
end.
%% @spec (NodeId, MaxItems, ItemIds) -> {NewItemIds,OldItemIds}
@@ -513,9 +556,7 @@ remove_extra_items(_NodeId, unlimited, ItemIds) ->
remove_extra_items(NodeId, MaxItems, ItemIds) ->
NewItems = lists:sublist(ItemIds, MaxItems),
OldItems = lists:nthtail(length(NewItems), ItemIds),
- %% Remove extra items:
del_items(NodeId, OldItems),
- %% Return the new items list:
{result, {NewItems, OldItems}}.
%% @spec (NodeId, Publisher, PublishModel, ItemId) ->
@@ -528,29 +569,33 @@ remove_extra_items(NodeId, MaxItems, ItemIds) ->
%% @doc <p>Triggers item deletion.</p>
%% <p>Default plugin: The user performing the deletion must be the node owner
%% or a publisher.</p>
-delete_item(NodeId, Publisher, PublishModel, ItemId) ->
+-spec(delete_item/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Publisher :: jid(),
+ PublishModel :: mod_pubsub:publishModel(),
+ ItemId :: <<>> | mod_pubsub:itemId())
+ -> {result, {default, broadcast}}
+ %%%
+ | {error, _}
+).
+delete_item(NodeIdx, Publisher, PublishModel, ItemId) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
- {result, Affiliation} = get_affiliation(NodeId, GenKey),
- Allowed = (Affiliation == publisher) orelse (Affiliation == owner)
- orelse (PublishModel == open)
- orelse case get_item(NodeId, ItemId) of
- {result, #pubsub_item{creation = {_, GenKey}}} -> true;
- _ -> false
- end,
- if
- not Allowed ->
- %% Requesting entity does not have sufficient privileges
- {error, ?ERR_FORBIDDEN};
- true ->
- case del_item(NodeId, ItemId) of
- {updated, 1} ->
- %% set new item list use useless
- {result, {default, broadcast}};
- _ ->
- %% Non-existent node or item
- {error, ?ERR_ITEM_NOT_FOUND}
- end
+ {result, Affiliation} = get_affiliation(NodeIdx, GenKey),
+ Allowed = Affiliation == publisher orelse
+ Affiliation == owner orelse
+ PublishModel == open orelse
+ case get_item(NodeIdx, ItemId) of
+ {result, #pubsub_item{creation = {_, GenKey}}} -> true;
+ _ -> false
+ end,
+ if not Allowed -> {error, ?ERR_FORBIDDEN};
+ true ->
+ case del_item(NodeIdx, ItemId) of
+ {updated, 1} -> {result, {default, broadcast}};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ end
end.
%% @spec (NodeId, Owner) ->
@@ -558,21 +603,27 @@ delete_item(NodeId, Publisher, PublishModel, ItemId) ->
%% {result, {default, broadcast}}
%% NodeId = mod_pubsub:pubsubNodeId()
%% Owner = mod_pubsub:jid()
-purge_node(NodeId, Owner) ->
+-spec(purge_node/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid())
+ -> {result, {default, broadcast}}
+ | {error, _}
+).
+purge_node(NodeIdx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- GenState = get_state(NodeId, GenKey),
+ GenState = get_state(NodeIdx, GenKey),
case GenState of
- #pubsub_state{affiliation = owner} ->
- {result, States} = get_states(NodeId),
- lists:foreach(
- fun(#pubsub_state{items = []}) -> ok;
- (#pubsub_state{items = Items}) -> del_items(NodeId, Items)
- end, States),
- {result, {default, broadcast}};
- _ ->
- %% Entity is not owner
- {error, ?ERR_FORBIDDEN}
+ #pubsub_state{affiliation = owner} ->
+ {result, States} = get_states(NodeIdx),
+ lists:foreach(fun (#pubsub_state{items = []}) -> ok;
+ (#pubsub_state{items = Items}) ->
+ del_items(NodeIdx, Items)
+ end,
+ States),
+ {result, {default, broadcast}};
+ _ -> {error, ?ERR_FORBIDDEN}
end.
%% @spec (Host, JID) -> [{Node,Affiliation}]
@@ -585,60 +636,98 @@ purge_node(NodeId, Owner) ->
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
+-spec(get_entity_affiliations/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]}
+).
get_entity_affiliations(Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- H = ?PUBSUB:escape(Host),
+ H = (?PUBSUB):escape(Host),
J = encode_jid(GenKey),
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select node, type, i.nodeid, affiliation "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid "
- "and jid='", J, "' "
- "and host='", H, "';"]) of
- {selected, ["node", "type", "nodeid", "affiliation"], RItems} ->
- lists:map(fun({N, T, I, A}) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}),
- {Node, decode_affiliation(A)}
- end, RItems);
- _ ->
- []
- end,
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation "
+ "from pubsub_state i, pubsub_node n where "
+ "i.nodeid = n.nodeid and jid='">>,
+ J, <<"' and host='">>, H,
+ <<"';">>])
+ of
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>,
+ <<"affiliation">>],
+ RItems} ->
+ lists:map(fun ({N, T, I, A}) ->
+ Node = nodetree_tree_odbc:raw_to_node(Host,
+ {N,
+ <<"">>,
+ T,
+ I}),
+ {Node, decode_affiliation(A)}
+ end,
+ RItems);
+ _ -> []
+ end,
{result, Reply}.
-get_node_affiliations(NodeId) ->
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select jid, affiliation "
- "from pubsub_state "
- "where nodeid='", NodeId, "';"]) of
- {selected, ["jid", "affiliation"], RItems} ->
- lists:map(fun({J, A}) -> {decode_jid(J), decode_affiliation(A)} end, RItems);
- _ ->
- []
- end,
+-spec(get_node_affiliations/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [{ljid(), mod_pubsub:affiliation()}]}
+).
+get_node_affiliations(NodeIdx) ->
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state "
+ "where nodeid='">>,
+ NodeIdx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"affiliation">>], RItems} ->
+ lists:map(fun ({J, A}) ->
+ {decode_jid(J), decode_affiliation(A)}
+ end,
+ RItems);
+ _ -> []
+ end,
{result, Reply}.
-get_affiliation(NodeId, Owner) ->
+-spec(get_affiliation/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid())
+ -> {result, mod_pubsub:affiliation()}
+).
+
+get_affiliation(NodeIdx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
J = encode_jid(GenKey),
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select affiliation from pubsub_state "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {selected, ["affiliation"], [{A}]} -> decode_affiliation(A);
- _ -> none
- end,
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state "
+ "where nodeid='">>,
+ NodeIdx, <<"' and jid='">>, J,
+ <<"';">>])
+ of
+ {selected, [<<"affiliation">>], [{A}]} ->
+ decode_affiliation(A);
+ _ -> none
+ end,
{result, Reply}.
-set_affiliation(NodeId, Owner, Affiliation) ->
+-spec(set_affiliation/3 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid(),
+ Affiliation :: mod_pubsub:affiliation())
+ -> ok
+).
+set_affiliation(NodeIdx, Owner, Affiliation) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- {_, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey),
+ {_, Subscriptions} = select_affiliation_subscriptions(NodeIdx, GenKey),
case {Affiliation, Subscriptions} of
- {none, none} ->
- del_state(NodeId, GenKey);
- _ ->
- update_affiliation(NodeId, GenKey, Affiliation)
+ {none, none} -> del_state(NodeIdx, GenKey);
+ _ -> update_affiliation(NodeIdx, GenKey, Affiliation)
end.
%% @spec (Host, Owner) -> [{Node,Subscription}]
@@ -651,223 +740,341 @@ set_affiliation(NodeId, Owner, Affiliation) ->
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
+
+-spec(get_entity_subscriptions/2 ::
+(
+ Host :: mod_pubsub:host(),
+ Owner :: jid())
+ -> {result,
+ [{mod_pubsub:pubsubNode(),
+ mod_pubsub:subscription(),
+ mod_pubsub:subId(),
+ ljid()}]
+ }
+).
get_entity_subscriptions(Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- H = ?PUBSUB:escape(Host),
+ H = (?PUBSUB):escape(Host),
SJ = encode_jid(SubKey),
GJ = encode_jid(GenKey),
Query = case SubKey of
- GenKey ->
- ["select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid "
- "and jid like '", GJ, "%' "
- "and host='", H, "';"];
- _ ->
- ["select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n "
- "where i.nodeid = n.nodeid "
- "and jid in ('", SJ, "', '", GJ, "') "
- "and host='", H, "';"]
- end,
+ GenKey ->
+ [<<"select node, type, i.nodeid, jid, subscriptio"
+ "ns from pubsub_state i, pubsub_node "
+ "n where i.nodeid = n.nodeid and jid "
+ "like '">>,
+ GJ, <<"%' and host='">>, H, <<"';">>];
+ _ ->
+ [<<"select node, type, i.nodeid, jid, subscriptio"
+ "ns from pubsub_state i, pubsub_node "
+ "n where i.nodeid = n.nodeid and jid "
+ "in ('">>,
+ SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
+ end,
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected, ["node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
- lists:foldl(fun({N, T, I, J, S}, Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}),
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Node, none, Jid}|Acc];
- Subs ->
- lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, Jid}|Acc2];
- (Sub, Acc2) -> [{Node, Sub, Jid}|Acc2]
- end, Acc, Subs)
- end
- end, [], RItems);
- _ ->
- []
- end,
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>,
+ <<"subscriptions">>],
+ RItems} ->
+ lists:foldl(fun ({N, T, I, J, S}, Acc) ->
+ Node =
+ nodetree_tree_odbc:raw_to_node(Host,
+ {N,
+ <<"">>,
+ T,
+ I}),
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] -> [{Node, none, Jid} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId},
+ Acc2) ->
+ [{Node, Sub,
+ SubId, Jid}
+ | Acc2];
+ (Sub, Acc2) ->
+ [{Node, Sub,
+ Jid}
+ | Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ -> []
+ end,
{result, Reply}.
%% do the same as get_entity_subscriptions but filter result only to
%% nodes having send_last_published_item=on_sub_and_presence
%% as this call avoid seeking node, it must return node and type as well
+-spec(get_entity_subscriptions_for_send_last/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result,
+ [{mod_pubsub:pubsubNode(),
+ mod_pubsub:subscription(),
+ mod_pubsub:subId(),
+ ljid()}]
+ }
+).
+
get_entity_subscriptions_for_send_last(Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
- H = ?PUBSUB:escape(Host),
+ H = (?PUBSUB):escape(Host),
SJ = encode_jid(SubKey),
GJ = encode_jid(GenKey),
Query = case SubKey of
- GenKey ->
- ["select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid "
- "and name='send_last_published_item' and val='on_sub_and_presence' "
- "and jid like '", GJ, "%' "
- "and host='", H, "';"];
- _ ->
- ["select node, type, i.nodeid, jid, subscriptions "
- "from pubsub_state i, pubsub_node n, pubsub_node_option o "
- "where i.nodeid = n.nodeid and n.nodeid = o.nodeid "
- "and name='send_last_published_item' and val='on_sub_and_presence' "
- "and jid in ('", SJ, "', '", GJ, "') "
- "and host='", H, "';"]
- end,
+ GenKey ->
+ [<<"select node, type, i.nodeid, jid, subscriptio"
+ "ns from pubsub_state i, pubsub_node "
+ "n, pubsub_node_option o where i.nodeid "
+ "= n.nodeid and n.nodeid = o.nodeid and "
+ "name='send_last_published_item' and "
+ "val='on_sub_and_presence' and jid like "
+ "'">>,
+ GJ, <<"%' and host='">>, H, <<"';">>];
+ _ ->
+ [<<"select node, type, i.nodeid, jid, subscriptio"
+ "ns from pubsub_state i, pubsub_node "
+ "n, pubsub_node_option o where i.nodeid "
+ "= n.nodeid and n.nodeid = o.nodeid and "
+ "name='send_last_published_item' and "
+ "val='on_sub_and_presence' and jid in "
+ "('">>,
+ SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
+ end,
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
- {selected, ["node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
- lists:foldl(fun({N, T, I, J, S}, Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}),
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Node, none, Jid}|Acc];
- Subs ->
- lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, Jid}|Acc2];
- (Sub, Acc2) -> [{Node, Sub, Jid}|Acc2]
- end, Acc, Subs)
- end
- end, [], RItems);
- _ ->
- []
- end,
+ {selected,
+ [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>,
+ <<"subscriptions">>],
+ RItems} ->
+ lists:foldl(fun ({N, T, I, J, S}, Acc) ->
+ Node =
+ nodetree_tree_odbc:raw_to_node(Host,
+ {N,
+ <<"">>,
+ T,
+ I}),
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] -> [{Node, none, Jid} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Node, Sub, SubId, Jid}| Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ -> []
+ end,
{result, Reply}.
-get_node_subscriptions(NodeId) ->
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select jid, subscriptions "
- "from pubsub_state "
- "where nodeid='", NodeId, "';"]) of
- {selected, ["jid", "subscriptions"], RItems} ->
- lists:foldl(fun({J, S}, Acc) ->
- Jid = decode_jid(J),
- case decode_subscriptions(S) of
- [] ->
- [{Jid, none}|Acc];
- Subs ->
- lists:foldl(fun({Sub, SubId}, Acc2) -> [{Jid, Sub, SubId}|Acc2];
- (Sub, Acc2) -> [{Jid, Sub}|Acc2]
- end, Acc, Subs)
- end
- end, [], RItems);
- _ ->
- []
- end,
+-spec(get_node_subscriptions/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}]}
+).
+get_node_subscriptions(NodeIdx) ->
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
+ "where nodeid='">>,
+ NodeIdx, <<"';">>])
+ of
+ {selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
+ lists:foldl(fun ({J, S}, Acc) ->
+ Jid = decode_jid(J),
+ case decode_subscriptions(S) of
+ [] -> [{Jid, none} | Acc];
+ Subs ->
+ lists:foldl(fun ({Sub, SubId}, Acc2) ->
+ [{Jid, Sub, SubId} | Acc2]
+ end,
+ Acc, Subs)
+ end
+ end,
+ [], RItems);
+ _ -> []
+ end,
{result, Reply}.
-get_subscriptions(NodeId, Owner) ->
+-spec(get_subscriptions/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: ljid())
+ -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]}
+).
+get_subscriptions(NodeIdx, Owner) ->
SubKey = jlib:jid_tolower(Owner),
J = encode_jid(SubKey),
- Reply = case catch ejabberd_odbc:sql_query_t(
- ["select subscriptions from pubsub_state "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {selected, ["subscriptions"], [{S}]} -> decode_subscriptions(S);
- _ -> []
- end,
+ Reply = case catch
+ ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state "
+ "where nodeid='">>,
+ NodeIdx, <<"' and jid='">>, J,
+ <<"';">>])
+ of
+ {selected, [<<"subscriptions">>], [{S}]} ->
+ decode_subscriptions(S);
+ _ -> []
+ end,
{result, Reply}.
-set_subscriptions(NodeId, Owner, Subscription, SubId) ->
+-spec(set_subscriptions/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid(),
+ Subscription :: mod_pubsub:subscription(),
+ SubId :: mod_pubsub:subId())
+ -> _
+ %%%
+ | {error, xmlel()}
+).
+set_subscriptions(NodeIdx, Owner, Subscription, SubId) ->
SubKey = jlib:jid_tolower(Owner),
- SubState = get_state_without_itemids(NodeId, SubKey),
+ SubState = get_state_without_itemids(NodeIdx, SubKey),
case {SubId, SubState#pubsub_state.subscriptions} of
- {_, []} ->
- case Subscription of
- none -> {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "not-subscribed")};
- _ -> new_subscription(NodeId, Owner, Subscription, SubState)
- end;
- {"", [{_, SID}]} ->
- case Subscription of
- none -> unsub_with_subid(NodeId, SID, SubState);
- _ -> replace_subscription({Subscription, SID}, SubState)
- end;
- {"", [_|_]} ->
- {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- _ ->
- case Subscription of
- none -> unsub_with_subid(NodeId, SubId, SubState);
- _ -> replace_subscription({Subscription, SubId}, SubState)
- end
+ {_, []} ->
+ case Subscription of
+ none ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST),
+ <<"not-subscribed">>)};
+ _ ->
+ new_subscription(NodeIdx, Owner, Subscription, SubState)
+ end;
+ {<<"">>, [{_, SID}]} ->
+ case Subscription of
+ none -> unsub_with_subid(NodeIdx, SID, SubState);
+ _ -> replace_subscription({Subscription, SID}, SubState)
+ end;
+ {<<"">>, [_ | _]} ->
+ {error,
+ ?ERR_EXTENDED((?ERR_BAD_REQUEST),
+ <<"subid-required">>)};
+ _ ->
+ case Subscription of
+ none -> unsub_with_subid(NodeIdx, SubId, SubState);
+ _ ->
+ replace_subscription({Subscription, SubId}, SubState)
+ end
end.
+-spec(replace_subscription/2 ::
+(
+ NewSub :: {mod_pubsub:subscription(), mod_pubsub:subId()},
+ SubState :: mod_pubsub:pubsubState())
+ -> {result, []}
+).
replace_subscription(NewSub, SubState) ->
- NewSubs = replace_subscription(NewSub,
- SubState#pubsub_state.subscriptions, []),
+ NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []),
set_state(SubState#pubsub_state{subscriptions = NewSubs}).
-replace_subscription(_, [], Acc) ->
- Acc;
+replace_subscription(_, [], Acc) -> Acc;
replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]).
-new_subscription(NodeId, Owner, Subscription, SubState) ->
- case pubsub_subscription_odbc:subscribe_node(Owner, NodeId, []) of
- {result, SubId} ->
- Subscriptions = SubState#pubsub_state.subscriptions,
- set_state(SubState#pubsub_state{subscriptions = [{Subscription, SubId} | Subscriptions]}),
- {Subscription, SubId};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+-spec(new_subscription/4 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ Owner :: jid(),
+ Subscription :: mod_pubsub:subscription(),
+ SubState :: mod_pubsub:pubsubState())
+ -> {mod_pubsub:subscription(), mod_pubsub:subId()}
+ %%%
+ | {error, xmlel()}
+).
+
+new_subscription(NodeIdx, Owner, Subscription, SubState) ->
+ case pubsub_subscription_odbc:subscribe_node(Owner, NodeIdx, []) of
+ {result, SubId} ->
+ Subscriptions = SubState#pubsub_state.subscriptions,
+ set_state(SubState#pubsub_state{subscriptions =
+ [{Subscription, SubId} | Subscriptions]}),
+ {Subscription, SubId};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-unsub_with_subid(NodeId, SubId, SubState) ->
+-spec(unsub_with_subid/3 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ SubId :: mod_pubsub:subId(),
+ SubState :: mod_pubsub:pubsubState())
+ -> ok
+).
+unsub_with_subid(NodeIdx, SubId, SubState) ->
pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid,
- NodeId, SubId),
- NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID end,
+ NodeIdx, SubId),
+ NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID
+ end,
SubState#pubsub_state.subscriptions),
case {NewSubs, SubState#pubsub_state.affiliation} of
- {[], none} ->
- del_state(NodeId, element(1, SubState#pubsub_state.stateid));
- _ ->
- set_state(SubState#pubsub_state{subscriptions = NewSubs})
+ {[], none} ->
+ del_state(NodeIdx,
+ element(1, SubState#pubsub_state.stateid));
+ _ ->
+ set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
-
%% @spec (Host, Owner) -> {result, [Node]} | {error, Reason}
%% Host = host()
%% Owner = jid()
%% Node = pubsubNode()
%% @doc <p>Returns a list of Owner's nodes on Host with pending
%% subscriptions.</p>
+-spec(get_pending_nodes/2 ::
+(
+ Host :: mod_pubsub:hostPubsub(),
+ Owner :: jid())
+ -> {result, [mod_pubsub:nodeId()]}
+).
get_pending_nodes(Host, Owner) ->
GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)),
- States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'},
- affiliation = owner,
- _ = '_'}),
- NodeIDs = [ID || #pubsub_state{stateid = {_, ID}} <- States],
- NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
- [{nodetree, N}] -> N;
- _ -> nodetree_tree_odbc
+ States = mnesia:match_object(#pubsub_state{stateid =
+ {GenKey, '_'},
+ affiliation = owner, _ = '_'}),
+ NodeIDs = [ID
+ || #pubsub_state{stateid = {_, ID}} <- States],
+ NodeTree = case catch
+ ets:lookup(gen_mod:get_module_proc(Host, config),
+ nodetree)
+ of
+ [{nodetree, N}] -> N;
+ _ -> nodetree_tree_odbc
end,
- Reply = mnesia:foldl(fun(#pubsub_state{stateid = {_, NID}} = S, Acc) ->
- case lists:member(NID, NodeIDs) of
- true ->
- case get_nodes_helper(NodeTree, S) of
- {value, Node} -> [Node | Acc];
- false -> Acc
- end;
- false ->
- Acc
- end
- end, [], pubsub_state),
+ Reply = mnesia:foldl(fun (#pubsub_state{stateid =
+ {_, NID}} =
+ S,
+ Acc) ->
+ case lists:member(NID, NodeIDs) of
+ true ->
+ case get_nodes_helper(NodeTree, S) of
+ {value, Node} -> [Node | Acc];
+ false -> Acc
+ end;
+ false -> Acc
+ end
+ end,
+ [], pubsub_state),
{result, Reply}.
get_nodes_helper(NodeTree,
- #pubsub_state{stateid = {_, N}, subscriptions = Subs}) ->
+ #pubsub_state{stateid = {_, N},
+ subscriptions = Subs}) ->
HasPending = fun ({pending, _}) -> true;
- (pending) -> true;
- (_) -> false
+ (pending) -> true;
+ (_) -> false
end,
case lists:any(HasPending, Subs) of
- true ->
- case NodeTree:get_node(N) of
- #pubsub_node{nodeid = {_, Node}} ->
- {value, Node};
- _ ->
- false
- end;
- false ->
- false
+ true ->
+ case NodeTree:get_node(N) of
+ #pubsub_node{nodeid = {_, Node}} -> {value, Node};
+ _ -> false
+ end;
+ false -> false
end.
%% @spec (NodeId) -> [States] | []
@@ -882,20 +1089,29 @@ get_nodes_helper(NodeTree,
%% they can implement this function like this:
%% ```get_states(NodeId) ->
%% node_default:get_states(NodeId).'''</p>
-get_states(NodeId) ->
- case catch ejabberd_odbc:sql_query_t(
- ["select jid, affiliation, subscriptions "
- "from pubsub_state "
- "where nodeid='", NodeId, "';"]) of
- {selected, ["jid", "affiliation", "subscriptions"], RItems} ->
- {result, lists:map(fun({SJID, Affiliation, Subscriptions}) ->
- #pubsub_state{stateid = {decode_jid(SJID), NodeId},
- items = itemids(NodeId, SJID),
- affiliation = decode_affiliation(Affiliation),
- subscriptions = decode_subscriptions(Subscriptions)}
- end, RItems)};
- _ ->
- {result, []}
+-spec(get_states/1 ::
+(
+ NodeIdx::mod_pubsub:nodeIdx())
+ -> {result, [mod_pubsub:pubsubState()]}
+).
+get_states(NodeIdx) ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ "from pubsub_state where nodeid='">>,
+ NodeIdx, <<"';">>])
+ of
+ {selected,
+ [<<"jid">>, <<"affiliation">>, <<"subscriptions">>],
+ RItems} ->
+ {result,
+ lists:map(fun ({SJID, Affiliation, Subscriptions}) ->
+ #pubsub_state{stateid = {decode_jid(SJID), NodeIdx},
+ items = itemids(NodeIdx, SJID),
+ affiliation = decode_affiliation(Affiliation),
+ subscriptions = decode_subscriptions(Subscriptions)}
+ end,
+ RItems)};
+ _ -> {result, []}
end.
%% @spec (NodeId, JID) -> [State] | []
@@ -903,50 +1119,78 @@ get_states(NodeId) ->
%% JID = mod_pubsub:jid()
%% State = mod_pubsub:pubsubItems()
%% @doc <p>Returns a state (one state list), given its reference.</p>
-get_state(NodeId, JID) ->
- State = get_state_without_itemids(NodeId, JID),
+
+-spec(get_state/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ JID :: ljid())
+ -> mod_pubsub:pubsubState()
+).
+get_state(NodeIdx, JID) ->
+ State = get_state_without_itemids(NodeIdx, JID),
{SJID, _} = State#pubsub_state.stateid,
- State#pubsub_state{items = itemids(NodeId, SJID)}.
-get_state_without_itemids(NodeId, JID) ->
+ State#pubsub_state{items = itemids(NodeIdx, SJID)}.
+
+-spec(get_state_without_itemids/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ JID :: jid())
+ -> mod_pubsub:pubsubState()
+).
+get_state_without_itemids(NodeIdx, JID) ->
J = encode_jid(JID),
- case catch ejabberd_odbc:sql_query_t(
- ["select jid, affiliation, subscriptions "
- "from pubsub_state "
- "where jid='", J, "' "
- "and nodeid='", NodeId, "';"]) of
- {selected, ["jid", "affiliation", "subscriptions"], [{SJID, Affiliation, Subscriptions}]} ->
- #pubsub_state{stateid = {decode_jid(SJID), NodeId},
- affiliation = decode_affiliation(Affiliation),
- subscriptions = decode_subscriptions(Subscriptions)};
- _ ->
- #pubsub_state{stateid={JID, NodeId}}
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ "from pubsub_state where jid='">>,
+ J, <<"' and nodeid='">>, NodeIdx,
+ <<"';">>])
+ of
+ {selected,
+ [<<"jid">>, <<"affiliation">>, <<"subscriptions">>],
+ [{SJID, Affiliation, Subscriptions}]} ->
+ #pubsub_state{stateid = {decode_jid(SJID), NodeIdx},
+ affiliation = decode_affiliation(Affiliation),
+ subscriptions = decode_subscriptions(Subscriptions)};
+ _ -> #pubsub_state{stateid = {JID, NodeIdx}}
end.
%% @spec (State) -> ok | {error, Reason::stanzaError()}
%% State = mod_pubsub:pubsubStates()
%% @doc <p>Write a state into database.</p>
+
+-spec(set_state/1 ::
+(
+ State :: mod_pubsub:pubsubState())
+ -> {result, []}
+).
set_state(State) ->
- {_, NodeId} = State#pubsub_state.stateid,
- set_state(NodeId, State).
-set_state(NodeId, State) ->
- %% NOTE: in odbc version, as we do not handle item list,
- %% we just need to update affiliation and subscription
- %% cause {JID,NodeId} is the key. if it does not exists, then we insert it.
- %% MySQL can be optimized using INSERT ... ON DUPLICATE KEY as well
+ {_, NodeIdx} = State#pubsub_state.stateid,
+ set_state(NodeIdx, State).
+
+-spec(set_state/2 ::
+(
+ NodeIdx :: mod_pubsub:nodeIdx(),
+ State :: mod_pubsub:pubsubState())
+ -> {result, []}
+).
+set_state(NodeIdx, State) ->
{JID, _} = State#pubsub_state.stateid,
J = encode_jid(JID),
S = encode_subscriptions(State#pubsub_state.subscriptions),
A = encode_affiliation(State#pubsub_state.affiliation),
- case catch ejabberd_odbc:sql_query_t(
- ["update pubsub_state "
- "set subscriptions='", S, "', affiliation='", A, "' "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {updated, 1} ->
- ok;
- _ ->
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('", NodeId, "', '", J, "', '", A, "', '", S, "');"])
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>,
+ S, <<"', affiliation='">>, A,
+ <<"' where nodeid='">>, NodeIdx,
+ <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, "
+ "affiliation, subscriptions) values('">>,
+ NodeIdx, <<"', '">>, J, <<"', '">>, A,
+ <<"', '">>, S, <<"');">>])
end,
{result, []}.
@@ -956,10 +1200,9 @@ set_state(NodeId, State) ->
%% @doc <p>Delete a state from database.</p>
del_state(NodeId, JID) ->
J = encode_jid(JID),
- catch ejabberd_odbc:sql_query_t(
- ["delete from pubsub_state "
- "where jid='", J, "' "
- "and nodeid='", NodeId, "';"]),
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>,
+ J, <<"' and nodeid='">>, NodeId, <<"';">>]),
ok.
%% @spec (NodeId, From) -> {[Items],RsmOut} | []
@@ -976,139 +1219,179 @@ del_state(NodeId, JID) ->
%% ```get_items(NodeId, From) ->
%% node_default:get_items(NodeId, From).'''</p>
get_items(NodeId, _From) ->
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid, publisher, creation, modification, payload "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "order by modification desc;"]) of
- {selected, ["itemid", "publisher", "creation", "modification", "payload"], RItems} ->
- {result, lists:map(fun(RItem) -> raw_to_item(NodeId, RItem) end, RItems)};
- _ ->
- {result, []}
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>,
+ NodeId,
+ <<"' order by modification desc;">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>,
+ <<"modification">>, <<"payload">>],
+ RItems} ->
+ {result,
+ lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) end,
+ RItems)};
+ _ -> {result, []}
end.
-get_items(NodeId, From, none) ->
- MaxItems = case catch ejabberd_odbc:sql_query_t(
- ["select val from pubsub_node_option "
- "where nodeid='", NodeId, "' "
- "and name='max_items';"]) of
- {selected, ["val"], [{Value}]} ->
- Tokens = element(2, erl_scan:string(Value++".")),
- element(2, erl_parse:parse_term(Tokens));
- _ ->
- ?MAXITEMS
- end,
- get_items(NodeId, From, #rsm_in{max=MaxItems});
-get_items(NodeId, _From, #rsm_in{max=M, direction=Direction, id=I, index=IncIndex})->
- Max = ?PUBSUB:escape(i2l(M)),
-
- {Way, Order} = case Direction of
- aft -> {"<", "desc"};
- before when I == [] -> {"is not", "asc"};
- before -> {">", "asc"};
- _ when IncIndex =/= undefined -> {"<", "desc"}; % using index
- _ -> {"is not", "desc"}% Can be better
- end,
- [AttrName, Id] = case I of
- undefined when IncIndex =/= undefined ->
- case catch ejabberd_odbc:sql_query_t(
- ["select modification from pubsub_item pi "
- "where exists ( "
- "select count(*) as count1 "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and modification > pi.modification "
- "having count1 = ",?PUBSUB:escape(i2l(IncIndex))," );"]) of
- {selected, [_], [{O}]} -> ["modification", "'"++O++"'"];
- _ -> ["modification", "null"]
- end;
- undefined -> ["modification", "null"];
- [] -> ["modification", "null"];
- I -> [A, B] = string:tokens(?PUBSUB:escape(i2l(I)), "@"),
- [A, "'"++B++"'"]
- end,
- Count= case catch ejabberd_odbc:sql_query_t(
- ["select count(*) "
- "from pubsub_item "
- "where nodeid='", NodeId, "';"]) of
- {selected, [_], [{C}]} -> C;
- _ -> "0"
- end,
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid, publisher, creation, modification, payload "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and ", AttrName," ", Way, " ", Id, " "
- "order by ", AttrName," ", Order," limit ", i2l(Max)," ;"]) of
- {selected, ["itemid", "publisher", "creation", "modification", "payload"], RItems} ->
- case length(RItems) of
- 0 -> {result, {[], #rsm_out{count=Count}}};
- _ ->
- {_, _, _, F, _} = hd(RItems),
- Index = case catch ejabberd_odbc:sql_query_t(
- ["select count(*) "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and ", AttrName," > '", F, "';"]) of
- %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
- {selected, [_], [{In}]} -> In;
- _ -> "0"
- end,
- %{F, _} = string:to_integer(FStr),
- {_, _, _, L, _} = lists:last(RItems),
- RsmOut = #rsm_out{count=Count, index=Index, first="modification@"++F, last="modification@"++i2l(L)},
- {result, {lists:map(fun(RItem) -> raw_to_item(NodeId, RItem) end, RItems), RsmOut}}
- end;
- _ ->
- {result, {[], none}}
+get_items(NodeId, From, none) ->
+ MaxItems = case catch
+ ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option where "
+ "nodeid='">>,
+ NodeId,
+ <<"' and name='max_items';">>])
+ of
+ {selected, [<<"val">>], [{Value}]} ->
+ Tokens = element(2,
+ erl_scan:string(<<Value/binary, ".">>)),
+ element(2, erl_parse:parse_term(Tokens));
+ _ -> ?MAXITEMS
+ end,
+ get_items(NodeId, From, #rsm_in{max = MaxItems});
+get_items(NodeId, _From,
+ #rsm_in{max = M, direction = Direction, id = I,
+ index = IncIndex}) ->
+ Max = (?PUBSUB):escape(i2l(M)),
+ {Way, Order} = case Direction of
+ aft -> {<<"<">>, <<"desc">>};
+ before when I == [] -> {<<"is not">>, <<"asc">>};
+ before -> {<<">">>, <<"asc">>};
+ _ when IncIndex =/= undefined ->
+ {<<"<">>, <<"desc">>}; % using index
+ _ -> {<<"is not">>, <<"desc">>}% Can be better
+ end,
+ [AttrName, Id] = case I of
+ undefined when IncIndex =/= undefined ->
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item "
+ "pi where exists ( select count(*) as "
+ "count1 from pubsub_item where nodeid='">>,
+ NodeId,
+ <<"' and modification > pi.modification "
+ "having count1 = ">>,
+ (?PUBSUB):escape(i2l(IncIndex)),
+ <<" );">>])
+ of
+ {selected, [_], [{O}]} ->
+ [<<"modification">>, <<"'", O/binary, "'">>];
+ _ -> [<<"modification">>, <<"null">>]
+ end;
+ undefined -> [<<"modification">>, <<"null">>];
+ [] -> [<<"modification">>, <<"null">>];
+ I ->
+ [A, B] = str:tokens((?PUBSUB):escape(i2l(I)),
+ <<"@">>),
+ [A, <<"'", B/binary, "'">>]
+ end,
+ Count = case catch
+ ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where "
+ "nodeid='">>,
+ NodeId, <<"';">>])
+ of
+ {selected, [_], [{C}]} -> C;
+ _ -> <<"0">>
+ end,
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>,
+ NodeId, <<"' and ">>, AttrName, <<" ">>,
+ Way, <<" ">>, Id, <<" order by ">>,
+ AttrName, <<" ">>, Order, <<" limit ">>,
+ i2l(Max), <<" ;">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>,
+ <<"modification">>, <<"payload">>],
+ RItems} ->
+ case str:len(RItems) of
+ 0 -> {result, {[], #rsm_out{count = Count}}};
+ _ ->
+ {_, _, _, F, _} = hd(RItems),
+ Index = case catch
+ ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where "
+ "nodeid='">>,
+ NodeId, <<"' and ">>,
+ AttrName, <<" > '">>,
+ F, <<"';">>])
+ of
+ %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
+ {selected, [_], [{In}]} -> In;
+ _ -> <<"0">>
+ end,
+ {_, _, _, L, _} = lists:last(RItems),
+ RsmOut = #rsm_out{count = Count, index = Index,
+ first = <<"modification@", F/binary>>,
+ last = <<"modification@", (i2l(L))/binary>>},
+ {result,
+ {lists:map(fun (RItem) -> raw_to_item(NodeId, RItem)
+ end,
+ RItems),
+ RsmOut}}
+ end;
+ _ -> {result, {[], none}}
end.
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId) ->
+ get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, none).
+
+get_items(NodeId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, _SubId, RSM) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey),
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if
- %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- Affiliation == outcast ->
- %% Requesting entity is blocked
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and (not PresenceSubscription) ->
- %% Entity is not authorized to create a subscription (presence subscription required)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
- (AccessModel == roster) and (not RosterGroup) ->
- %% Entity is not authorized to create a subscription (not in roster group)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
- (AccessModel == whitelist) and (not Whitelisted) ->
- %% Node has whitelist access model and entity lacks required affiliation
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- (AccessModel == authorize) and (not Whitelisted) ->
- %% Node has authorize access model
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_items(NodeId, JID, RSM)
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeId, GenKey,
+ SubKey),
+ Whitelisted = can_fetch_item(Affiliation,
+ Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ Affiliation == outcast -> {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and
+ not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true -> get_items(NodeId, JID, RSM)
end.
get_last_items(NodeId, _From, Count) ->
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid, publisher, creation, modification, payload "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "order by modification desc limit ", i2l(Count), ";"]) of
- {selected, ["itemid", "publisher", "creation", "modification", "payload"], RItems} ->
- {result, lists:map(fun(RItem) -> raw_to_item(NodeId, RItem) end, RItems)};
- _ ->
- {result, []}
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>,
+ NodeId,
+ <<"' order by modification desc limit ">>,
+ i2l(Count), <<";">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>,
+ <<"modification">>, <<"payload">>],
+ RItems} ->
+ {result,
+ lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) end,
+ RItems)};
+ _ -> {result, []}
end.
%% @spec (NodeId, ItemId) -> [Item] | []
@@ -1117,49 +1400,56 @@ get_last_items(NodeId, _From, Count) ->
%% Item = mod_pubsub:pubsubItems()
%% @doc <p>Returns an item (one item list), given its reference.</p>
get_item(NodeId, ItemId) ->
- I = ?PUBSUB:escape(ItemId),
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid, publisher, creation, modification, payload "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and itemid='", I,"';"]) of
- {selected, ["itemid", "publisher", "creation", "modification", "payload"], [RItem]} ->
- {result, raw_to_item(NodeId, RItem)};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ I = (?PUBSUB):escape(ItemId),
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ "modification, payload from pubsub_item "
+ "where nodeid='">>,
+ NodeId, <<"' and itemid='">>, I,
+ <<"';">>])
+ of
+ {selected,
+ [<<"itemid">>, <<"publisher">>, <<"creation">>,
+ <<"modification">>, <<"payload">>],
+ [RItem]} ->
+ {result, raw_to_item(NodeId, RItem)};
+ _ -> {error, ?ERR_ITEM_NOT_FOUND}
end.
-get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
+
+get_item(NodeId, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, _SubId) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
- {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey),
- Whitelisted = can_fetch_item(Affiliation, Subscriptions),
- if
- %%SubId == "", ?? ->
- %% Entity has multiple subscriptions to the node but does not specify a subscription ID
- %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
- %%InvalidSubId ->
- %% Entity is subscribed but specifies an invalid subscription ID
- %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
- Affiliation == outcast ->
- %% Requesting entity is blocked
- {error, ?ERR_FORBIDDEN};
- (AccessModel == presence) and (not PresenceSubscription) ->
- %% Entity is not authorized to create a subscription (presence subscription required)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
- (AccessModel == roster) and (not RosterGroup) ->
- %% Entity is not authorized to create a subscription (not in roster group)
- {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
- (AccessModel == whitelist) and (not Whitelisted) ->
- %% Node has whitelist access model and entity lacks required affiliation
- {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- (AccessModel == authorize) and (not Whitelisted) ->
- %% Node has authorize access model
- {error, ?ERR_FORBIDDEN};
- %%MustPay ->
- %% % Payment is required for a subscription
- %% {error, ?ERR_PAYMENT_REQUIRED};
- true ->
- get_item(NodeId, ItemId)
+ {Affiliation, Subscriptions} =
+ select_affiliation_subscriptions(NodeId, GenKey,
+ SubKey),
+ Whitelisted = can_fetch_item(Affiliation,
+ Subscriptions),
+ if %%SubId == "", ?? ->
+ %% Entity has multiple subscriptions to the node but does not specify a subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+ %%InvalidSubId ->
+ %% Entity is subscribed but specifies an invalid subscription ID
+ %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ Affiliation == outcast -> {error, ?ERR_FORBIDDEN};
+ (AccessModel == presence) and
+ not PresenceSubscription ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"presence-subscription-required">>)};
+ (AccessModel == roster) and not RosterGroup ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED),
+ <<"not-in-roster-group">>)};
+ (AccessModel == whitelist) and not Whitelisted ->
+ {error,
+ ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)};
+ (AccessModel == authorize) and not Whitelisted ->
+ {error, ?ERR_FORBIDDEN};
+ %%MustPay ->
+ %% % Payment is required for a subscription
+ %% {error, ?ERR_PAYMENT_REQUIRED};
+ true -> get_item(NodeId, ItemId)
end.
%% @spec (Item) -> ok | {error, Reason::stanzaError()}
@@ -1167,26 +1457,36 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
%% @doc <p>Write an item into database.</p>
set_item(Item) ->
{ItemId, NodeId} = Item#pubsub_item.itemid,
- I = ?PUBSUB:escape(ItemId),
+ I = (?PUBSUB):escape(ItemId),
{C, _} = Item#pubsub_item.creation,
{M, JID} = Item#pubsub_item.modification,
P = encode_jid(JID),
Payload = Item#pubsub_item.payload,
- XML = ?PUBSUB:escape(lists:flatten(lists:map(fun(X) -> xml:element_to_string(X) end, Payload))),
- S = fun({T1, T2, T3}) ->
- lists:flatten([i2l(T1, 6), ":", i2l(T2, 6), ":", i2l(T3, 6)])
+ XML = (?PUBSUB):escape(lists:flatten(lists:map(fun
+ (X) ->
+ xml:element_to_binary(X)
+ end,
+ Payload))),
+ S = fun ({T1, T2, T3}) ->
+ lists:flatten([i2l(T1, 6), <<":">>, i2l(T2, 6), <<":">>,
+ i2l(T3, 6)])
end,
- case catch ejabberd_odbc:sql_query_t(
- ["update pubsub_item "
- "set publisher='", P, "', modification='", S(M), "', payload='", XML, "' "
- "where nodeid='", NodeId, "' and itemid='", I, "';"]) of
- {updated, 1} ->
- ok;
- _ ->
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_item "
- "(nodeid, itemid, publisher, creation, modification, payload) "
- "values('", NodeId, "', '", I, "', '", P, "', '", S(C), "', '", S(M), "', '", XML, "');"])
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>,
+ P, <<"', modification='">>, S(M),
+ <<"', payload='">>, XML,
+ <<"' where nodeid='">>, NodeId,
+ <<"' and itemid='">>, I, <<"';">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
+ "publisher, creation, modification, payload) "
+ "values('">>,
+ NodeId, <<"', '">>, I, <<"', '">>, P,
+ <<"', '">>, S(C), <<"', '">>, S(M),
+ <<"', '">>, XML, <<"');">>])
end,
{result, []}.
@@ -1195,161 +1495,181 @@ set_item(Item) ->
%% ItemId = string()
%% @doc <p>Delete an item from database.</p>
del_item(NodeId, ItemId) ->
- I = ?PUBSUB:escape(ItemId),
- catch ejabberd_odbc:sql_query_t(
- ["delete from pubsub_item "
- "where itemid='", I, "' "
- "and nodeid='", NodeId, "';"]).
-del_items(_, []) ->
- ok;
-del_items(NodeId, [ItemId]) ->
- del_item(NodeId, ItemId);
-del_items(NodeId, ItemIds) ->
- I = string:join([["'", ?PUBSUB:escape(X), "'"] || X <- ItemIds], ","),
- catch ejabberd_odbc:sql_query_t(
- ["delete from pubsub_item "
- "where itemid in (", I, ") "
- "and nodeid='", NodeId, "';"]).
+ I = (?PUBSUB):escape(ItemId),
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>,
+ I, <<"' and nodeid='">>, NodeId, <<"';">>]).
+del_items(_, []) -> ok;
+del_items(NodeId, [ItemId]) -> del_item(NodeId, ItemId);
+del_items(NodeId, ItemIds) ->
%% @doc <p>Return the name of the node if known: Default is to return
%% node id.</p>
-get_item_name(_Host, _Node, Id) ->
- Id.
+ I = str:join([[<<"'">>, (?PUBSUB):escape(X), <<"'">>]
+ || X <- ItemIds],
+ <<",">>),
+ catch
+ ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid "
+ "in (">>,
+ I, <<") and nodeid='">>, NodeId, <<"';">>]).
-node_to_path(Node) ->
- string:tokens(binary_to_list(Node), "/").
+get_item_name(_Host, _Node, Id) -> Id.
-path_to_node([]) ->
- <<>>;
-path_to_node(Path) ->
- list_to_binary(string:join([""|Path], "/")).
+node_to_path(Node) -> str:tokens((Node), <<"/">>).
+path_to_node([]) -> <<>>;
+path_to_node(Path) ->
%% @spec (Affiliation, Subscription) -> true | false
%% Affiliation = owner | member | publisher | outcast | none
%% Subscription = subscribed | none
%% @doc Determines if the combination of Affiliation and Subscribed
%% are allowed to get items from a node.
-can_fetch_item(owner, _) -> true;
-can_fetch_item(member, _) -> true;
-can_fetch_item(publisher, _) -> true;
-can_fetch_item(outcast, _) -> false;
-can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions);
+ iolist_to_binary(str:join([<<"">> | Path], <<"/">>)).
+
+can_fetch_item(owner, _) -> true;
+can_fetch_item(member, _) -> true;
+can_fetch_item(publisher, _) -> true;
+can_fetch_item(outcast, _) -> false;
+can_fetch_item(none, Subscriptions) ->
+ is_subscribed(Subscriptions);
can_fetch_item(_Affiliation, _Subscription) -> false.
is_subscribed(Subscriptions) ->
lists:any(fun ({subscribed, _SubId}) -> true;
- (_) -> false
- end, Subscriptions).
+ (_) -> false
+ end,
+ Subscriptions).
%% Returns the first item where Pred() is true in List
-first_in_list(_Pred, []) ->
- false;
+first_in_list(_Pred, []) -> false;
first_in_list(Pred, [H | T]) ->
case Pred(H) of
- true -> {value, H};
- _ -> first_in_list(Pred, T)
+ true -> {value, H};
+ _ -> first_in_list(Pred, T)
end.
itemids(NodeId, {U, S, R}) ->
itemids(NodeId, encode_jid({U, S, R}));
itemids(NodeId, SJID) ->
- case catch ejabberd_odbc:sql_query_t(
- ["select itemid "
- "from pubsub_item "
- "where nodeid='", NodeId, "' "
- "and publisher like '", SJID, "%' "
- "order by modification desc;"]) of
- {selected, ["itemid"], RItems} ->
- lists:map(fun({ItemId}) -> ItemId end, RItems);
- _ ->
- []
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where "
+ "nodeid='">>,
+ NodeId, <<"' and publisher like '">>,
+ SJID,
+ <<"%' order by modification desc;">>])
+ of
+ {selected, [<<"itemid">>], RItems} ->
+ lists:map(fun ({ItemId}) -> ItemId end, RItems);
+ _ -> []
end.
select_affiliation_subscriptions(NodeId, JID) ->
J = encode_jid(JID),
- case catch ejabberd_odbc:sql_query_t(
- ["select affiliation,subscriptions from pubsub_state "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {selected, ["affiliation", "subscriptions"], [{A, S}]} ->
- {decode_affiliation(A), decode_subscriptions(S)};
- _ ->
- {none, []}
+ case catch
+ ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from "
+ "pubsub_state where nodeid='">>,
+ NodeId, <<"' and jid='">>, J, <<"';">>])
+ of
+ {selected, [<<"affiliation">>, <<"subscriptions">>],
+ [{A, S}]} ->
+ {decode_affiliation(A), decode_subscriptions(S)};
+ _ -> {none, []}
end.
+
select_affiliation_subscriptions(NodeId, JID, JID) ->
select_affiliation_subscriptions(NodeId, JID);
-select_affiliation_subscriptions(NodeId, GenKey, SubKey) ->
+select_affiliation_subscriptions(NodeId, GenKey,
+ SubKey) ->
{result, Affiliation} = get_affiliation(NodeId, GenKey),
- {result, Subscriptions} = get_subscriptions(NodeId, SubKey),
+ {result, Subscriptions} = get_subscriptions(NodeId,
+ SubKey),
{Affiliation, Subscriptions}.
update_affiliation(NodeId, JID, Affiliation) ->
J = encode_jid(JID),
A = encode_affiliation(Affiliation),
- case catch ejabberd_odbc:sql_query_t(
- ["update pubsub_state "
- "set affiliation='", A, "' "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {updated, 1} ->
- ok;
- _ ->
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('", NodeId, "', '", J, "', '", A, "', '');"])
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>,
+ A, <<"' where nodeid='">>, NodeId,
+ <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, "
+ "affiliation, subscriptions) values('">>,
+ NodeId, <<"', '">>, J, <<"', '">>, A,
+ <<"', '');">>])
end.
update_subscription(NodeId, JID, Subscription) ->
J = encode_jid(JID),
S = encode_subscriptions(Subscription),
- case catch ejabberd_odbc:sql_query_t(
- ["update pubsub_state "
- "set subscriptions='", S, "' "
- "where nodeid='", NodeId, "' and jid='", J, "';"]) of
- {updated, 1} ->
- ok;
- _ ->
- catch ejabberd_odbc:sql_query_t(
- ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
- "values('", NodeId, "', '", J, "', 'n', '", S, "');"])
+ case catch
+ ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>,
+ S, <<"' where nodeid='">>, NodeId,
+ <<"' and jid='">>, J, <<"';">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ catch
+ ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, "
+ "affiliation, subscriptions) values('">>,
+ NodeId, <<"', '">>, J, <<"', 'n', '">>,
+ S, <<"');">>])
end.
-decode_jid(SJID) -> jlib:jid_tolower(jlib:string_to_jid(SJID)).
+decode_jid(SJID) ->
+ jlib:jid_tolower(jlib:string_to_jid(SJID)).
-decode_node(N) -> ?PUBSUB:string_to_node(N).
+decode_node(N) -> (?PUBSUB):string_to_node(N).
-decode_affiliation("o") -> owner;
-decode_affiliation("p") -> publisher;
-decode_affiliation("m") -> member;
-decode_affiliation("c") -> outcast;
+decode_affiliation(<<"o">>) -> owner;
+decode_affiliation(<<"p">>) -> publisher;
+decode_affiliation(<<"m">>) -> member;
+decode_affiliation(<<"c">>) -> outcast;
decode_affiliation(_) -> none.
-decode_subscription("s") -> subscribed;
-decode_subscription("p") -> pending;
-decode_subscription("u") -> unconfigured;
+decode_subscription(<<"s">>) -> subscribed;
+decode_subscription(<<"p">>) -> pending;
+decode_subscription(<<"u">>) -> unconfigured;
decode_subscription(_) -> none.
+
decode_subscriptions(Subscriptions) ->
- lists:foldl(fun(Subscription, Acc) ->
- case string:tokens(Subscription, ":") of
- [S, SubId] -> [{decode_subscription(S), SubId}|Acc];
- _ -> Acc
- end
- end, [], string:tokens(Subscriptions, ",")).
-
-encode_jid(JID) -> ?PUBSUB:escape(jlib:jid_to_string(JID)).
-
-encode_affiliation(owner) -> "o";
-encode_affiliation(publisher) -> "p";
-encode_affiliation(member) -> "m";
-encode_affiliation(outcast) -> "c";
-encode_affiliation(_) -> "n".
-
-encode_subscription(subscribed) -> "s";
-encode_subscription(pending) -> "p";
-encode_subscription(unconfigured) -> "u";
-encode_subscription(_) -> "n".
+ lists:foldl(fun (Subscription, Acc) ->
+ case str:tokens(Subscription, <<":">>) of
+ [S, SubId] -> [{decode_subscription(S), SubId} | Acc];
+ _ -> Acc
+ end
+ end,
+ [], str:tokens(Subscriptions, <<",">>)).
+
+%-spec(encode_jid/1 ::
+%(
+% JID :: jid() | jid())
+% -> binary()
+%).
+encode_jid(JID) ->
+ (?PUBSUB):escape(jlib:jid_to_string(JID)).
+
+encode_affiliation(owner) -> <<"o">>;
+encode_affiliation(publisher) -> <<"p">>;
+encode_affiliation(member) -> <<"m">>;
+encode_affiliation(outcast) -> <<"c">>;
+encode_affiliation(_) -> <<"n">>.
+
+encode_subscription(subscribed) -> <<"s">>;
+encode_subscription(pending) -> <<"p">>;
+encode_subscription(unconfigured) -> <<"u">>;
+encode_subscription(_) -> <<"n">>.
+
encode_subscriptions(Subscriptions) ->
- string:join(lists:map(fun({S, SubId}) ->
- encode_subscription(S)++":"++SubId
- end, Subscriptions), ",").
+ str:join(lists:map(fun ({S, SubId}) ->
+ <<(encode_subscription(S))/binary, ":",
+ SubId/binary>>
+ end,
+ Subscriptions),
+ <<",">>).
%%% record getter/setter
@@ -1357,32 +1677,38 @@ state_to_raw(NodeId, State) ->
{JID, _} = State#pubsub_state.stateid,
J = encode_jid(JID),
A = encode_affiliation(State#pubsub_state.affiliation),
- S = encode_subscriptions(State#pubsub_state.subscriptions),
- ["'", NodeId, "', '", J, "', '", A, "', '", S, "'"].
+ S =
+ encode_subscriptions(State#pubsub_state.subscriptions),
+ [<<"'">>, NodeId, <<"', '">>, J, <<"', '">>, A,
+ <<"', '">>, S, <<"'">>].
-raw_to_item(NodeId, {ItemId, SJID, Creation, Modification, XML}) ->
+raw_to_item(NodeId,
+ {ItemId, SJID, Creation, Modification, XML}) ->
JID = decode_jid(SJID),
- ToTime = fun(Str) ->
- [T1,T2,T3] = string:tokens(Str, ":"),
+ ToTime = fun (Str) ->
+ [T1, T2, T3] = str:tokens(Str, <<":">>),
{l2i(T1), l2i(T2), l2i(T3)}
end,
Payload = case xml_stream:parse_element(XML) of
- {error, _Reason} -> [];
- El -> [El]
+ {error, _Reason} -> [];
+ El -> [El]
end,
#pubsub_item{itemid = {ItemId, NodeId},
- creation={ToTime(Creation), JID},
- modification={ToTime(Modification), JID},
+ creation = {ToTime(Creation), JID},
+ modification = {ToTime(Modification), JID},
payload = Payload}.
-l2i(L) when is_list(L) -> list_to_integer(L);
+l2i(L) when is_binary(L) -> jlib:binary_to_integer(L);
l2i(I) when is_integer(I) -> I.
-i2l(I) when is_integer(I) -> integer_to_list(I);
-i2l(L) when is_list(L) -> L.
+
+i2l(I) when is_integer(I) ->
+ iolist_to_binary(integer_to_list(I));
+i2l(L) when is_binary(L) -> L.
+
i2l(I, N) when is_integer(I) -> i2l(i2l(I), N);
-i2l(L, N) when is_list(L) ->
- case length(L) of
- N -> L;
- C when C > N -> L;
- _ -> i2l([$0|L], N)
+i2l(L, N) when is_binary(L) ->
+ case str:len(L) of
+ N -> L;
+ C when C > N -> L;
+ _ -> i2l(<<$0, L/binary>>, N)
end.