mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-20 16:15:59 +01:00
initial merge of pubsub odbc, compilation pass ok
SVN Revision: 2437
This commit is contained in:
parent
0ad45b1a93
commit
5598d34478
@ -25,7 +25,7 @@ ERLBEHAVBEAMS = $(addprefix $(OUTDIR)/,$(ERLBEHAVS:.erl=.beam))
|
||||
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
|
||||
|
||||
|
||||
all: $(ERLBEHAVBEAMS) $(BEAMS)
|
||||
all: odbc $(ERLBEHAVBEAMS) $(BEAMS)
|
||||
|
||||
$(BEAMS): $(ERLBEHAVBEAMS)
|
||||
|
||||
@ -38,6 +38,9 @@ clean:
|
||||
distclean: clean
|
||||
rm -f Makefile
|
||||
|
||||
odbc:
|
||||
patch -o mod_pubsub_odbc.erl mod_pubsub.erl pubsub_odbc.patch
|
||||
|
||||
TAGS:
|
||||
etags *.erl
|
||||
|
||||
|
3680
src/mod_pubsub/mod_pubsub_odbc.erl
Normal file
3680
src/mod_pubsub/mod_pubsub_odbc.erl
Normal file
File diff suppressed because it is too large
Load Diff
188
src/mod_pubsub/node_flat_odbc.erl
Normal file
188
src/mod_pubsub/node_flat_odbc.erl
Normal file
@ -0,0 +1,188 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% The Initial Developer of the Original Code is ProcessOne.
|
||||
%%% Portions created by ProcessOne are Copyright 2006-2009, ProcessOne
|
||||
%%% All Rights Reserved.''
|
||||
%%% This software is copyright 2006-2009, ProcessOne.
|
||||
%%%
|
||||
%%% @copyright 2006-2009 ProcessOne
|
||||
%%% @author Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% [http://www.process-one.net/]
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
-module(node_flat_odbc).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-behaviour(gen_pubsub_node).
|
||||
|
||||
%% API definition
|
||||
-export([init/3, terminate/2,
|
||||
options/0, features/0,
|
||||
create_node_permission/6,
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/7,
|
||||
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_subscription/2,
|
||||
set_subscription/3,
|
||||
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
|
||||
]).
|
||||
|
||||
|
||||
init(Host, ServerHost, Opts) ->
|
||||
node_hometree_odbc:init(Host, ServerHost, Opts).
|
||||
|
||||
terminate(Host, ServerHost) ->
|
||||
node_hometree_odbc:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, flat},
|
||||
{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS div 2},
|
||||
{subscribe, true},
|
||||
{access_model, open},
|
||||
{roster_groups_allowed, []},
|
||||
{publish_model, publishers},
|
||||
{max_payload_size, ?MAX_PAYLOAD_SIZE},
|
||||
{send_last_published_item, on_sub_and_presence},
|
||||
{deliver_notifications, true},
|
||||
{presence_based_delivery, false},
|
||||
{odbc, true},
|
||||
{rsm, true}].
|
||||
|
||||
features() ->
|
||||
node_hometree_odbc:features().
|
||||
|
||||
%% use same code as node_hometree_odbc, but do not limite node to
|
||||
%% the home/localhost/user/... hierarchy
|
||||
%% any node is allowed
|
||||
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
|
||||
LOwner = jlib:jid_tolower(Owner),
|
||||
Allowed = case LOwner of
|
||||
{"", Host, ""} ->
|
||||
true; % pubsub service always allowed
|
||||
_ ->
|
||||
acl:match_rule(ServerHost, Access, LOwner) =:= allow
|
||||
end,
|
||||
{result, Allowed}.
|
||||
|
||||
create_node(NodeId, Owner) ->
|
||||
node_hometree_odbc:create_node(NodeId, Owner).
|
||||
|
||||
delete_node(Removed) ->
|
||||
node_hometree_odbc:delete_node(Removed).
|
||||
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
|
||||
node_hometree_odbc:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
|
||||
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
|
||||
node_hometree_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
|
||||
|
||||
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_hometree_odbc:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
|
||||
remove_extra_items(NodeId, MaxItems, ItemIds) ->
|
||||
node_hometree_odbc:remove_extra_items(NodeId, MaxItems, ItemIds).
|
||||
|
||||
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
|
||||
node_hometree_odbc:delete_item(NodeId, Publisher, PublishModel, ItemId).
|
||||
|
||||
purge_node(NodeId, Owner) ->
|
||||
node_hometree_odbc:purge_node(NodeId, Owner).
|
||||
|
||||
get_entity_affiliations(Host, Owner) ->
|
||||
node_hometree_odbc:get_entity_affiliations(Host, Owner).
|
||||
|
||||
get_node_affiliations(NodeId) ->
|
||||
node_hometree_odbc:get_node_affiliations(NodeId).
|
||||
|
||||
get_affiliation(NodeId, Owner) ->
|
||||
node_hometree_odbc:get_affiliation(NodeId, Owner).
|
||||
|
||||
set_affiliation(NodeId, Owner, Affiliation) ->
|
||||
node_hometree_odbc:set_affiliation(NodeId, Owner, Affiliation).
|
||||
|
||||
get_entity_subscriptions(Host, Owner) ->
|
||||
node_hometree_odbc:get_entity_subscriptions(Host, Owner).
|
||||
|
||||
get_entity_subscriptions_for_send_last(Host, Owner) ->
|
||||
node_hometree_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
|
||||
|
||||
get_node_subscriptions(NodeId) ->
|
||||
node_hometree_odbc:get_node_subscriptions(NodeId).
|
||||
|
||||
get_subscription(NodeId, Owner) ->
|
||||
node_hometree_odbc:get_subscription(NodeId, Owner).
|
||||
|
||||
set_subscription(NodeId, Owner, Subscription) ->
|
||||
node_hometree_odbc:set_subscription(NodeId, Owner, Subscription).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_hometree_odbc:get_states(NodeId).
|
||||
|
||||
get_state(NodeId, JID) ->
|
||||
node_hometree_odbc:get_state(NodeId, JID).
|
||||
|
||||
set_state(State) ->
|
||||
node_hometree_odbc:set_state(State).
|
||||
|
||||
get_items(NodeId, From) ->
|
||||
node_hometree_odbc:get_items(NodeId, From).
|
||||
get_items(NodeId, From, RSM) ->
|
||||
node_hometree_odbc:get_items(NodeId, From, 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) ->
|
||||
node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM).
|
||||
|
||||
get_item(NodeId, ItemId) ->
|
||||
node_hometree_odbc:get_item(NodeId, ItemId).
|
||||
|
||||
get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
|
||||
node_hometree_odbc:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
|
||||
|
||||
set_item(Item) ->
|
||||
node_hometree_odbc:set_item(Item).
|
||||
|
||||
get_item_name(Host, Node, Id) ->
|
||||
node_hometree_odbc:get_item_name(Host, Node, Id).
|
@ -92,7 +92,7 @@
|
||||
%% 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) ->
|
||||
ok = pubsub_subscription:init(),
|
||||
pubsub_subscription:init(),
|
||||
mnesia:create_table(pubsub_state,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, pubsub_state)}]),
|
||||
@ -311,8 +311,9 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
(AccessModel == whitelist) and (not Whitelisted) ->
|
||||
%% Node has whitelist access model and entity lacks required affiliation
|
||||
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
|
||||
PendingSubscription ->
|
||||
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")};
|
||||
(AccessModel == authorize) -> % TODO: to be done
|
||||
%% Node has authorize access model
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
%%MustPay ->
|
||||
%% % Payment is required for a subscription
|
||||
%% {error, ?ERR_PAYMENT_REQUIRED};
|
||||
@ -320,33 +321,32 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
%% % Requesting entity is anonymous
|
||||
%% {error, ?ERR_FORBIDDEN};
|
||||
true ->
|
||||
case pubsub_subscription:subscribe_node(Subscriber,
|
||||
NodeId, Options) of
|
||||
{result, SubID} ->
|
||||
case pubsub_subscription:subscribe_node(Subscriber, NodeId, Options) of
|
||||
{result, SubId} ->
|
||||
NewSub = case AccessModel of
|
||||
authorize -> pending;
|
||||
_ -> subscribed
|
||||
_ -> subscribed
|
||||
end,
|
||||
set_state(SubState#pubsub_state{subscriptions = [{NewSub, SubID} | Subscriptions]}),
|
||||
set_state(SubState#pubsub_state{subscriptions = [{NewSub, SubId} | Subscriptions]}),
|
||||
case {NewSub, SendLast} of
|
||||
{subscribed, never} ->
|
||||
{result, {default, subscribed, SubID}};
|
||||
{result, {default, subscribed, SubId}};
|
||||
{subscribed, _} ->
|
||||
{result, {default, subscribed, SubID, send_last}};
|
||||
{result, {default, subscribed, SubId, send_last}};
|
||||
{_, _} ->
|
||||
{result, {default, pending, SubID}}
|
||||
{result, {default, pending, SubId}}
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (NodeId, Sender, Subscriber, SubID) ->
|
||||
%% @spec (NodeId, Sender, Subscriber, SubId) ->
|
||||
%% {error, Reason} | {result, []}
|
||||
%% NodeId = mod_pubsub:pubsubNodeId()
|
||||
%% Sender = mod_pubsub:jid()
|
||||
%% Subscriber = mod_pubsub:jid()
|
||||
%% SubID = mod_pubsub:subid()
|
||||
%% SubId = mod_pubsub:subid()
|
||||
%% Reason = mod_pubsub:stanzaError()
|
||||
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
@ -358,8 +358,8 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
GenKey -> GenState;
|
||||
_ -> get_state(NodeId, SubKey)
|
||||
end,
|
||||
Subscriptions = lists:filter(fun({_Sub, _SubID}) -> true;
|
||||
(_SubID) -> false
|
||||
Subscriptions = lists:filter(fun({_Sub, _SubId}) -> true;
|
||||
(_SubId) -> false
|
||||
end, SubState#pubsub_state.subscriptions),
|
||||
SubIdExists = case SubId of
|
||||
[] -> false;
|
||||
@ -370,11 +370,11 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
%% Requesting entity is prohibited from unsubscribing entity
|
||||
not Authorized ->
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
%% Entity did not specify SubID
|
||||
%%SubID == "", ?? ->
|
||||
%% Entity did not specify SubId
|
||||
%%SubId == "", ?? ->
|
||||
%% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
|
||||
%% Invalid subscription identifier
|
||||
%%InvalidSubID ->
|
||||
%%InvalidSubId ->
|
||||
%% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
|
||||
%% Requesting entity is not a subscriber
|
||||
Subscriptions == [] ->
|
||||
@ -405,11 +405,11 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}
|
||||
end.
|
||||
|
||||
delete_subscription(SubKey, NodeID, {Subscription, SubID}, SubState) ->
|
||||
delete_subscription(SubKey, NodeID, {Subscription, SubId}, SubState) ->
|
||||
Affiliation = SubState#pubsub_state.affiliation,
|
||||
AllSubs = SubState#pubsub_state.subscriptions,
|
||||
NewSubs = AllSubs -- [{Subscription, SubID}],
|
||||
pubsub_subscription:unsubscribe_node(SubKey, NodeID, SubID),
|
||||
NewSubs = AllSubs -- [{Subscription, SubId}],
|
||||
pubsub_subscription:unsubscribe_node(SubKey, NodeID, SubId),
|
||||
case {Affiliation, NewSubs} of
|
||||
{none, []} ->
|
||||
% Just a regular subscriber, and this is final item, so
|
||||
@ -662,8 +662,8 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
|
||||
case NodeTree:get_node(N) of
|
||||
#pubsub_node{nodeid = {Host, _}} = Node ->
|
||||
lists:foldl(fun({Sub, SubID}, Acc2) ->
|
||||
[{Node, Sub, SubID, J} | Acc2];
|
||||
lists:foldl(fun({Sub, SubId}, Acc2) ->
|
||||
[{Node, Sub, SubId, J} | Acc2];
|
||||
(S, Acc2) ->
|
||||
[{Node, S, J} | Acc2]
|
||||
end, Acc, Ss);
|
||||
@ -678,8 +678,8 @@ get_node_subscriptions(NodeId) ->
|
||||
%% TODO: get rid of cases to handle non-list subscriptions
|
||||
case Subscriptions of
|
||||
[_|_] ->
|
||||
lists:foldl(fun({S, SubID}, Acc) ->
|
||||
[{J, S, SubID} | Acc];
|
||||
lists:foldl(fun({S, SubId}, Acc) ->
|
||||
[{J, S, SubId} | Acc];
|
||||
(S, Acc) ->
|
||||
[{J, S} | Acc]
|
||||
end, [], Subscriptions);
|
||||
@ -696,19 +696,6 @@ get_subscriptions(NodeId, Owner) ->
|
||||
SubState = get_state(NodeId, SubKey),
|
||||
{result, SubState#pubsub_state.subscriptions}.
|
||||
|
||||
set_subscriptions(NodeId, Owner, none, SubId) ->
|
||||
SubKey = jlib:jid_tolower(Owner),
|
||||
SubState = get_state(NodeId, SubKey),
|
||||
case {SubId, SubState#pubsub_state.subscriptions} of
|
||||
{_, []} ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
{"", [{_, SID}]} ->
|
||||
unsub_with_subid(NodeId, SID, SubState);
|
||||
{"", [_|_]} ->
|
||||
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
|
||||
_ ->
|
||||
unsub_with_subid(NodeId, SubId, SubState)
|
||||
end;
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
SubKey = jlib:jid_tolower(Owner),
|
||||
SubState = get_state(NodeId, SubKey),
|
||||
@ -716,11 +703,17 @@ set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
{_, []} ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
{"", [{_, SID}]} ->
|
||||
replace_subscription({Subscription, SID}, SubState);
|
||||
case Subscription of
|
||||
none -> unsub_with_subid(NodeId, SID, SubState);
|
||||
_ -> replace_subscription({Subscription, SID}, SubState)
|
||||
end;
|
||||
{"", [_|_]} ->
|
||||
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
|
||||
_ ->
|
||||
replace_subscription({Subscription, SubId}, SubState)
|
||||
case Subscription of
|
||||
none -> unsub_with_subid(NodeId, SubId, SubState);
|
||||
_ -> replace_subscription({Subscription, SubId}, SubState)
|
||||
end
|
||||
end.
|
||||
|
||||
replace_subscription(NewSub, SubState) ->
|
||||
@ -730,8 +723,8 @@ replace_subscription(NewSub, SubState) ->
|
||||
|
||||
replace_subscription(_, [], Acc) ->
|
||||
Acc;
|
||||
replace_subscription({Sub, SubID}, [{_, SubID} | T], Acc) ->
|
||||
replace_subscription({Sub, SubID}, T, [{Sub, SubID} | Acc]).
|
||||
replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) ->
|
||||
replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]).
|
||||
|
||||
unsub_with_subid(NodeId, SubId, SubState) ->
|
||||
pubsub_subscription:unsubscribe_node(SubState#pubsub_state.stateid,
|
||||
@ -863,10 +856,10 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
|
||||
Subscriptions = SubState#pubsub_state.subscriptions,
|
||||
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
|
||||
if
|
||||
%%SubID == "", ?? ->
|
||||
%%SubId == "", ?? ->
|
||||
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
|
||||
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
|
||||
%%InvalidSubID ->
|
||||
%%InvalidSubId ->
|
||||
%% Entity is subscribed but specifies an invalid subscription ID
|
||||
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
|
||||
GenState#pubsub_state.affiliation == outcast ->
|
||||
@ -881,7 +874,7 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
|
||||
(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) ->
|
||||
(AccessModel == authorize) -> % TODO: to be done
|
||||
%% Node has authorize access model
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
%%MustPay ->
|
||||
@ -911,10 +904,10 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
|
||||
Subscriptions = GenState#pubsub_state.subscriptions,
|
||||
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
|
||||
if
|
||||
%%SubID == "", ?? ->
|
||||
%%SubId == "", ?? ->
|
||||
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
|
||||
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
|
||||
%%InvalidSubID ->
|
||||
%%InvalidSubId ->
|
||||
%% Entity is subscribed but specifies an invalid subscription ID
|
||||
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
|
||||
GenState#pubsub_state.affiliation == outcast ->
|
||||
@ -976,7 +969,7 @@ can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions);
|
||||
can_fetch_item(_Affiliation, _Subscription) -> false.
|
||||
|
||||
is_subscribed(Subscriptions) ->
|
||||
lists:any(fun ({subscribed, _SubID}) -> true;
|
||||
lists:any(fun ({subscribed, _SubId}) -> true;
|
||||
(_) -> false
|
||||
end, Subscriptions).
|
||||
|
||||
|
1324
src/mod_pubsub/node_hometree_odbc.erl
Normal file
1324
src/mod_pubsub/node_hometree_odbc.erl
Normal file
File diff suppressed because it is too large
Load Diff
327
src/mod_pubsub/node_pep_odbc.erl
Normal file
327
src/mod_pubsub/node_pep_odbc.erl
Normal file
@ -0,0 +1,327 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% The Initial Developer of the Original Code is ProcessOne.
|
||||
%%% Portions created by ProcessOne are Copyright 2006-2009, ProcessOne
|
||||
%%% All Rights Reserved.''
|
||||
%%% This software is copyright 2006-2009, ProcessOne.
|
||||
%%%
|
||||
%%%
|
||||
%%% @copyright 2006-2009 ProcessOne
|
||||
%%% @author Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% [http://www.process-one.net/]
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
|
||||
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
|
||||
|
||||
-module(node_pep_odbc).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("pubsub.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(PUBSUB, mod_pubsub_odbc).
|
||||
|
||||
-behaviour(gen_pubsub_node).
|
||||
|
||||
%% API definition
|
||||
-export([init/3, terminate/2,
|
||||
options/0, features/0,
|
||||
create_node_permission/6,
|
||||
create_node/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
remove_extra_items/3,
|
||||
get_entity_affiliations/2,
|
||||
get_node_affiliations/1,
|
||||
get_affiliation/2,
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_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
|
||||
]).
|
||||
|
||||
init(Host, ServerHost, Opts) ->
|
||||
node_hometree_odbc:init(Host, ServerHost, Opts),
|
||||
complain_if_modcaps_disabled(ServerHost),
|
||||
ok.
|
||||
|
||||
terminate(Host, ServerHost) ->
|
||||
node_hometree_odbc:terminate(Host, ServerHost),
|
||||
ok.
|
||||
|
||||
options() ->
|
||||
[{odbc, true},
|
||||
{node_type, pep},
|
||||
{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, false},
|
||||
{persist_items, false},
|
||||
{max_items, ?MAXITEMS div 2},
|
||||
{subscribe, true},
|
||||
{access_model, presence},
|
||||
{roster_groups_allowed, []},
|
||||
{publish_model, publishers},
|
||||
{max_payload_size, ?MAX_PAYLOAD_SIZE},
|
||||
{send_last_published_item, on_sub_and_presence},
|
||||
{deliver_notifications, true},
|
||||
{presence_based_delivery, true}].
|
||||
|
||||
features() ->
|
||||
["create-nodes", %*
|
||||
"auto-create", %*
|
||||
"auto-subscribe", %*
|
||||
"delete-nodes", %*
|
||||
"delete-items", %*
|
||||
"filtered-notifications", %*
|
||||
"modify-affiliations",
|
||||
"outcast-affiliation",
|
||||
"persistent-items",
|
||||
"publish", %*
|
||||
"purge-nodes",
|
||||
"retract-items",
|
||||
"retrieve-affiliations",
|
||||
"retrieve-items", %*
|
||||
"retrieve-subscriptions",
|
||||
"subscribe" %*
|
||||
].
|
||||
|
||||
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 Host of
|
||||
{User, Server, _} -> true;
|
||||
_ -> false
|
||||
end;
|
||||
E ->
|
||||
?DEBUG("Create not allowed : ~p~n", [E]),
|
||||
false
|
||||
end
|
||||
end,
|
||||
{result, Allowed}.
|
||||
|
||||
create_node(NodeId, Owner) ->
|
||||
case node_hometree_odbc:create_node(NodeId, Owner) of
|
||||
{result, _} -> {result, []};
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
delete_node(Removed) ->
|
||||
case node_hometree_odbc:delete_node(Removed) of
|
||||
{result, {_, _, Removed}} -> {result, {[], Removed}};
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
node_hometree_odbc:subscribe_node(
|
||||
NodeId, Sender, Subscriber, AccessModel, SendLast,
|
||||
PresenceSubscription, RosterGroup, Options).
|
||||
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
|
||||
case node_hometree_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of
|
||||
{error, Error} -> {error, Error};
|
||||
{result, _} -> {result, []}
|
||||
end.
|
||||
|
||||
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_hometree_odbc:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
|
||||
|
||||
remove_extra_items(NodeId, MaxItems, ItemIds) ->
|
||||
node_hometree_odbc:remove_extra_items(NodeId, MaxItems, ItemIds).
|
||||
|
||||
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
|
||||
node_hometree_odbc:delete_item(NodeId, Publisher, PublishModel, ItemId).
|
||||
|
||||
purge_node(NodeId, Owner) ->
|
||||
node_hometree_odbc:purge_node(NodeId, Owner).
|
||||
|
||||
get_entity_affiliations(_Host, Owner) ->
|
||||
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
||||
node_hometree_odbc:get_entity_affiliations(OwnerKey, Owner).
|
||||
|
||||
get_node_affiliations(NodeId) ->
|
||||
node_hometree_odbc:get_node_affiliations(NodeId).
|
||||
|
||||
get_affiliation(NodeId, Owner) ->
|
||||
node_hometree_odbc:get_affiliation(NodeId, Owner).
|
||||
|
||||
set_affiliation(NodeId, Owner, Affiliation) ->
|
||||
node_hometree_odbc:set_affiliation(NodeId, Owner, Affiliation).
|
||||
|
||||
get_entity_subscriptions(_Host, Owner) ->
|
||||
SubKey = jlib:jid_tolower(Owner),
|
||||
GenKey = jlib:jid_remove_resource(SubKey),
|
||||
Host = ?PUBSUB:escape(element(2, SubKey)),
|
||||
SJ = node_hometree_odbc:encode_jid(SubKey),
|
||||
GJ = node_hometree_odbc:encode_jid(GenKey),
|
||||
Query = case SubKey of
|
||||
GenKey ->
|
||||
["select host, node, type, i.nodeid, jid, subscription "
|
||||
"from pubsub_state i, pubsub_node n "
|
||||
"where i.nodeid = n.nodeid "
|
||||
"and jid like '", GJ, "%' "
|
||||
"and host like '%@", Host, "';"];
|
||||
_ ->
|
||||
["select host, node, type, i.nodeid, jid, subscription "
|
||||
"from pubsub_state i, pubsub_node n "
|
||||
"where i.nodeid = n.nodeid "
|
||||
"and jid in ('", SJ, "', '", GJ, "') "
|
||||
"and host like '%@", Host, "';"]
|
||||
end,
|
||||
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
|
||||
{selected, ["host", "node", "type", "nodeid", "jid", "subscription"], RItems} ->
|
||||
lists:map(fun({H, N, T, I, J, S}) ->
|
||||
O = node_hometree_odbc:decode_jid(H),
|
||||
Node = nodetree_odbc:raw_to_node(O, {N, "", T, I}),
|
||||
{Node, node_hometree_odbc:decode_subscription(S), node_hometree_odbc:decode_jid(J)}
|
||||
end, RItems);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
{result, Reply}.
|
||||
|
||||
get_entity_subscriptions_for_send_last(_Host, Owner) ->
|
||||
SubKey = jlib:jid_tolower(Owner),
|
||||
GenKey = jlib:jid_remove_resource(SubKey),
|
||||
Host = ?PUBSUB:escape(element(2, SubKey)),
|
||||
SJ = node_hometree_odbc:encode_jid(SubKey),
|
||||
GJ = node_hometree_odbc:encode_jid(GenKey),
|
||||
Query = case SubKey of
|
||||
GenKey ->
|
||||
["select host, node, type, i.nodeid, jid, subscription "
|
||||
"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 like '%@", Host, "';"];
|
||||
_ ->
|
||||
["select host, node, type, i.nodeid, jid, subscription "
|
||||
"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 like '%@", Host, "';"]
|
||||
end,
|
||||
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
|
||||
{selected, ["host", "node", "type", "nodeid", "jid", "subscription"], RItems} ->
|
||||
lists:map(fun({H, N, T, I, J, S}) ->
|
||||
O = node_hometree_odbc:decode_jid(H),
|
||||
Node = nodetree_odbc:raw_to_node(O, {N, "", T, I}),
|
||||
{Node, node_hometree_odbc:decode_subscription(S), node_hometree_odbc:decode_jid(J)}
|
||||
end, RItems);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
{result, Reply}.
|
||||
|
||||
get_node_subscriptions(NodeId) ->
|
||||
%% note: get_node_subscriptions is used for broadcasting
|
||||
%% there should not have any subscriptions
|
||||
%% but that call returns also all subscription to none
|
||||
%% and this is required for broadcast to occurs
|
||||
%% DO NOT REMOVE
|
||||
node_hometree_odbc:get_node_subscriptions(NodeId).
|
||||
|
||||
get_subscriptions(NodeId, Owner) ->
|
||||
node_hometree_odbc:get_subscriptions(NodeId, Owner).
|
||||
|
||||
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
|
||||
node_hometree_odbc:set_subscriptions(NodeId, Owner, Subscription, SubId).
|
||||
|
||||
get_pending_nodes(Host, Owner) ->
|
||||
node_hometree_odbc:get_pending_nodes(Host, Owner).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_hometree_odbc:get_states(NodeId).
|
||||
|
||||
get_state(NodeId, JID) ->
|
||||
node_hometree_odbc:get_state(NodeId, JID).
|
||||
|
||||
set_state(State) ->
|
||||
node_hometree_odbc:set_state(State).
|
||||
|
||||
get_items(NodeId, From) ->
|
||||
node_hometree_odbc:get_items(NodeId, From).
|
||||
get_items(NodeId, From, RSM) ->
|
||||
node_hometree_odbc:get_items(NodeId, From, 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) ->
|
||||
node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM).
|
||||
|
||||
get_last_items(NodeId, JID, Count) ->
|
||||
node_hometree_odbc:get_last_items(NodeId, JID, Count).
|
||||
|
||||
get_item(NodeId, ItemId) ->
|
||||
node_hometree_odbc:get_item(NodeId, ItemId).
|
||||
|
||||
get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
|
||||
node_hometree_odbc:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
|
||||
|
||||
set_item(Item) ->
|
||||
node_hometree_odbc:set_item(Item).
|
||||
|
||||
get_item_name(Host, Node, Id) ->
|
||||
node_hometree_odbc:get_item_name(Host, Node, Id).
|
||||
|
||||
|
||||
%%%
|
||||
%%% Internal
|
||||
%%%
|
||||
|
||||
%% @doc Check mod_caps is enabled, otherwise show warning.
|
||||
%% The PEP plugin for mod_pubsub requires mod_caps to be enabled in the host.
|
||||
%% Check that the mod_caps module is enabled in that Jabber Host
|
||||
%% If not, show a warning message in the ejabberd log file.
|
||||
complain_if_modcaps_disabled(ServerHost) ->
|
||||
Modules = ejabberd_config:get_local_option({modules, ServerHost}),
|
||||
ModCaps = [mod_caps_enabled || {mod_caps, _Opts} <- Modules],
|
||||
case ModCaps of
|
||||
[] ->
|
||||
?WARNING_MSG("The PEP plugin is enabled in mod_pubsub of host ~p. "
|
||||
"This plugin requires mod_caps to be enabled, "
|
||||
"but it isn't.", [ServerHost]);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
@ -148,10 +148,8 @@ get_parentnodes(_Host, _Node, _From) ->
|
||||
%% 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, mnesia:read(pubsub_node, {Host, Node})}];
|
||||
Error ->
|
||||
Error
|
||||
N when is_record(N, pubsub_node) -> [{0, [N]}];
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
%% @spec (Host, Node, From) -> [pubsubNode()] | {error, Reason}
|
||||
|
353
src/mod_pubsub/nodetree_tree_odbc.erl
Normal file
353
src/mod_pubsub/nodetree_tree_odbc.erl
Normal file
@ -0,0 +1,353 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% The Initial Developer of the Original Code is ProcessOne.
|
||||
%%% Portions created by ProcessOne are Copyright 2006-2009, ProcessOne
|
||||
%%% All Rights Reserved.''
|
||||
%%% This software is copyright 2006-2009, ProcessOne.
|
||||
%%%
|
||||
%%%
|
||||
%%% @copyright 2006-2009 ProcessOne
|
||||
%%% @author Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% [http://www.process-one.net/]
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
%%% @doc The module <strong>{@module}</strong> is the default PubSub node tree plugin.
|
||||
%%% <p>It is used as a default for all unknown PubSub node type. It can serve
|
||||
%%% as a developer basis and reference to build its own custom pubsub node tree
|
||||
%%% types.</p>
|
||||
%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p>
|
||||
%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
|
||||
%%% development is still a work in progress. However, the system is already
|
||||
%%% useable and useful as is. Please, send us comments, feedback and
|
||||
%%% improvements.</p>
|
||||
|
||||
-module(nodetree_tree_odbc).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(PUBSUB, mod_pubsub_odbc).
|
||||
-define(PLUGIN_PREFIX, "node_").
|
||||
|
||||
-behaviour(gen_pubsub_nodetree).
|
||||
|
||||
-export([init/3,
|
||||
terminate/2,
|
||||
options/0,
|
||||
set_node/1,
|
||||
get_node/3,
|
||||
get_node/2,
|
||||
get_node/1,
|
||||
get_nodes/2,
|
||||
get_nodes/1,
|
||||
get_parentnodes/3,
|
||||
get_parentnodes_tree/3,
|
||||
get_subnodes/3,
|
||||
get_subnodes_tree/3,
|
||||
create_node/5,
|
||||
delete_node/2
|
||||
]).
|
||||
|
||||
-export([raw_to_node/2]).
|
||||
|
||||
%% ================
|
||||
%% API definition
|
||||
%% ================
|
||||
|
||||
%% @spec (Host, ServerHost, Opts) -> any()
|
||||
%% Host = mod_pubsub:host()
|
||||
%% ServerHost = host()
|
||||
%% Opts = list()
|
||||
%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
|
||||
%% implement this function. It can return anything.</p>
|
||||
%% <p>This function is mainly used to trigger the setup task necessary for the
|
||||
%% plugin. It can be used for example by the developer to create the specific
|
||||
%% module database schema if it does not exists yet.</p>
|
||||
init(_Host, _ServerHost, _Opts) ->
|
||||
ok.
|
||||
terminate(_Host, _ServerHost) ->
|
||||
ok.
|
||||
|
||||
%% @spec () -> [Option]
|
||||
%% Option = mod_pubsub:nodetreeOption()
|
||||
%% @doc Returns the default pubsub node tree options.
|
||||
options() ->
|
||||
[{virtual_tree, false},
|
||||
{odbc, true}].
|
||||
|
||||
|
||||
%% @spec (Host, Node) -> pubsubNode() | {error, Reason}
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
get_node(Host, Node, _From) ->
|
||||
get_node(Host, Node).
|
||||
get_node(Host, Node) ->
|
||||
H = ?PUBSUB:escape(Host),
|
||||
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
|
||||
case catch ejabberd_odbc:sql_query_t(
|
||||
["select node, parent, type, nodeid "
|
||||
"from pubsub_node "
|
||||
"where host='", H, "' and node='", N, "';"])
|
||||
of
|
||||
{selected, ["node", "parent", "type", "nodeid"], [RItem]} ->
|
||||
raw_to_node(Host, RItem);
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
_ ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
end.
|
||||
get_node(NodeId) ->
|
||||
case catch ejabberd_odbc:sql_query_t(
|
||||
["select host, node, parent, type "
|
||||
"from pubsub_node "
|
||||
"where nodeid='", NodeId, "';"])
|
||||
of
|
||||
{selected, ["host", "node", "parent", "type"], [{Host, Node, Parent, Type}]} ->
|
||||
raw_to_node(Host, {Node, Parent, Type, NodeId});
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
_ ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
end.
|
||||
|
||||
%% @spec (Host) -> [pubsubNode()] | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
get_nodes(Host, _From) ->
|
||||
get_nodes(Host).
|
||||
get_nodes(Host) ->
|
||||
H = ?PUBSUB:escape(Host),
|
||||
case catch ejabberd_odbc:sql_query_t(
|
||||
["select node, parent, type, nodeid "
|
||||
"from pubsub_node "
|
||||
"where host='", H, "';"])
|
||||
of
|
||||
{selected, ["node", "parent", "type", "nodeid"], RItems} ->
|
||||
lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% From = mod_pubsub:jid()
|
||||
%% Depth = integer()
|
||||
%% Record = pubsubNode()
|
||||
%% @doc <p>Default node tree does not handle parents, return empty list.</p>
|
||||
get_parentnodes(_Host, _Node, _From) ->
|
||||
[].
|
||||
|
||||
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% From = mod_pubsub:jid()
|
||||
%% Depth = integer()
|
||||
%% Record = pubsubNode()
|
||||
%% @doc <p>Default node tree does not handle parents, return a list
|
||||
%% containing just this node.</p>
|
||||
get_parentnodes_tree(Host, Node, From) ->
|
||||
case get_node(Host, Node, From) of
|
||||
N when is_record(N, pubsub_node) -> [{0, [N]}];
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
get_subnodes(Host, Node, _From) ->
|
||||
get_subnodes(Host, Node).
|
||||
get_subnodes(Host, Node) ->
|
||||
H = ?PUBSUB:escape(Host),
|
||||
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
|
||||
case catch ejabberd_odbc:sql_query_t(
|
||||
["select node, parent, type, nodeid "
|
||||
"from pubsub_node "
|
||||
"where host='", H, "' and parent='", N, "';"])
|
||||
of
|
||||
{selected, ["node", "parent", "type", "nodeid"], RItems} ->
|
||||
lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
get_subnodes_tree(Host, Node, _From) ->
|
||||
get_subnodes_tree(Host, Node).
|
||||
get_subnodes_tree(Host, Node) ->
|
||||
H = ?PUBSUB:escape(Host),
|
||||
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
|
||||
case catch ejabberd_odbc:sql_query_t(
|
||||
["select node, parent, type, nodeid "
|
||||
"from pubsub_node "
|
||||
"where host='", H, "' and node like '", N, "%';"])
|
||||
of
|
||||
{selected, ["node", "parent", "type", "nodeid"], RItems} ->
|
||||
lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (Host, Node, Type, Owner, Options) -> ok | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% NodeType = mod_pubsub:nodeType()
|
||||
%% Owner = mod_pubsub:jid()
|
||||
%% Options = list()
|
||||
create_node(Host, Node, Type, _Owner, Options) ->
|
||||
case nodeid(Host, Node) of
|
||||
{error, ?ERR_ITEM_NOT_FOUND} ->
|
||||
{ParentNode, ParentExists} = case Host of
|
||||
{_U, _S, _R} ->
|
||||
%% This is special case for PEP handling
|
||||
%% PEP does not uses hierarchy
|
||||
{[], true};
|
||||
_ ->
|
||||
Parent = lists:sublist(Node, length(Node) - 1),
|
||||
ParentE = (Parent == []) orelse
|
||||
case nodeid(Host, Parent) of
|
||||
{result, _} -> true;
|
||||
_ -> false
|
||||
end,
|
||||
{Parent, ParentE}
|
||||
end,
|
||||
case ParentExists of
|
||||
true ->
|
||||
case set_node(#pubsub_node{
|
||||
nodeid={Host, Node},
|
||||
parents=[ParentNode],
|
||||
type=Type,
|
||||
options=Options}) of
|
||||
{result, NodeId} -> {ok, NodeId};
|
||||
Other -> Other
|
||||
end;
|
||||
false ->
|
||||
%% Requesting entity is prohibited from creating nodes
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
end;
|
||||
{result, _} ->
|
||||
%% NodeID already exists
|
||||
{error, ?ERR_CONFLICT};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
%% @spec (Host, Node) -> [mod_pubsub:node()]
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
delete_node(Host, Node) ->
|
||||
H = ?PUBSUB:escape(Host),
|
||||
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
|
||||
Removed = get_subnodes_tree(Host, Node),
|
||||
catch ejabberd_odbc:sql_query_t(
|
||||
["delete from pubsub_node "
|
||||
"where host='", H, "' and node like '", N, "%';"]),
|
||||
Removed.
|
||||
|
||||
%% helpers
|
||||
|
||||
raw_to_node(Host, {Node, Parent, Type, NodeId}) ->
|
||||
Options = case catch ejabberd_odbc:sql_query_t(
|
||||
["select name,val "
|
||||
"from pubsub_node_option "
|
||||
"where nodeid='", NodeId, "';"])
|
||||
of
|
||||
{selected, ["name", "val"], ROptions} ->
|
||||
DbOpts = lists:map(fun({Key, Value}) ->
|
||||
RKey = list_to_atom(Key),
|
||||
Tokens = element(2, erl_scan:string(Value++".")),
|
||||
RValue = element(2, erl_parse:parse_term(Tokens)),
|
||||
{RKey, RValue}
|
||||
end, ROptions),
|
||||
Module = list_to_atom(?PLUGIN_PREFIX++Type),
|
||||
StdOpts = Module:options(),
|
||||
lists:foldl(fun({Key, Value}, Acc)->
|
||||
lists:keyreplace(Key, 1, Acc, {Key, Value})
|
||||
end, StdOpts, DbOpts);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
#pubsub_node{
|
||||
nodeid = {Host, string_to_node(Host, Node)},
|
||||
parents = [string_to_node(Host, Parent)],
|
||||
id = NodeId,
|
||||
type = Type,
|
||||
options = Options}.
|
||||
|
||||
%% @spec (NodeRecord) -> ok | {error, Reason}
|
||||
%% Record = mod_pubsub:pubsub_node()
|
||||
set_node(Record) ->
|
||||
{Host, Node} = Record#pubsub_node.nodeid,
|
||||
[Parent] = Record#pubsub_node.parents,
|
||||
Type = Record#pubsub_node.type,
|
||||
H = ?PUBSUB:escape(Host),
|
||||
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
|
||||
P = ?PUBSUB:escape(?PUBSUB:node_to_string(Parent)),
|
||||
NodeId = case nodeid(Host, Node) of
|
||||
{result, OldNodeId} ->
|
||||
catch ejabberd_odbc:sql_query_t(
|
||||
["delete from pubsub_node_option "
|
||||
"where nodeid='", OldNodeId, "';"]),
|
||||
catch ejabberd_odbc:sql_query_t(
|
||||
["update pubsub_node "
|
||||
"set host='", H, "' "
|
||||
"node='", N, "' "
|
||||
"parent='", P, "' "
|
||||
"type='", Type, "' "
|
||||
"where nodeid='", OldNodeId, "';"]),
|
||||
OldNodeId;
|
||||
_ ->
|
||||
catch ejabberd_odbc:sql_query_t(
|
||||
["insert into pubsub_node(host, node, parent, type) "
|
||||
"values('", H, "', '", N, "', '", P, "', '", Type, "');"]),
|
||||
case nodeid(Host, Node) of
|
||||
{result, NewNodeId} -> NewNodeId;
|
||||
_ -> none % this should not happen
|
||||
end
|
||||
end,
|
||||
case NodeId of
|
||||
none ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
_ ->
|
||||
lists:foreach(fun({Key, Value}) ->
|
||||
SKey = atom_to_list(Key),
|
||||
SValue = ?PUBSUB:escape(lists:flatten(io_lib:fwrite("~p",[Value]))),
|
||||
catch ejabberd_odbc:sql_query_t(
|
||||
["insert into pubsub_node_option(nodeid, name, val) "
|
||||
"values('", NodeId, "', '", SKey, "', '", SValue, "');"])
|
||||
end, Record#pubsub_node.options),
|
||||
{result, NodeId}
|
||||
end.
|
||||
|
||||
nodeid(Host, Node) ->
|
||||
H = ?PUBSUB:escape(Host),
|
||||
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
|
||||
case catch ejabberd_odbc:sql_query_t(
|
||||
["select nodeid "
|
||||
"from pubsub_node "
|
||||
"where host='", H, "' and node='", N, "';"])
|
||||
of
|
||||
{selected, ["nodeid"], [{NodeId}]} ->
|
||||
{result, NodeId};
|
||||
{'EXIT', _Reason} ->
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
_ ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
end.
|
||||
|
||||
string_to_node({_, _, _}, Node) -> Node;
|
||||
string_to_node(_, Node) -> ?PUBSUB:string_to_node(Node).
|
136
src/mod_pubsub/pubsub_db_odbc.erl
Normal file
136
src/mod_pubsub/pubsub_db_odbc.erl
Normal file
@ -0,0 +1,136 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% The Initial Developer of the Original Code is ProcessOne.
|
||||
%%% Portions created by ProcessOne are Copyright 2006-2009, ProcessOne
|
||||
%%% All Rights Reserved.''
|
||||
%%% This software is copyright 2006-2009, ProcessOne.
|
||||
%%%
|
||||
%%% @author Pablo Polvorin <pablo.polvorin@process-one.net>
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
-module(pubsub_db_odbc).
|
||||
-author("pablo.polvorin@process-one.net").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
|
||||
-export([add_subscription/1,
|
||||
read_subscription/1,
|
||||
delete_subscription/1,
|
||||
update_subscription/1]).
|
||||
|
||||
|
||||
-spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound.
|
||||
read_subscription(SubID) ->
|
||||
case ejabberd_odbc:sql_query_t(
|
||||
["select opt_name, opt_value "
|
||||
"from pubsub_subscription_opt "
|
||||
"where subid = '", ejabberd_odbc:escape(SubID), "'"]) of
|
||||
{selected, ["opt_name", "opt_value"], []} ->
|
||||
notfound;
|
||||
|
||||
{selected, ["opt_name", "opt_value"], Options} ->
|
||||
|
||||
{ok, #pubsub_subscription{subid = SubID,
|
||||
options = lists:map(fun subscription_opt_from_odbc/1, Options)}}
|
||||
end.
|
||||
|
||||
|
||||
|
||||
-spec delete_subscription(SubID :: string()) -> ok.
|
||||
delete_subscription(SubID) ->
|
||||
ejabberd_odbc:sql_query_t(["delete from pubsub_subscription_opt "
|
||||
"where subid = '", ejabberd_odbc:escape(SubID), "'"]),
|
||||
ok.
|
||||
|
||||
|
||||
-spec update_subscription(#pubsub_subscription{}) -> ok .
|
||||
update_subscription(#pubsub_subscription{subid = SubId} = Sub) ->
|
||||
delete_subscription(SubId),
|
||||
add_subscription(Sub).
|
||||
|
||||
-spec add_subscription(#pubsub_subscription{}) -> ok.
|
||||
add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) ->
|
||||
EscapedSubId = ejabberd_odbc:escape(SubId),
|
||||
lists:foreach(fun(Opt) ->
|
||||
{OdbcOptName, OdbcOptValue} = subscription_opt_to_odbc(Opt),
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["insert into pubsub_subscription_opt(subid, opt_name, opt_value)"
|
||||
"values ('", EscapedSubId, "','", OdbcOptName, "','", OdbcOptValue, "')"])
|
||||
end, Opts),
|
||||
ok.
|
||||
|
||||
|
||||
|
||||
%% -------------- Internal utilities -----------------------
|
||||
subscription_opt_from_odbc({"DELIVER", Value}) ->
|
||||
{deliver, odbc_to_boolean(Value)};
|
||||
subscription_opt_from_odbc({"DIGEST", Value}) ->
|
||||
{digest, odbc_to_boolean(Value)};
|
||||
subscription_opt_from_odbc({"DIGEST_FREQUENCY", Value}) ->
|
||||
{digest_frequency, odbc_to_integer(Value)};
|
||||
subscription_opt_from_odbc({"EXPIRE", Value}) ->
|
||||
{expire, odbc_to_timestamp(Value)};
|
||||
subscription_opt_from_odbc({"INCLUDE_BODY", Value}) ->
|
||||
{include_body, odbc_to_boolean(Value)};
|
||||
|
||||
%%TODO: might be > than 1 show_values value??.
|
||||
%% need to use compact all in only 1 opt.
|
||||
subscription_opt_from_odbc({"SHOW_VALUES", Value}) ->
|
||||
{show_values, Value};
|
||||
subscription_opt_from_odbc({"SUBSCRIPTION_TYPE", Value}) ->
|
||||
{subscription_type, case Value of
|
||||
"items" -> items;
|
||||
"nodes" -> nodes
|
||||
end};
|
||||
|
||||
subscription_opt_from_odbc({"SUBSCRIPTION_DEPTH", Value}) ->
|
||||
{subscription_depth, case Value of
|
||||
"all" -> all;
|
||||
N -> odbc_to_integer(N)
|
||||
end}.
|
||||
|
||||
subscription_opt_to_odbc({deliver, Bool}) ->
|
||||
{"DELIVER", boolean_to_odbc(Bool)};
|
||||
subscription_opt_to_odbc({digest, Bool}) ->
|
||||
{"DIGEST", boolean_to_odbc(Bool)};
|
||||
subscription_opt_to_odbc({digest_frequency, Int}) ->
|
||||
{"DIGEST_FREQUENCY", integer_to_odbc(Int)};
|
||||
subscription_opt_to_odbc({expire, Timestamp}) ->
|
||||
{"EXPIRE", timestamp_to_odbc(Timestamp)};
|
||||
subscription_opt_to_odbc({include_body, Bool}) ->
|
||||
{"INCLUDE_BODY", boolean_to_odbc(Bool)};
|
||||
subscription_opt_to_odbc({show_values, Values}) ->
|
||||
{"SHOW_VALUES", Values};
|
||||
subscription_opt_to_odbc({subscription_type, Type}) ->
|
||||
{"SUBSCRIPTION_TYPE", case Type of
|
||||
items -> "items";
|
||||
nodes -> "nodes"
|
||||
end};
|
||||
subscription_opt_to_odbc({subscription_depth, Depth}) ->
|
||||
{"SUBSCRIPTION_DEPTH", case Depth of
|
||||
all -> "all";
|
||||
N -> integer_to_odbc(N)
|
||||
end}.
|
||||
|
||||
integer_to_odbc(N) ->
|
||||
integer_to_list(N).
|
||||
|
||||
boolean_to_odbc(true) -> "1";
|
||||
boolean_to_odbc(false) -> "0".
|
||||
timestamp_to_odbc(T) -> jlib:now_to_utc_string(T).
|
||||
|
||||
|
||||
odbc_to_integer(N) -> list_to_integer(N).
|
||||
odbc_to_boolean(B) -> B == "1".
|
||||
odbc_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T).
|
484
src/mod_pubsub/pubsub_odbc.patch
Normal file
484
src/mod_pubsub/pubsub_odbc.patch
Normal file
@ -0,0 +1,484 @@
|
||||
--- mod_pubsub.erl 2009-07-31 16:53:48.000000000 +0200
|
||||
+++ mod_pubsub_odbc.erl 2009-07-31 17:07:00.000000000 +0200
|
||||
@@ -45,7 +45,7 @@
|
||||
%%% TODO
|
||||
%%% plugin: generate Reply (do not use broadcast atom anymore)
|
||||
|
||||
--module(mod_pubsub).
|
||||
+-module(mod_pubsub_odbc).
|
||||
-author('christophe.romain@process-one.net').
|
||||
-version('1.12-06').
|
||||
|
||||
@@ -57,9 +57,9 @@
|
||||
-include("jlib.hrl").
|
||||
-include("pubsub.hrl").
|
||||
|
||||
--define(STDTREE, "tree").
|
||||
--define(STDNODE, "flat").
|
||||
--define(PEPNODE, "pep").
|
||||
+-define(STDTREE, "tree_odbc").
|
||||
+-define(STDNODE, "flat_odbc").
|
||||
+-define(PEPNODE, "pep_odbc").
|
||||
|
||||
%% exports for hooks
|
||||
-export([presence_probe/3,
|
||||
@@ -104,7 +104,8 @@
|
||||
string_to_subscription/1,
|
||||
string_to_affiliation/1,
|
||||
extended_error/2,
|
||||
- extended_error/3
|
||||
+ extended_error/3,
|
||||
+ escape/1
|
||||
]).
|
||||
|
||||
%% API and gen_server callbacks
|
||||
@@ -123,7 +124,7 @@
|
||||
-export([send_loop/1
|
||||
]).
|
||||
|
||||
--define(PROCNAME, ejabberd_mod_pubsub).
|
||||
+-define(PROCNAME, ejabberd_mod_pubsub_odbc).
|
||||
-define(PLUGIN_PREFIX, "node_").
|
||||
-define(TREE_PREFIX, "nodetree_").
|
||||
|
||||
@@ -212,8 +213,6 @@
|
||||
ok
|
||||
end,
|
||||
ejabberd_router:register_route(Host),
|
||||
- update_node_database(Host, ServerHost),
|
||||
- update_state_database(Host, ServerHost),
|
||||
init_nodes(Host, ServerHost),
|
||||
State = #state{host = Host,
|
||||
server_host = ServerHost,
|
||||
@@ -456,17 +455,15 @@
|
||||
%% for each node From is subscribed to
|
||||
%% and if the node is so configured, send the last published item to From
|
||||
lists:foreach(fun(PType) ->
|
||||
- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, JID]),
|
||||
+ Subscriptions = case catch node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]) of
|
||||
+ {result, S} -> S;
|
||||
+ _ -> []
|
||||
+ end,
|
||||
lists:foreach(
|
||||
fun({Node, subscribed, _, SubJID}) ->
|
||||
if (SubJID == LJID) or (SubJID == BJID) ->
|
||||
- #pubsub_node{options = Options, type = Type, id = NodeId} = Node,
|
||||
- case get_option(Options, send_last_published_item) of
|
||||
- on_sub_and_presence ->
|
||||
- send_items(Host, Node, NodeId, Type, SubJID, last);
|
||||
- _ ->
|
||||
- ok
|
||||
- end;
|
||||
+ #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId} = Node,
|
||||
+ send_items(H, N, NodeId, Type, SubJID, last);
|
||||
true ->
|
||||
% resource not concerned about that subscription
|
||||
ok
|
||||
@@ -789,10 +786,10 @@
|
||||
{result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Subscriber]),
|
||||
lists:foreach(fun
|
||||
({Node, subscribed, _, JID}) ->
|
||||
- #pubsub_node{options = Options, owners = Owners, type = Type, id = NodeId} = Node,
|
||||
+ #pubsub_node{options = Options, type = Type, id = NodeId} = Node,
|
||||
case get_option(Options, access_model) of
|
||||
presence ->
|
||||
- case lists:member(BJID, Owners) of
|
||||
+ case lists:member(BJID, node_owners(Host, Type, NodeId)) of
|
||||
true ->
|
||||
node_action(Host, Type, unsubscribe_node, [NodeId, Subscriber, JID, all]);
|
||||
false ->
|
||||
@@ -906,7 +903,8 @@
|
||||
sub_el = SubEl} = IQ ->
|
||||
{xmlelement, _, QAttrs, _} = SubEl,
|
||||
Node = xml:get_attr_s("node", QAttrs),
|
||||
- Res = case iq_disco_items(Host, Node, From) of
|
||||
+ Rsm = jlib:rsm_decode(IQ),
|
||||
+ Res = case iq_disco_items(Host, Node, From, Rsm) of
|
||||
{result, IQRes} ->
|
||||
jlib:iq_to_xml(
|
||||
IQ#iq{type = result,
|
||||
@@ -1011,7 +1009,7 @@
|
||||
[] ->
|
||||
["leaf"]; %% No sub-nodes: it's a leaf node
|
||||
_ ->
|
||||
- case node_call(Type, get_items, [NodeId, From]) of
|
||||
+ case node_call(Type, get_items, [NodeId, From, none]) of
|
||||
{result, []} -> ["collection"];
|
||||
{result, _} -> ["leaf", "collection"];
|
||||
_ -> []
|
||||
@@ -1027,8 +1025,9 @@
|
||||
[];
|
||||
true ->
|
||||
[{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} |
|
||||
- lists:map(fun(T) ->
|
||||
- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
|
||||
+ lists:map(fun
|
||||
+ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
|
||||
+ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
|
||||
end, features(Type))]
|
||||
end,
|
||||
%% TODO: add meta-data info (spec section 5.4)
|
||||
@@ -1056,14 +1055,15 @@
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
|
||||
- lists:map(fun(Feature) ->
|
||||
- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++Feature}], []}
|
||||
+ lists:map(fun
|
||||
+ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
|
||||
+ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
|
||||
end, features(Host, Node))};
|
||||
_ ->
|
||||
node_disco_info(Host, Node, From)
|
||||
end.
|
||||
|
||||
-iq_disco_items(Host, [], From) ->
|
||||
+iq_disco_items(Host, [], From, _RSM) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
|
||||
SN = node_to_string(SubNode),
|
||||
@@ -1073,7 +1073,7 @@
|
||||
{"node", SN},
|
||||
{"name", RN}], []}
|
||||
end, tree_action(Host, get_subnodes, [Host, [], From]))};
|
||||
-iq_disco_items(Host, Item, From) ->
|
||||
+iq_disco_items(Host, Item, From, RSM) ->
|
||||
case string:tokens(Item, "!") of
|
||||
[_SNode, _ItemID] ->
|
||||
{result, []};
|
||||
@@ -1085,9 +1085,9 @@
|
||||
%% TODO That is, remove name attribute (or node?, please check for 2.1)
|
||||
Action =
|
||||
fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
- NodeItems = case node_call(Type, get_items, [NodeId, From]) of
|
||||
+ {NodeItems, RsmOut} = case node_call(Type, get_items, [NodeId, From, RSM]) of
|
||||
{result, I} -> I;
|
||||
- _ -> []
|
||||
+ _ -> {[], none}
|
||||
end,
|
||||
Nodes = lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
|
||||
@@ -1103,7 +1103,7 @@
|
||||
{xmlelement, "item", [{"jid", Host}, {"node", SN},
|
||||
{"name", Name}], []}
|
||||
end, NodeItems),
|
||||
- {result, Nodes ++ Items}
|
||||
+ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{result, {_, Result}} -> {result, Result};
|
||||
@@ -1235,7 +1235,8 @@
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, [], xml:remove_cdata(Els)),
|
||||
- get_items(Host, Node, From, SubId, MaxItems, ItemIDs);
|
||||
+ RSM = jlib:rsm_decode(SubEl),
|
||||
+ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM);
|
||||
{get, "subscriptions"} ->
|
||||
get_subscriptions(Host, Node, From, Plugins);
|
||||
{get, "affiliations"} ->
|
||||
@@ -1258,7 +1259,10 @@
|
||||
|
||||
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
|
||||
{xmlelement, _, _, SubEls} = SubEl,
|
||||
- Action = xml:remove_cdata(SubEls),
|
||||
+ NoRSM = lists:filter(fun({xmlelement, Name, _, _}) ->
|
||||
+ Name == "set"
|
||||
+ end, SubEls),
|
||||
+ Action = xml:remove_cdata(SubEls) -- NoRSM,
|
||||
case Action of
|
||||
[{xmlelement, Name, Attrs, Els}] ->
|
||||
Node = case Host of
|
||||
@@ -1384,7 +1388,8 @@
|
||||
_ -> []
|
||||
end
|
||||
end,
|
||||
- case transaction(fun () -> {result, lists:flatmap(Tr, Plugins)} end,
|
||||
+ case transaction(Host,
|
||||
+ fun () -> {result, lists:flatmap(Tr, Plugins)} end,
|
||||
sync_dirty) of
|
||||
{result, Res} -> Res;
|
||||
Err -> Err
|
||||
@@ -1428,7 +1433,7 @@
|
||||
|
||||
%%% authorization handling
|
||||
|
||||
-send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, Subscriber) ->
|
||||
+send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id = NodeId}, Subscriber) ->
|
||||
Lang = "en", %% TODO fix
|
||||
Stanza = {xmlelement, "message",
|
||||
[],
|
||||
@@ -1457,7 +1462,7 @@
|
||||
[{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
|
||||
lists:foreach(fun(Owner) ->
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(Owner), Stanza}
|
||||
- end, Owners).
|
||||
+ end, node_owners(Host, Type, NodeId)).
|
||||
|
||||
find_authorization_response(Packet) ->
|
||||
{xmlelement, _Name, _Attrs, Els} = Packet,
|
||||
@@ -1524,8 +1529,8 @@
|
||||
"true" -> true;
|
||||
_ -> false
|
||||
end,
|
||||
- Action = fun(#pubsub_node{type = Type, owners = Owners, id = NodeId}) ->
|
||||
- IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), Owners),
|
||||
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
+ IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), node_owners_call(Type, NodeId)),
|
||||
{result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
|
||||
if
|
||||
not IsApprover ->
|
||||
@@ -1711,7 +1716,7 @@
|
||||
Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
|
||||
[{xmlelement, "create", nodeAttr(Node),
|
||||
[]}]}],
|
||||
- case transaction(CreateNode, transaction) of
|
||||
+ case transaction(Host, CreateNode, transaction) of
|
||||
{result, {Result, broadcast}} ->
|
||||
%%Lang = "en", %% TODO: fix
|
||||
%%OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
||||
@@ -1823,7 +1828,7 @@
|
||||
error -> {"", "", ""};
|
||||
J -> jlib:jid_tolower(J)
|
||||
end,
|
||||
- Action = fun(#pubsub_node{options = Options, owners = [Owner|_], type = Type, id = NodeId}) ->
|
||||
+ Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
|
||||
Features = features(Type),
|
||||
SubscribeFeature = lists:member("subscribe", Features),
|
||||
OptionsFeature = lists:member("subscription-options", Features),
|
||||
@@ -1842,9 +1847,13 @@
|
||||
{"", "", ""} ->
|
||||
{false, false};
|
||||
_ ->
|
||||
- {OU, OS, _} = Owner,
|
||||
- get_roster_info(OU, OS,
|
||||
- Subscriber, AllowedGroups)
|
||||
+ case node_owners_call(Type, NodeId) of
|
||||
+ [{OU, OS, _}|_] ->
|
||||
+ get_roster_info(OU, OS,
|
||||
+ Subscriber, AllowedGroups);
|
||||
+ _ ->
|
||||
+ {false, false}
|
||||
+ end
|
||||
end
|
||||
end,
|
||||
if
|
||||
@@ -2167,7 +2176,7 @@
|
||||
%% <p>The permission are not checked in this function.</p>
|
||||
%% @todo We probably need to check that the user doing the query has the right
|
||||
%% to read the items.
|
||||
-get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) ->
|
||||
+get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) ->
|
||||
MaxItems =
|
||||
if
|
||||
SMaxItems == "" -> ?MAXITEMS;
|
||||
@@ -2206,11 +2215,11 @@
|
||||
node_call(Type, get_items,
|
||||
[NodeId, From,
|
||||
AccessModel, PresenceSubscription, RosterGroup,
|
||||
- SubId])
|
||||
+ SubId, RSM])
|
||||
end
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
- {result, {_, Items}} ->
|
||||
+ {result, {_, {Items, RSMOut}}} ->
|
||||
SendItems = case ItemIDs of
|
||||
[] ->
|
||||
Items;
|
||||
@@ -2223,7 +2232,8 @@
|
||||
%% number of items sent to MaxItems:
|
||||
{result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
- itemsEls(lists:sublist(SendItems, MaxItems))}]}]};
|
||||
+ itemsEls(lists:sublist(SendItems, MaxItems))}
|
||||
+ | jlib:rsm_encode(RSMOut)]}]};
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
@@ -2255,15 +2265,22 @@
|
||||
%% @doc <p>Resend the items of a node to the user.</p>
|
||||
%% @todo use cache-last-item feature
|
||||
send_items(Host, Node, NodeId, Type, LJID, last) ->
|
||||
- case get_cached_item(Host, NodeId) of
|
||||
+ Stanza = case get_cached_item(Host, NodeId) of
|
||||
undefined ->
|
||||
- send_items(Host, Node, NodeId, Type, LJID, 1);
|
||||
+ % special ODBC optimization, works only with node_hometree_odbc, node_flat_odbc and node_pep_odbc
|
||||
+ ToSend = case node_action(Host, Type, get_last_items, [NodeId, LJID, 1]) of
|
||||
+ {result, []} -> [];
|
||||
+ {result, Items} -> Items
|
||||
+ end,
|
||||
+ event_stanza(
|
||||
+ [{xmlelement, "items", nodeAttr(Node),
|
||||
+ itemsEls(ToSend)}]);
|
||||
LastItem ->
|
||||
- Stanza = event_stanza(
|
||||
+ event_stanza(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
- itemsEls([LastItem])}]),
|
||||
- ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza}
|
||||
- end;
|
||||
+ itemsEls([LastItem])}])
|
||||
+ end,
|
||||
+ ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza};
|
||||
send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
|
||||
{result, []} ->
|
||||
@@ -2381,29 +2398,12 @@
|
||||
error ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
_ ->
|
||||
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) ->
|
||||
- case lists:member(Owner, Owners) of
|
||||
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
+ case lists:member(Owner, node_owners_call(Type, NodeId)) of
|
||||
true ->
|
||||
lists:foreach(
|
||||
fun({JID, Affiliation}) ->
|
||||
- node_call(Type, set_affiliation, [NodeId, JID, Affiliation]),
|
||||
- case Affiliation of
|
||||
- owner ->
|
||||
- NewOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
|
||||
- NewOwners = [NewOwner|Owners],
|
||||
- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
|
||||
- none ->
|
||||
- OldOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
|
||||
- case lists:member(OldOwner, Owners) of
|
||||
- true ->
|
||||
- NewOwners = Owners--[OldOwner],
|
||||
- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
|
||||
- _ ->
|
||||
- ok
|
||||
- end;
|
||||
- _ ->
|
||||
- ok
|
||||
- end
|
||||
+ node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
|
||||
end, Entities),
|
||||
{result, []};
|
||||
_ ->
|
||||
@@ -2665,8 +2665,8 @@
|
||||
error ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
_ ->
|
||||
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}) ->
|
||||
- case lists:member(Owner, Owners) of
|
||||
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
+ case lists:member(Owner, node_owners_call(Type, NodeId)) of
|
||||
true ->
|
||||
lists:foreach(fun({JID, Subscription, SubId}) ->
|
||||
node_call(Type, set_subscriptions, [NodeId, JID, Subscription, SubId])
|
||||
@@ -3154,6 +3154,30 @@
|
||||
Result
|
||||
end.
|
||||
|
||||
+%% @spec (NodeId) -> [ljid()]
|
||||
+%% NodeId = pubsubNodeId()
|
||||
+%% @doc <p>Return list of node owners.</p>
|
||||
+node_owners(Host, Type, NodeId) ->
|
||||
+ case node_action(Host, Type, get_node_affiliations, [NodeId]) of
|
||||
+ {result, Affiliations} ->
|
||||
+ lists:foldl(
|
||||
+ fun({LJID, owner}, Acc) -> [LJID|Acc];
|
||||
+ (_, Acc) -> Acc
|
||||
+ end, [], Affiliations);
|
||||
+ _ ->
|
||||
+ []
|
||||
+ end.
|
||||
+node_owners_call(Type, NodeId) ->
|
||||
+ case node_call(Type, get_node_affiliations, [NodeId]) of
|
||||
+ {result, Affiliations} ->
|
||||
+ lists:foldl(
|
||||
+ fun({LJID, owner}, Acc) -> [LJID|Acc];
|
||||
+ (_, Acc) -> Acc
|
||||
+ end, [], Affiliations);
|
||||
+ _ ->
|
||||
+ []
|
||||
+ end.
|
||||
+
|
||||
%% @spec (Options) -> MaxItems
|
||||
%% Host = host()
|
||||
%% Options = [Option]
|
||||
@@ -3527,7 +3551,13 @@
|
||||
tree_action(Host, Function, Args) ->
|
||||
?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
|
||||
Fun = fun() -> tree_call(Host, Function, Args) end,
|
||||
- catch mnesia:sync_dirty(Fun).
|
||||
+ case catch ejabberd_odbc:sql_bloc(odbc_conn(Host), Fun) of
|
||||
+ {atomic, Result} ->
|
||||
+ Result;
|
||||
+ {aborted, Reason} ->
|
||||
+ ?ERROR_MSG("transaction return internal error: ~p~n",[{aborted, Reason}]),
|
||||
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
+ end.
|
||||
|
||||
%% @doc <p>node plugin call.</p>
|
||||
node_call(Type, Function, Args) ->
|
||||
@@ -3547,13 +3577,13 @@
|
||||
|
||||
node_action(Host, Type, Function, Args) ->
|
||||
?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
|
||||
- transaction(fun() ->
|
||||
+ transaction(Host, fun() ->
|
||||
node_call(Type, Function, Args)
|
||||
end, sync_dirty).
|
||||
|
||||
%% @doc <p>plugin transaction handling.</p>
|
||||
transaction(Host, Node, Action, Trans) ->
|
||||
- transaction(fun() ->
|
||||
+ transaction(Host, fun() ->
|
||||
case tree_call(Host, get_node, [Host, Node]) of
|
||||
N when is_record(N, pubsub_node) ->
|
||||
case Action(N) of
|
||||
@@ -3566,8 +3596,14 @@
|
||||
end
|
||||
end, Trans).
|
||||
|
||||
-transaction(Fun, Trans) ->
|
||||
- case catch mnesia:Trans(Fun) of
|
||||
+transaction(Host, Fun, Trans) ->
|
||||
+ transaction_retry(Host, Fun, Trans, 2).
|
||||
+transaction_retry(Host, Fun, Trans, Count) ->
|
||||
+ SqlFun = case Trans of
|
||||
+ transaction -> sql_transaction;
|
||||
+ _ -> sql_bloc
|
||||
+ end,
|
||||
+ case catch ejabberd_odbc:SqlFun(odbc_conn(Host), Fun) of
|
||||
{result, Result} -> {result, Result};
|
||||
{error, Error} -> {error, Error};
|
||||
{atomic, {result, Result}} -> {result, Result};
|
||||
@@ -3575,6 +3611,15 @@
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
+ {'EXIT', {timeout, _} = Reason} ->
|
||||
+ case Count of
|
||||
+ 0 ->
|
||||
+ ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
|
||||
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
+ N ->
|
||||
+ erlang:yield(),
|
||||
+ transaction_retry(Host, Fun, Trans, N-1)
|
||||
+ end;
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
@@ -3583,6 +3628,17 @@
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
+odbc_conn({_U, Host, _R})->
|
||||
+ Host;
|
||||
+odbc_conn(Host) ->
|
||||
+ Host--"pubsub.". %% TODO, improve that for custom host
|
||||
+
|
||||
+%% escape value for database storage
|
||||
+escape({_U, _H, _R}=JID)->
|
||||
+ ejabberd_odbc:escape(jlib:jid_to_string(JID));
|
||||
+escape(Value)->
|
||||
+ ejabberd_odbc:escape(Value).
|
||||
+
|
||||
%%%% helpers
|
||||
|
||||
%% Add pubsub-specific error element
|
@ -86,28 +86,28 @@ init() ->
|
||||
ok = create_table().
|
||||
|
||||
subscribe_node(JID, NodeID, Options) ->
|
||||
case mnesia:transaction(fun add_subscription/3,
|
||||
case mnesia:sync_dirty(fun add_subscription/3,
|
||||
[JID, NodeID, Options]) of
|
||||
{atomic, Result} -> {result, Result};
|
||||
{aborted, Error} -> Error
|
||||
end.
|
||||
|
||||
unsubscribe_node(JID, NodeID, SubID) ->
|
||||
case mnesia:transaction(fun delete_subscription/3,
|
||||
case mnesia:sync_dirty(fun delete_subscription/3,
|
||||
[JID, NodeID, SubID]) of
|
||||
{atomic, Result} -> {result, Result};
|
||||
{aborted, Error} -> Error
|
||||
end.
|
||||
|
||||
get_subscription(JID, NodeID, SubID) ->
|
||||
case mnesia:transaction(fun read_subscription/3,
|
||||
case mnesia:sync_dirty(fun read_subscription/3,
|
||||
[JID, NodeID, SubID]) of
|
||||
{atomic, Result} -> {result, Result};
|
||||
{aborted, Error} -> Error
|
||||
end.
|
||||
|
||||
set_subscription(JID, NodeID, SubID, Options) ->
|
||||
case mnesia:transaction(fun write_subscription/4,
|
||||
case mnesia:sync_dirty(fun write_subscription/4,
|
||||
[JID, NodeID, SubID, Options]) of
|
||||
{atomic, Result} -> {result, Result};
|
||||
{aborted, Error} -> Error
|
||||
|
292
src/mod_pubsub/pubsub_subscription_odbc.erl
Normal file
292
src/mod_pubsub/pubsub_subscription_odbc.erl
Normal file
@ -0,0 +1,292 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% The Initial Developer of the Original Code is ProcessOne.
|
||||
%%% Portions created by ProcessOne are Copyright 2006-2009, ProcessOne
|
||||
%%% All Rights Reserved.''
|
||||
%%% This software is copyright 2006-2009, ProcessOne.
|
||||
%%%
|
||||
%%% @author Pablo Polvorin <pablo.polvorin@process-one.net>, based on
|
||||
%% pubsub_subscription.erl by Brian Cully <bjc@kublai.com>
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
-module(pubsub_subscription_odbc).
|
||||
-author("bjc@kublai.com").
|
||||
|
||||
%% API
|
||||
-export([init/0,
|
||||
subscribe_node/3,
|
||||
unsubscribe_node/3,
|
||||
get_subscription/3,
|
||||
set_subscription/4,
|
||||
get_options_xform/2,
|
||||
parse_options_xform/1]).
|
||||
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(PUBSUB_DELIVER, "pubsub#deliver").
|
||||
-define(PUBSUB_DIGEST, "pubsub#digest").
|
||||
-define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency").
|
||||
-define(PUBSUB_EXPIRE, "pubsub#expire").
|
||||
-define(PUBSUB_INCLUDE_BODY, "pubsub#include_body").
|
||||
-define(PUBSUB_SHOW_VALUES, "pubsub#show-values").
|
||||
-define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type").
|
||||
-define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth").
|
||||
|
||||
-define(DELIVER_LABEL,
|
||||
"Whether an entity wants to receive or disable notifications").
|
||||
-define(DIGEST_LABEL,
|
||||
"Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually").
|
||||
-define(DIGEST_FREQUENCY_LABEL,
|
||||
"The minimum number of milliseconds between sending any two notification digests").
|
||||
-define(EXPIRE_LABEL,
|
||||
"The DateTime at which a leased subscription will end or has ended").
|
||||
-define(INCLUDE_BODY_LABEL,
|
||||
"Whether an entity wants to receive an XMPP message body in addition to the payload format").
|
||||
-define(SHOW_VALUES_LABEL,
|
||||
"The presence states for which an entity wants to receive notifications").
|
||||
-define(SUBSCRIPTION_TYPE_LABEL,
|
||||
"Type of notification to receive").
|
||||
-define(SUBSCRIPTION_DEPTH_LABEL,
|
||||
"Depth from subscription for which to receive notifications").
|
||||
|
||||
-define(SHOW_VALUE_AWAY_LABEL, "XMPP Show Value of Away").
|
||||
-define(SHOW_VALUE_CHAT_LABEL, "XMPP Show Value of Chat").
|
||||
-define(SHOW_VALUE_DND_LABEL, "XMPP Show Value of DND (Do Not Disturb)").
|
||||
-define(SHOW_VALUE_ONLINE_LABEL, "Mere Availability in XMPP (No Show Value)").
|
||||
-define(SHOW_VALUE_XA_LABEL, "XMPP Show Value of XA (Extended Away)").
|
||||
|
||||
-define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL,
|
||||
"Receive notification of new items only").
|
||||
-define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL,
|
||||
"Receive notification of new nodes only").
|
||||
|
||||
-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL,
|
||||
"Receive notification from direct child nodes only").
|
||||
-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL,
|
||||
"Receive notification from all descendent nodes").
|
||||
|
||||
|
||||
-define(DB_MOD, pubsub_db_odbc).
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
init() ->
|
||||
ok = create_table().
|
||||
|
||||
subscribe_node(_JID, _NodeID, Options) ->
|
||||
SubId = make_subid(),
|
||||
ok = pubsub_db_odbc:add_subscription(#pubsub_subscription{subid = SubId,
|
||||
options = Options}),
|
||||
{result, SubId}.
|
||||
|
||||
|
||||
unsubscribe_node(_JID, _NodeID, SubID) ->
|
||||
{ok, Sub} = ?DB_MOD:read_subscription(SubID),
|
||||
ok = ?DB_MOD:delete_subscription(SubID),
|
||||
{result, Sub}.
|
||||
|
||||
get_subscription(_JID, _NodeID, SubID) ->
|
||||
case ?DB_MOD:read_subscription(SubID) of
|
||||
{ok, Sub} -> {result, Sub};
|
||||
notfound -> {error, notfound}
|
||||
end.
|
||||
|
||||
|
||||
set_subscription(_JID, _NodeID, SubID, Options) ->
|
||||
case ?DB_MOD:read_subscription(SubID) of
|
||||
{ok, _} ->
|
||||
ok = ?DB_MOD:update_subscription(#pubsub_subscription{subid = SubID, options = Options}),
|
||||
{result, ok};
|
||||
notfound ->
|
||||
{error, notfound}
|
||||
end.
|
||||
|
||||
|
||||
get_options_xform(Lang, Options) ->
|
||||
Keys = [deliver, show_values, subscription_type, subscription_depth],
|
||||
XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
|
||||
|
||||
{result, {xmlelement, "x", [{"xmlns", ?NS_XDATA}],
|
||||
[{xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}],
|
||||
[{xmlelement, "value", [],
|
||||
[{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] ++ XFields}}.
|
||||
|
||||
parse_options_xform(XFields) ->
|
||||
case xml:remove_cdata(XFields) of
|
||||
[] -> {result, []};
|
||||
[{xmlelement, "x", _Attrs, _Els} = XEl] ->
|
||||
case jlib:parse_xdata_submit(XEl) of
|
||||
XData when is_list(XData) ->
|
||||
case set_xoption(XData, []) of
|
||||
Opts when is_list(Opts) -> {result, Opts};
|
||||
Other -> Other
|
||||
end;
|
||||
Other ->
|
||||
Other
|
||||
end;
|
||||
Other ->
|
||||
Other
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
create_table() ->
|
||||
ok.
|
||||
|
||||
|
||||
make_subid() ->
|
||||
{T1, T2, T3} = now(),
|
||||
lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
|
||||
|
||||
%%
|
||||
%% Subscription XForm processing.
|
||||
%%
|
||||
|
||||
%% Return processed options, with types converted and so forth, using
|
||||
%% Opts as defaults.
|
||||
set_xoption([], Opts) ->
|
||||
Opts;
|
||||
set_xoption([{Var, Value} | T], Opts) ->
|
||||
NewOpts = case var_xfield(Var) of
|
||||
{error, _} ->
|
||||
Opts;
|
||||
Key ->
|
||||
Val = val_xfield(Key, Value),
|
||||
lists:keystore(Key, 1, Opts, {Key, Val})
|
||||
end,
|
||||
set_xoption(T, NewOpts).
|
||||
|
||||
%% Return the options list's key for an XForm var.
|
||||
var_xfield(?PUBSUB_DELIVER) -> deliver;
|
||||
var_xfield(?PUBSUB_DIGEST) -> digest;
|
||||
var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency;
|
||||
var_xfield(?PUBSUB_EXPIRE) -> expire;
|
||||
var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body;
|
||||
var_xfield(?PUBSUB_SHOW_VALUES) -> show_values;
|
||||
var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
|
||||
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
|
||||
var_xfield(_) -> {error, badarg}.
|
||||
|
||||
%% Convert Values for option list's Key.
|
||||
val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
|
||||
val_xfield(digest, [Val]) -> xopt_to_bool(Val);
|
||||
val_xfield(digest_frequency, [Val]) -> list_to_integer(Val);
|
||||
val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
|
||||
val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
|
||||
val_xfield(show_values, Vals) -> Vals;
|
||||
val_xfield(subscription_type, ["items"]) -> items;
|
||||
val_xfield(subscription_type, ["nodes"]) -> nodes;
|
||||
val_xfield(subscription_depth, ["all"]) -> all;
|
||||
val_xfield(subscription_depth, [Depth]) ->
|
||||
case catch list_to_integer(Depth) of
|
||||
N when is_integer(N) -> N;
|
||||
_ -> {error, ?ERR_NOT_ACCEPTABLE}
|
||||
end.
|
||||
|
||||
%% Convert XForm booleans to Erlang booleans.
|
||||
xopt_to_bool("0") -> false;
|
||||
xopt_to_bool("1") -> true;
|
||||
xopt_to_bool("false") -> false;
|
||||
xopt_to_bool("true") -> true;
|
||||
xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
|
||||
|
||||
%% Return a field for an XForm for Key, with data filled in, if
|
||||
%% applicable, from Options.
|
||||
get_option_xfield(Lang, Key, Options) ->
|
||||
Var = xfield_var(Key),
|
||||
Label = xfield_label(Key),
|
||||
{Type, OptEls} = type_and_options(xfield_type(Key), Lang),
|
||||
Vals = case lists:keysearch(Key, 1, Options) of
|
||||
{value, {_, Val}} ->
|
||||
[tr_xfield_values(Vals) || Vals <- xfield_val(Key, Val)];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
{xmlelement, "field",
|
||||
[{"var", Var}, {"type", Type},
|
||||
{"label", translate:translate(Lang, Label)}],
|
||||
OptEls ++ Vals}.
|
||||
|
||||
type_and_options({Type, Options}, Lang) ->
|
||||
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
|
||||
type_and_options(Type, _Lang) ->
|
||||
{Type, []}.
|
||||
|
||||
tr_xfield_options({Value, Label}, Lang) ->
|
||||
{xmlelement, "option",
|
||||
[{"label", translate:translate(Lang, Label)}], [{xmlelement, "value", [],
|
||||
[{xmlcdata, Value}]}]}.
|
||||
|
||||
tr_xfield_values(Value) ->
|
||||
{xmlelement, "value", [], [{xmlcdata, Value}]}.
|
||||
|
||||
%% Return the XForm variable name for a subscription option key.
|
||||
xfield_var(deliver) -> ?PUBSUB_DELIVER;
|
||||
xfield_var(digest) -> ?PUBSUB_DIGEST;
|
||||
xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY;
|
||||
xfield_var(expire) -> ?PUBSUB_EXPIRE;
|
||||
xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
|
||||
xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
|
||||
xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
|
||||
xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
|
||||
|
||||
%% Return the XForm variable type for a subscription option key.
|
||||
xfield_type(deliver) -> "boolean";
|
||||
xfield_type(digest) -> "boolean";
|
||||
xfield_type(digest_frequency) -> "text-single";
|
||||
xfield_type(expire) -> "text-single";
|
||||
xfield_type(include_body) -> "boolean";
|
||||
xfield_type(show_values) ->
|
||||
{"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL},
|
||||
{"chat", ?SHOW_VALUE_CHAT_LABEL},
|
||||
{"dnd", ?SHOW_VALUE_DND_LABEL},
|
||||
{"online", ?SHOW_VALUE_ONLINE_LABEL},
|
||||
{"xa", ?SHOW_VALUE_XA_LABEL}]};
|
||||
xfield_type(subscription_type) ->
|
||||
{"list-single", [{"items", ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
|
||||
{"nodes", ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
|
||||
xfield_type(subscription_depth) ->
|
||||
{"list-single", [{"1", ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
|
||||
{"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
|
||||
|
||||
%% Return the XForm variable label for a subscription option key.
|
||||
xfield_label(deliver) -> ?DELIVER_LABEL;
|
||||
xfield_label(digest) -> ?DIGEST_LABEL;
|
||||
xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL;
|
||||
xfield_label(expire) -> ?EXPIRE_LABEL;
|
||||
xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
|
||||
xfield_label(show_values) -> ?SHOW_VALUES_LABEL;
|
||||
xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL;
|
||||
xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
|
||||
|
||||
%% Return the XForm value for a subscription option key.
|
||||
xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
|
||||
xfield_val(digest, Val) -> [bool_to_xopt(Val)];
|
||||
xfield_val(digest_frequency, Val) -> [integer_to_list(Val)];
|
||||
xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)];
|
||||
xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
|
||||
xfield_val(show_values, Val) -> Val;
|
||||
xfield_val(subscription_type, items) -> ["items"];
|
||||
xfield_val(subscription_type, nodes) -> ["nodes"];
|
||||
xfield_val(subscription_depth, all) -> ["all"];
|
||||
xfield_val(subscription_depth, N) -> [integer_to_list(N)].
|
||||
|
||||
%% Convert erlang booleans to XForms.
|
||||
bool_to_xopt(false) -> "false";
|
||||
bool_to_xopt(true) -> "true".
|
@ -158,3 +158,58 @@ CREATE TABLE roster_version (
|
||||
-- ALTER TABLE rosterusers ADD COLUMN askmessage text AFTER ask;
|
||||
-- UPDATE rosterusers SET askmessage = '';
|
||||
-- ALTER TABLE rosterusers ALTER COLUMN askmessage SET NOT NULL;
|
||||
|
||||
CREATE TABLE pubsub_node (
|
||||
host text,
|
||||
node text,
|
||||
parent text,
|
||||
type text,
|
||||
nodeid bigint auto_increment primary key
|
||||
) CHARACTER SET utf8;
|
||||
CREATE INDEX i_pubsub_node_parent ON pubsub_node(parent(120));
|
||||
CREATE UNIQUE INDEX i_pubsub_node_tuple ON pubsub_node(host(20), node(120));
|
||||
|
||||
CREATE TABLE pubsub_node_option (
|
||||
nodeid bigint,
|
||||
name text,
|
||||
val text
|
||||
) CHARACTER SET utf8;
|
||||
CREATE INDEX i_pubsub_node_option_nodeid ON pubsub_node_option(nodeid);
|
||||
ALTER TABLE `pubsub_node_option` ADD FOREIGN KEY (`nodeid`) REFERENCES `ejabberd`.`pubsub_node` (`nodeid`) ON DELETE CASCADE;
|
||||
|
||||
CREATE TABLE pubsub_node_owner (
|
||||
nodeid bigint,
|
||||
owner text
|
||||
) CHARACTER SET utf8;
|
||||
CREATE INDEX i_pubsub_node_owner_nodeid ON pubsub_node_owner(nodeid);
|
||||
ALTER TABLE `pubsub_node_owner` ADD FOREIGN KEY (`nodeid`) REFERENCES `ejabberd`.`pubsub_node` (`nodeid`) ON DELETE CASCADE;
|
||||
|
||||
CREATE TABLE pubsub_state (
|
||||
nodeid bigint,
|
||||
jid text,
|
||||
affiliation character(1),
|
||||
subscription character(1),
|
||||
stateid bigint auto_increment primary key
|
||||
) CHARACTER SET utf8;
|
||||
CREATE INDEX i_pubsub_state_jid ON pubsub_state(jid(60));
|
||||
CREATE UNIQUE INDEX i_pubsub_state_tuple ON pubsub_state(nodeid, jid(60));
|
||||
ALTER TABLE `pubsub_state` ADD FOREIGN KEY (`nodeid`) REFERENCES `ejabberd`.`pubsub_node` (`nodeid`) ON DELETE CASCADE;
|
||||
|
||||
CREATE TABLE pubsub_item (
|
||||
nodeid bigint,
|
||||
itemid text,
|
||||
publisher text,
|
||||
creation text,
|
||||
modification text,
|
||||
payload text
|
||||
) CHARACTER SET utf8;
|
||||
CREATE INDEX i_pubsub_item_itemid ON pubsub_item(itemid(36));
|
||||
CREATE UNIQUE INDEX i_pubsub_item_tuple ON pubsub_item(nodeid, itemid(36));
|
||||
ALTER TABLE `pubsub_item` ADD FOREIGN KEY (`nodeid`) REFERENCES `ejabberd`.`pubsub_node` (`nodeid`) ON DELETE CASCADE;
|
||||
|
||||
CREATE TABLE pubsub_subscription_opt (
|
||||
subid text,
|
||||
opt_name varchar(32),
|
||||
opt_value text
|
||||
);
|
||||
CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt(subid(32), opt_name(32));
|
||||
|
@ -162,3 +162,54 @@ CREATE TABLE roster_version (
|
||||
-- ALTER TABLE rosterusers ADD COLUMN askmessage text;
|
||||
-- UPDATE rosterusers SET askmessage = '';
|
||||
-- ALTER TABLE rosterusers ALTER COLUMN askmessage SET NOT NULL;
|
||||
|
||||
CREATE TABLE pubsub_node (
|
||||
host text,
|
||||
node text,
|
||||
parent text,
|
||||
"type" text,
|
||||
nodeid SERIAL UNIQUE
|
||||
);
|
||||
CREATE INDEX i_pubsub_node_parent ON pubsub_node USING btree (parent);
|
||||
CREATE UNIQUE INDEX i_pubsub_node_tuple ON pubsub_node USING btree (host, node);
|
||||
|
||||
CREATE TABLE pubsub_node_option (
|
||||
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
|
||||
name text,
|
||||
val text
|
||||
);
|
||||
CREATE INDEX i_pubsub_node_option_nodeid ON pubsub_node_option USING btree (nodeid);
|
||||
|
||||
CREATE TABLE pubsub_node_owner (
|
||||
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
|
||||
owner text
|
||||
);
|
||||
CREATE INDEX i_pubsub_node_owner_nodeid ON pubsub_node_owner USING btree (nodeid);
|
||||
|
||||
CREATE TABLE pubsub_state (
|
||||
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
|
||||
jid text,
|
||||
affiliation character(1),
|
||||
subscription character(1),
|
||||
stateid SERIAL UNIQUE
|
||||
);
|
||||
CREATE INDEX i_pubsub_state_jid ON pubsub_state USING btree (jid);
|
||||
CREATE UNIQUE INDEX i_pubsub_state_tuple ON pubsub_state USING btree (nodeid, jid);
|
||||
|
||||
CREATE TABLE pubsub_item (
|
||||
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
|
||||
itemid text,
|
||||
publisher text,
|
||||
creation text,
|
||||
modification text,
|
||||
payload text
|
||||
);
|
||||
CREATE INDEX i_pubsub_item_itemid ON pubsub_item USING btree (itemid);
|
||||
CREATE UNIQUE INDEX i_pubsub_item_tuple ON pubsub_item USING btree (nodeid, itemid);
|
||||
|
||||
CREATE TABLE pubsub_subscription_opt (
|
||||
subid text,
|
||||
opt_name varchar(32),
|
||||
opt_value text
|
||||
);
|
||||
CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt USING btree (subid, opt_name);
|
||||
|
Loading…
Reference in New Issue
Block a user