%%% ==================================================================== %%% ``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-2013, ProcessOne %%% All Rights Reserved.'' %%% This software is copyright 2006-2013, ProcessOne. %%% %%% @copyright 2006-2013 ProcessOne %%% @author Karim Gemayel %%% [http:%%www.process-one.net/] %%% @version {@vsn}, {@date} {@time} %%% @end %%% ==================================================================== %%% @headerfile "pubsub_dev.hrl" -module(pubsub_db_mnesia). -author('karim.gemayel@process-one.net'). -compile(export_all). -include("pubsub_dev.hrl"). -include("pubsub_api.hrl"). -import(pubsub_tools, [ get_option/2, get_option/3, get_value/2, get_value/3, set_value/3, %% check_access_model/3, check_access_model/7, check_publish_model/3, is_contact_subscribed_to_node_owners/3, is_contact_in_allowed_roster_groups/2, has_subscriptions/1 ]). -export_type([ suffix/0, table/0 ]). -type(suffix() :: undefined | atom()). -type(table() :: atom()). %% -spec(init/1 :: ( Suffix :: undefined | atom()) -> 'ok' ). init(Suffix) -> create_table_pubsub_node(Suffix), create_table_pubsub_state(Suffix), create_table_pubsub_item(Suffix), create_table_pubsub_last_item(Suffix), create_table_pubsub_subscription_pending(Suffix), pubsub_index_dev:init(Suffix), pubsub_groups:create_table_pubsub_groups(Suffix). %% -spec(table/2 :: ( Base_Table :: atom(), Suffix :: undefined | atom()) -> Table :: atom() ). table(Base_Table, undefined = _Suffix) -> _Table = Base_Table; table(Base_Table, Suffix) -> _Table = list_to_atom(atom_to_list(Base_Table) ++ "_" ++ atom_to_list(Suffix)). %% create_table_pubsub_node(Suffix) -> mnesia:create_table(table('pubsub_node', Suffix), [{type, set}, {disc_copies, [node()]}, {record_name, pubsub_node_dev}, {attributes, record_info(fields, pubsub_node_dev)}]), mnesia:add_table_index(pubsub_node_dev, idx). create_table_pubsub_state(Suffix) -> mnesia:create_table(table('pubsub_state', Suffix), [{type, set}, {disc_copies, [node()]}, {record_name, pubsub_state_dev}, {attributes, record_info(fields, pubsub_state_dev)}]), mnesia:add_table_index(pubsub_state_dev, nodeidx). create_table_pubsub_item(Suffix) -> mnesia:create_table(table('pubsub_item', Suffix), [{type, set}, {disc_copies, [node()]}, {record_name, pubsub_item_dev}, {attributes, record_info(fields, pubsub_item_dev)}]), mnesia:add_table_index(pubsub_item_dev, nodeidx). create_table_pubsub_last_item(Suffix) -> mnesia:create_table(table('pubsub_last_item', Suffix), [{type, set}, {disc_copies, [node()]}, {record_name, pubsub_last_item_dev}, {attributes, record_info(fields, pubsub_last_item_dev)}]). create_table_pubsub_subscription_pending(Suffix) -> mnesia:create_table(table('pubsub_subscription_pending', Suffix), [{type, set}, {disc_copies, [node()]}, {record_name, pubsub_subscription_pending}, {attributes, record_info(fields, pubsub_subscription_pending)}]), mnesia:add_table_index(pubsub_subscription_pending, nodeidx). %% transaction(Module, Function, Arguments) -> case mnesia:transaction(fun() -> apply(Module, Function, Arguments) end) of {atomic, Result} -> Result; {aborted, Reason} -> ?INFO_MSG("DB TRANSACTION ERROR ~p", [Reason]), {error, 'internal-server-error'} end. %% -spec(read_item/3 :: ( Suffix :: atom(), NodeIdx :: exmpp_pubsub:nodeIdx(), ItemId :: exmpp_pubsub:itemId()) -> Pubsub_Item :: undefined | mod_pubsub_dev:pubsub_item() ). read_item(Suffix, NodeIdx, ItemId) -> _Pubsub_Item = case mnesia:read({_Table = table('pubsub_item', Suffix), {ItemId, NodeIdx}}) of [Pubsub_Item] -> Pubsub_Item; [] -> undefined end. %% -spec(read_node/3 :: ( Suffix :: atom(), Pubsub_Host :: exmpp_pubsub:host(), NodeId :: exmpp_pubsub:nodeId()) -> Pubsub_Node :: undefined | mod_pubsub_dev:pubsub_node() ). read_node(Suffix, Pubsub_Host, NodeId) -> _Pubsub_Node = case mnesia:read({_Table = table('pubsub_node', Suffix), {Pubsub_Host, NodeId}}) of [Pubsub_Node] -> Pubsub_Node; [] -> undefined end. %% -spec(read_state/3 :: ( Suffix :: atom(), Entity :: xmpp_jid:usr_entity(), NodeIdx :: exmpp_pubsub:nodeIdx()) -> Pubsub_State :: undefined | mod_pubsub_dev:pubsub_state() ). read_state(Suffix, {U,S,_R} = _Entity, NodeIdx) -> _Pubsub_State = case mnesia:read({_Table = table('pubsub_state', Suffix), {{U,S,undefined}, NodeIdx}}) of [Pubsub_State] -> Pubsub_State; [] -> undefined end. %% -spec(index_read_node/2 :: ( Suffix :: atom(), NodeIdx :: exmpp_pubsub:nodeIdx()) -> Pubsub_Nodes::[Pubsub_Node::mod_pubsub_dev:pubsub_node()] ). index_read_node(Suffix, NodeIdx) -> _Pubsub_Nodes = mnesia:index_read(_Table = table('pubsub_node', Suffix), NodeIdx, idx). %% -spec(index_read_state/2 :: ( Suffix :: atom(), NodeIdx :: exmpp_pubsub:nodeIdx()) -> Pubsub_States::[Pubsub_State::mod_pubsub_dev:pubsub_state()] ). index_read_state(Suffix, NodeIdx) -> _Pubsub_States = mnesia:index_read(_Table = table('pubsub_state', Suffix), NodeIdx, nodeidx). %-- Create_Node --% -spec(create_node/7 :: ( Suffix :: atom(), Pubsub_Host :: exmpp_pubsub:host(), Entity :: xmpp_jid:usr_entity(), NodeId :: exmpp_pubsub:nodeId(), Level :: exmpp_pubsub:level(), Node_Options :: pubsub_options:options_node(), Pubsub_Features :: exmpp_pubsub:pubsub_features()) -> Subscription :: undefined | exmpp_pubsub:subscription_subscribed() ). create_node(Suffix, Pubsub_Host, {U,S,R} = _Entity, NodeId, Level, Node_Options, Pubsub_Features) -> %% Create Pubsub_Node mnesia:write(table('pubsub_node', Suffix), #pubsub_node_dev{ id = {Pubsub_Host, NodeId}, idx = NodeIdx = pubsub_index_dev:new(Suffix, 'node'), level = Level, creation = {_DateTime = now(), _Creator = {U,S,R}}, owners = [{U,S,undefined}], options = Node_Options }, write), _Subscription = case lists:member(<<"auto-subscribe">>, Pubsub_Features) andalso get_value(Node_Options, 'subscribe', false) of true -> mnesia:write(table('pubsub_state', Suffix), #pubsub_state_dev{ id = {{U,S,undefined}, NodeIdx}, nodeidx = NodeIdx, affiliation = 'owner', subscriptions = [Subscription = { _Subscription_State = 'subscribed', _SubId = exmpp_pubsub:subId(), _Resource = undefined, _Subscription_Options = [] }] }, write), Subscription; false -> mnesia:write(table('pubsub_state', Suffix), #pubsub_state_dev{ id = {{U,S,undefined}, NodeIdx}, nodeidx = NodeIdx, affiliation = 'owner' }, write), undefined end. %-- Purge_Node --% %% TODO : purge Pubsub_Purge_Offline %% TODO : purge Pubsub_Expiry_Item -spec(purge_node/2 :: ( Suffix :: atom(), Pubsub_Node :: mod_pubsub_dev:pubsub_node()) -> Recipients :: undefined | [Entity::xmpp_jid:usr_bare()] ). purge_node(Suffix, Pubsub_Node) -> %% Update Pubsub_Node mnesia:write(table('pubsub_node', Suffix), Pubsub_Node#pubsub_node_dev{itemids = []}, write), %% Delete Pubsub_Last_Item mnesia:delete(table('pubsub_last_item', Suffix), NodeIdx = Pubsub_Node#pubsub_node_dev.idx, write), %% Table_Pubsub_State = table('pubsub_state', Suffix), Table_Pubsub_Item = table('pubsub_item', Suffix), _Recipients = case get_value(Pubsub_Node#pubsub_node_dev.options, 'notify_retract', false) of true -> %% Delete Pubsub_Items lists:foreach(fun (Pubsub_Item) -> mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write) % end, _Pubsub_Items = mnesia:index_read(Table_Pubsub_Item, NodeIdx, nodeidx)), %% Update Pubsub_States _Subscribers = lists:foldl(fun %% (Pubsub_State, Subscribers) when (Pubsub_State#pubsub_state_dev.affiliation == 'owner' orelse Pubsub_State#pubsub_state_dev.affiliation == 'outcast' orelse Pubsub_State#pubsub_state_dev.affiliation == 'publish-only') andalso Pubsub_State#pubsub_state_dev.itemids =/= [] -> mnesia:write(Table_Pubsub_State, Pubsub_State#pubsub_state_dev{itemids = []}, write), Subscribers; %% (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State, Subscribers) when Pubsub_State#pubsub_state_dev.affiliation == 'publisher' andalso Pubsub_State#pubsub_state_dev.itemids =/= [] -> mnesia:write(Table_Pubsub_State, Pubsub_State#pubsub_state_dev{itemids = []}, write), case Pubsub_State#pubsub_state_dev.subscriptions of [] = _Subscriptions -> Subscribers; _Subscriptions -> [_Subscriber = Entity | Subscribers] end; %% (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State, Subscribers) when Pubsub_State#pubsub_state_dev.affiliation == 'publisher' andalso Pubsub_State#pubsub_state_dev.subscriptions =/= [] -> [_Subscriber = Entity | Subscribers]; %% (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State, Subscribers) when Pubsub_State#pubsub_state_dev.affiliation == 'member' andalso Pubsub_State#pubsub_state_dev.itemids =/= [] -> mnesia:write(Table_Pubsub_State, Pubsub_State#pubsub_state_dev{itemids = []}, write), case (Pubsub_State#pubsub_state_dev.subscriptions == [] orelse has_subscriptions( Pubsub_State#pubsub_state_dev.subscriptions)) of true -> [_Subscriber = Entity | Subscribers]; false -> Subscribers end; %% (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State, Subscribers) when Pubsub_State#pubsub_state_dev.affiliation == 'member'-> case (Pubsub_State#pubsub_state_dev.subscriptions == [] orelse has_subscriptions( Pubsub_State#pubsub_state_dev.subscriptions)) of true -> [_Subscriber = Entity | Subscribers]; false -> Subscribers end; %% (_Pubsub_State, Subscribers) -> Subscribers % end, [], _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)); false -> %% Update Pubsub_States lists:foreach(fun (_Items_Owner = Entity) -> case mnesia:read(Table_Pubsub_State, {Entity, NodeIdx}, write) of [Pubsub_State] when Pubsub_State#pubsub_state_dev.itemids =/= [] -> mnesia:write(Table_Pubsub_State, Pubsub_State#pubsub_state_dev{itemids = []}, write); _Pubsub_State -> ok end end, _Items_Owners = lists:foldl(fun %% Delete Pubsub_Items (Pubsub_Item, Items_Owners) -> mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write), lists:usort(lists:append(Items_Owners, Pubsub_Item#pubsub_item_dev.owners)) end, [], _Pubsub_Items = mnesia:index_read(Table_Pubsub_Item, NodeIdx, nodeidx)) ), _Subscribers = undefined end. %-- Delete_Node --% -spec(delete_node/2 :: ( Suffix :: atom(), Pubsub_Node :: mod_pubsub_dev:pubsub_node()) -> Recipients :: undefined | [Entity::xmpp_jid:usr_bare()] ). delete_node(Suffix, Pubsub_Node) -> %% Delete Pubsub_Node mnesia:delete_object(table('pubsub_node', Suffix), Pubsub_Node, write), pubsub_index_dev:free(Suffix, 'node', NodeIdx = Pubsub_Node#pubsub_node_dev.idx), %% Delete Pubsub_Last_Item mnesia:delete(table('pubsub_last_item', Suffix), NodeIdx, write), %% Delete Pubsub_Items Table_Pubsub_Item = table('pubsub_item', Suffix), lists:foreach(fun (Pubsub_Item) -> mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write) % end, _Pubsub_Items = mnesia:index_read(Table_Pubsub_Item, NodeIdx, nodeidx)), %% Delete Pubsub_States Table_Pubsub_State = table('pubsub_state', Suffix), _Recipients = case get_value(Pubsub_Node#pubsub_node_dev.options, 'notify_delete', false) of true -> _Subscribers = lists:foldl(fun (#pubsub_state_dev{id = {Entity, _NodeIdx}} = Pubsub_State, Subscribers) -> mnesia:delete_object(Table_Pubsub_State, Pubsub_State, write), case {Pubsub_State#pubsub_state_dev.affiliation, Pubsub_State#pubsub_state_dev.subscriptions} of %% {'publisher' = _Affiliation, Subscriptions} when Subscriptions =/= [] -> [_Subscriber = Entity | Subscribers]; %% {'member' = _Affiliation, _Subscriptions} -> case (_Subscriptions == [] orelse has_subscriptions(_Subscriptions)) of true -> [_Subscriber = Entity | Subscribers]; false -> Subscribers end; %% {_Affiliation, _Subscriptions} -> Subscribers end % end, [], _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)); false -> lists:foreach(fun (Pubsub_State) -> mnesia:delete_object(Table_Pubsub_State, Pubsub_State, write) end, _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)), _Subscribers = undefined end. %-- Get_Entity_Subscriptions --% -spec(get_entity_subscriptions/4 :: ( Suffix :: atom(), Pubsub_Host :: exmpp_pubsub:host(), Entity :: xmpp_jid:usr_entity(), NodeId :: undefined | exmpp_pubsub:nodeId()) -> Entity_Subscriptions :: [{ NodeId :: exmpp_pubsub:nodeId(), Subscriptions :: exmpp_pubsub:subscriptions() }] ). get_entity_subscriptions(Suffix, Pubsub_Host, {U,S,_R} = _Entity, undefined = _NodeId) -> Table_Pubsub_Node = table('pubsub_node', Suffix), _Entity_Subscriptions = lists:foldl(fun ([NodeIdx, Subscriptions], Entity_Subscriptions) -> case mnesia:index_read(Table_Pubsub_Node, NodeIdx, idx) of %% IMPORTANT : Pubsub_Host must be used in the matching, %% else it'd return all NodeIds subscriptions (in case of VHosts) %% and not only the ones from to the current Pubsub_Host [#pubsub_node_dev{id = {Pubsub_Host, NodeId}}] -> [{NodeId, Subscriptions} | Entity_Subscriptions]; _Pubsub_Node -> Entity_Subscriptions end end, [], mnesia:select(table('pubsub_state', Suffix), [{#pubsub_state_dev{ id = {{U,S,undefined}, '$1'}, subscriptions = '$2', _ = '_'}, [{'=/=', '$2', []}], ['$$']}])); %% get_entity_subscriptions(Suffix, Pubsub_Host, {U,S,_R} = _Entity, NodeId) -> _Entity_Subscriptions = case mnesia:read(table('pubsub_node', Suffix), {Pubsub_Host, NodeId}, read) of [] -> []; [#pubsub_node_dev{idx = NodeIdx}] -> case mnesia:read(table('pubsub_state', Suffix), {{U,S,undefined}, NodeIdx}, read) of [#pubsub_state_dev{subscriptions = Subscriptions}] when Subscriptions =/= [] -> [{NodeId, Subscriptions}]; _Pubsub_State -> [] end end. %-- Get_Entity_Affiliations --% -spec(get_entity_affiliations/4 :: ( Suffix :: atom(), Pubsub_Host :: exmpp_pubsub:host(), Entity :: xmpp_jid:usr_bare(), NodeId :: undefined | exmpp_pubsub:nodeId()) -> Entity_Affiliations :: [Entity_Affiliation :: { NodeId :: exmpp_pubsub:nodeId(), Affiliation :: exmpp_pubsub:affiliation() }] ). get_entity_affiliations(Suffix, Pubsub_Host, {U,S,_R} = _Entity, undefined = _NodeId) -> Table_Pubsub_Node = table('pubsub_node', Suffix), _Entity_Affiliations = lists:foldl(fun ([NodeIdx, Affiliation], Entity_Affiliations) -> case mnesia:index_read(Table_Pubsub_Node, NodeIdx, idx) of %% IMPORTANT : Pubsub_Host must be used in the matching, %% else it'd return all NodeIds affiliations (in case of VHosts) %% and not only the ones from to the current Pubsub_Host [#pubsub_node_dev{id = {Pubsub_Host, NodeId}}] -> [{NodeId, Affiliation} | Entity_Affiliations]; _Pubsub_Node -> Entity_Affiliations end end, [], mnesia:select(table('pubsub_state', Suffix), [{#pubsub_state_dev{ id = {{U,S,undefined}, '$1'}, affiliation = '$2', _ = '_'}, [], ['$$']}], read) ); %% get_entity_affiliations(Suffix, Pubsub_Host, {U,S,_R} = _Entity, NodeId) -> _Entity_Affiliations = case mnesia:read(table('pubsub_node', Suffix), {Pubsub_Host, NodeId}, read) of [] -> []; [#pubsub_node_dev{idx = NodeIdx}] -> case mnesia:read(table('pubsub_state', Suffix), {{U,S,undefined}, NodeIdx}) of [#pubsub_state_dev{affiliation = Affiliation}] -> [{NodeId, Affiliation}]; _Pubsub_State -> [] end end. %-- Publish_Item --% -spec(publish_item1/9 :: ( Suffix :: atom(), Pubsub_Host :: exmpp_pubsub:host(), Entity :: xmpp_jid:usr_entity(), NodeId :: undefined | exmpp_pubsub:nodeId(), Level :: exmpp_pubsub:level(), ItemId :: undefined | exmpp_pubsub:itemId(), Payload :: [Xmlel::#xmlel{}] | exmpp_pubsub:payload(), Options :: {Node_Options :: pubsub_options:options_node(), Item_Options :: [] | pubsub_options:options_item()}, Pubsub_Features :: exmpp_pubsub:pubsub_features()) -> {ItemId :: exmpp_pubsub:itemId(), Pubsub_State@Owner :: mod_pubsub_dev:pubsub_state_owner()} ). publish_item1(Suffix, Pubsub_Host, Entity, NodeId, Level, ItemId, [Payload], Options, Pubsub_Features) -> publish_item1(Suffix, Pubsub_Host, Entity, NodeId, Level, ItemId, Payload, Options, Pubsub_Features); publish_item1(Suffix, Pubsub_Host, Entity, NodeId, Level, undefined = _ItemId, Payload, Options, Pubsub_Features) -> publish_item1(Suffix, Pubsub_Host, Entity, NodeId, Level, exmpp_pubsub:itemId(), Payload, Options, Pubsub_Features); %% publish_item1(Suffix, Pubsub_Host, {U,S,R} = _Entity, NodeId, Level, ItemId, Payload, {Node_Options, Item_Options} = Options, Pubsub_Features) -> NodeIdx = pubsub_index_dev:new(Suffix, 'node'), DateTime = now(), Write_Item = get_value(Options, 'persist_items', false) andalso (get_value(Node_Options, 'max_items', 0) > 0), %% Create Pubsub_Node mnesia:write(table('pubsub_node', Suffix), #pubsub_node_dev{ id = {Pubsub_Host, NodeId}, idx = NodeIdx, creation = {DateTime, _Creator = {U,S,R}}, level = Level, owners = [{U,S,undefined}], itemids = case Write_Item of true -> [ItemId]; false -> [] end, options = Node_Options }, write), %% Create Pubsub_Item case Write_Item of false -> ok; true -> mnesia:write(table('pubsub_item', Suffix), #pubsub_item_dev{ id = {ItemId, NodeIdx}, nodeidx = NodeIdx, owners = [{U,S,undefined}], creation = {DateTime, _Creator = {U,S,R}}, payload = Payload, options = Item_Options }, write), %%TODO : Register pubsub#item_expire case get_value(Options, 'item_expire', undefined) of undefined -> ko; Delay -> ok end end, %% Create Pubsub_Last_Item case get_value(Options, 'send_last_published_item', 'never') of 'never' -> ok; _Send_Last_Published_Item -> mnesia:write(table('pubsub_last_item', Suffix), #pubsub_last_item_dev{ nodeidx = NodeIdx, id = ItemId, owners = [{U,S,undefined}], creation = {DateTime, _Creator = {U,S,R}}, payload = Payload, options = Item_Options }, write) end, %% Create Pubsub_State mnesia:write(table('pubsub_state', Suffix), Pubsub_State = #pubsub_state_dev{ id = {{U,S,undefined}, NodeIdx}, nodeidx = NodeIdx, itemids = case Write_Item of true -> [ItemId]; false -> [] end, affiliation = 'owner', subscriptions = case lists:member(<<"auto-subscribe">>, Pubsub_Features) andalso get_value(Node_Options, 'subscribe', false) of true -> [{_Subscription_State = 'subscribed', _SubId = exmpp_pubsub:subId(), _Resource = undefined, _Subscription_Options = []}]; false -> [] end }, write), {ItemId, Pubsub_State}. %% -spec(publish_item2/9 :: ( Suffix :: atom(), Pubsub_Host :: exmpp_pubsub:host(), Entity :: xmpp_jid:usr_entity(), Pubsub_State :: undefined | mod_pubsub_dev:pubsub_state(), Pubsub_Node :: mod_pubsub_dev:pubsub_node(), _ :: undefined | exmpp_pubsub:itemId() | mod_pubsub_dev:pubsub_item(), Payload :: [Xmlel::#xmlel{}] | exmpp_pubsub:payload(), Options :: {Node_Options :: pubsub_options:options_node(), Item_Options :: [] | pubsub_options:options_item()}, Pubsub_Features :: exmpp_pubsub:pubsub_features()) -> {ItemId :: exmpp_pubsub:itemId(), Retracted_Items :: [Retracted_Item::{ ItemId :: exmpp_pubsub:itemId(), Item_Options :: [] | pubsub_options:options_node() }], Pubsub_States :: [Pubsub_State::mod_pubsub_dev:pubsub_state(),...]} ). publish_item2(Suffix, Pubsub_Host, Entity, Pubsub_State, Pubsub_Node, Pubsub_Item, [Payload], Item_Options, Pubsub_Features) -> publish_item2(Suffix, Pubsub_Host, Entity, Pubsub_State, Pubsub_Node, Pubsub_Item, Payload, Item_Options, Pubsub_Features); %% publish_item2(Suffix, Pubsub_Host, {U,S,R} = _Entity, Pubsub_State, Pubsub_Node, #pubsub_item_dev{id = {ItemId, NodeIdx}} = Pubsub_Item, Payload, Item_Options, Pubsub_Features) -> Node_Options = Pubsub_Node#pubsub_node_dev.options, DateTime = now(), Table_Pubsub_State = table('pubsub_state', Suffix), _Retracted_Items = case {get_value(Node_Options, 'persist_items', false), get_value(Node_Options, 'max_items', 0)} of %% {Persist_Items, Max_Items} when Persist_Items == false orelse Max_Items == 0 -> case Pubsub_State of undefined -> mnesia:write(table('pubsub_state', Suffix), #pubsub_state_dev{ id = {{U,S,undefined}, NodeIdx}, nodeidx = NodeIdx }, write), []; _Pubsub_State -> [] end; %% {true, undefined} -> mnesia:write(table('pubsub_node', Suffix), Pubsub_Node#pubsub_node_dev{ itemids = lists:reverse([ItemId | lists:reverse(lists:delete(ItemId, Pubsub_Node#pubsub_node_dev.itemids))]) }, write), %% mnesia:write(Table_Pubsub_State, case Pubsub_State of undefined -> #pubsub_state_dev{ id = {{U,S,undefined}, NodeIdx}, nodeidx = NodeIdx, itemids = [ItemId] }; _ -> Pubsub_State#pubsub_state_dev{ itemids = [ItemId | lists:delete(ItemId, Pubsub_State#pubsub_state_dev.itemids)] } end, write), mnesia:write(table('pubsub_item', Suffix), Pubsub_Item#pubsub_item_dev{ modification = {DateTime, {U,S,R}}, payload = Payload, options = Item_Options, owners = [{U,S,undefined} | lists:delete({U,S,undefined}, Pubsub_Item#pubsub_item_dev.owners)] }, write), []; {true, Max_Items} -> {New_ItemIds, Old_ItemIds} = max_items(Max_Items, lists:reverse([ItemId | lists:reverse(lists:delete(ItemId, Pubsub_Node#pubsub_node_dev.itemids))])), {Retracted_Items, Publisher_Retracted_ItemIds} = delete_old_items(Suffix, _Publisher = {U,S,undefined}, NodeIdx, _Notify_Retract = get_value(Node_Options, 'notify_retract', false), Old_ItemIds, {[], {[], []}}), %% mnesia:write(table('pubsub_node', Suffix), Pubsub_Node#pubsub_node_dev{ itemids = New_ItemIds }, write), mnesia:write(table('pubsub_item', Suffix), Pubsub_Item#pubsub_item_dev{ modification = {DateTime, {U,S,R}}, payload = Payload, options = Item_Options, owners = [{U,S,undefined} | lists:delete({U,S,undefined}, Pubsub_Item#pubsub_item_dev.owners)] }, write), mnesia:write(Table_Pubsub_State, case Pubsub_State of undefined -> #pubsub_state_dev{ id = {{U,S,undefined}, NodeIdx}, nodeidx = NodeIdx, itemids = [ItemId] }; _ -> Pubsub_State#pubsub_state_dev{ itemids = [ItemId | lists:foldl(fun (Publisher_Retracted_ItemId, Publisher_ItemIds) -> lists:delete( Publisher_Retracted_ItemId, Publisher_ItemIds) end, lists:delete(ItemId, Pubsub_State#pubsub_state_dev.itemids), Publisher_Retracted_ItemIds)] } end, write), Retracted_Items end, case get_value(Node_Options, 'send_last_published_item', 'never') of 'never' -> ok; _Send_Last_Published_Item -> mnesia:write(table('pubsub_last_item', Suffix), #pubsub_last_item_dev{ nodeidx = NodeIdx, id = ItemId, owners = [{U,S,undefined}], creation = {DateTime, _Creator = {U,S,R}}, payload = Payload, options = Item_Options }, write) end, {ItemId, _Retracted_Items, _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)}; %% publish_item2(Suffix, Pubsub_Host, {U,S,R} = Entity, Pubsub_State, Pubsub_Node, _ItemId, Payload, Item_Options, Pubsub_Features) -> NodeIdx = Pubsub_Node#pubsub_node_dev.idx, Node_Options = Pubsub_Node#pubsub_node_dev.options, ItemId = case _ItemId of undefined -> exmpp_pubsub:itemId(); _ -> _ItemId end, DateTime = now(), Table_Pubsub_State = table('pubsub_state', Suffix), _Retracted_Items = case {get_value(Node_Options, 'persist_items', false), get_value(Node_Options, 'max_items', 0)} of %% {Persist_Items, Max_Items} when Persist_Items == false orelse Max_Items == 0 -> case Pubsub_State of undefined -> mnesia:write(Table_Pubsub_State, #pubsub_state_dev{ id = {{U,S,undefined}, NodeIdx}, nodeidx = NodeIdx }, write), []; _Pubsub_State -> [] end; %% {true, undefined} -> mnesia:write(table('pubsub_node', Suffix), Pubsub_Node#pubsub_node_dev{ itemids = lists:reverse([ItemId | lists:reverse(ItemId, Pubsub_Node#pubsub_node_dev.itemids)]) }, write), %% mnesia:write(Table_Pubsub_State, case Pubsub_State of undefined -> #pubsub_state_dev{ id = {{U,S,undefined}, NodeIdx}, nodeidx = NodeIdx, itemids = [ItemId] }; _ -> Pubsub_State#pubsub_state_dev{ itemids = [ItemId | Pubsub_State#pubsub_state_dev.itemids] } end, write), mnesia:write(table('pubsub_item', Suffix), #pubsub_item_dev{ id = {ItemId, NodeIdx}, nodeidx = NodeIdx, owners = [{U,S,undefined}], creation = {DateTime, {U,S,R}}, payload = Payload, options = Item_Options }, write), []; {true, Max_Items} -> {New_ItemIds, Old_ItemIds} = max_items(Max_Items, lists:reverse([ItemId | lists:reverse(Pubsub_Node#pubsub_node_dev.itemids)])), {Retracted_Items, Publisher_Retracted_ItemIds} = delete_old_items(Suffix, _Publisher = {U,S,undefined}, NodeIdx, _Notify_Retract = get_value(Node_Options, 'notify_retract', false), Old_ItemIds, {[], {[], []}}), %% mnesia:write(table('pubsub_node', Suffix), Pubsub_Node#pubsub_node_dev{ itemids = New_ItemIds }, write), mnesia:write(table('pubsub_item', Suffix), #pubsub_item_dev{ id = {ItemId, NodeIdx}, nodeidx = NodeIdx, owners = [{U,S,undefined}], creation = {DateTime, {U,S,R}}, payload = Payload, options = Item_Options }, write), %% mnesia:write(Table_Pubsub_State, case Pubsub_State of undefined -> #pubsub_state_dev{ id = {{U,S,undefined}, NodeIdx}, nodeidx = NodeIdx, itemids = [ItemId] }; _ -> Pubsub_State#pubsub_state_dev{ itemids = [ItemId | lists:foldl(fun (Publisher_Retracted_ItemId, Publisher_ItemIds) -> lists:delete( Publisher_Retracted_ItemId, Publisher_ItemIds) end, Pubsub_State#pubsub_state_dev.itemids, Publisher_Retracted_ItemIds)] } end, write), Retracted_Items end, case get_value(Node_Options, 'send_last_published_item', 'never') of 'never' -> ok; _Send_Last_Published_Item -> mnesia:write(table('pubsub_last_item', Suffix), #pubsub_last_item_dev{ nodeidx = NodeIdx, id = ItemId, owners = [{U,S,undefined}], creation = {DateTime, _Creator = {U,S,R}}, payload = Payload, options = Item_Options }, write) end, {ItemId, _Retracted_Items, _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)}. %% -spec(max_items/2 :: ( Max_Items :: undefined | non_neg_integer(), New_ItemIds :: [ItemId::exmpp_pubsub:itemId()]) -> ItemIds :: {New_ItemIds::[ItemId::exmpp_pubsub:itemId()], Old_ItemIds::[ItemId::exmpp_pubsub:itemId()]} ). max_items(undefined = _Max_Items, New_ItemIds) -> _ItemIds = {New_ItemIds, _Old_ItemIds = []}; max_items(0 = _Max_Items, New_ItemIds) -> _ItemIds = {_New_ItemIds = [], _Old_ItemIds = New_ItemIds}; max_items(Max_Items, New_ItemIds) -> _ItemIds = case Max_Items >= (Length_Items = length(New_ItemIds)) of true -> {New_ItemIds, _Old_ItemIds = []}; false -> diff_items((Length_Items - Max_Items), _Count = 0, {New_ItemIds, []}) end. %% -spec(diff_items/3 :: ( Diff_Items :: pos_integer(), Count :: non_neg_integer(), ItemIds :: {New_ItemIds::[ItemId::exmpp_pubsub:itemId()], Old_ItemIds::[ItemId::exmpp_pubsub:itemId()]}) -> ItemIds :: {New_ItemIds::[ItemId::exmpp_pubsub:itemId()], Old_ItemIds::[ItemId::exmpp_pubsub:itemId()]} ). diff_items(Diff_Items, _Count = Diff_Items, ItemIds) -> ItemIds; diff_items(Diff_Items, Count, {[ItemId | New_ItemIds], Old_ItemIds}) -> diff_items(Diff_Items, Count + 1, {New_ItemIds, [ItemId | Old_ItemIds]}). %% -spec(delete_old_items/6 :: ( Suffix :: atom(), Publisher :: xmpp_jid:usr_bare(), NodeIdx :: exmpp_pubsub:nodeIdx(), Node_Notify_Retract :: boolean(), ItemIds :: [ItemId::exmpp_pubsub:itemId()], %% {Retracted_Items :: [Retracted_Item::{ ItemId :: exmpp_pubsub:itemId(), Item_Options :: [] | pubsub_options:options_item() }], % Publishers_Retracted_ItemIds :: { %% Entities_Retracted_ItemIds :: [Entity_Retracted_ItemIds::{ Entity :: xmpp_jid:usr_bare(), ItemIds :: [ItemId::exmpp_pubsub:itemId()] }], %% Publisher_Retracted_ItemIds :: [ItemId::exmpp_pubsub:itemId()] } }) -> {Retracted_Items :: [Retracted_Item::{ ItemId :: exmpp_pubsub:itemId(), Item_Options :: [] | pubsub_options:options_item() }], Publisher_Retracted_ItemIds :: [ItemId::exmpp_pubsub:itemId()]} ). delete_old_items(Suffix, _Publisher, NodeIdx, _Notify_Retract, [] = _ItemIds, {Retracted_Items, {Entities_ItemIds, Publisher_ItemIds}}) -> Table_Pubsub_State = table('pubsub_state', Suffix), lists:foreach(fun ({Entity, Entity_ItemIds}) -> case mnesia:read(Table_Pubsub_State, {Entity, NodeIdx}, write) of [Pubsub_State] -> mnesia:write(Table_Pubsub_State, Pubsub_State#pubsub_state_dev{ itemids = lists:foldl(fun (Retracted_ItemId, ItemIds) -> lists:delete(Retracted_ItemId, ItemIds) end, Pubsub_State#pubsub_state_dev.itemids, Entity_ItemIds) }, write); [] -> %% shouldn't happen ok end % end, Entities_ItemIds), {Retracted_Items, Publisher_ItemIds}; %% delete_old_items(Suffix, Publisher, NodeIdx, Notify_Retract, [ItemId | ItemIds], {Retracted_Items, Publishers_ItemIds}) -> Table_Pubsub_Item = table('pubsub_item', Suffix), delete_old_items(Suffix, Publisher, NodeIdx, Notify_Retract, ItemIds, case mnesia:read(Table_Pubsub_Item, {ItemId, NodeIdx}, write) of [Pubsub_Item] -> mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write), %% @TODO: Unregister 'pubsub#item_expire' {_Retracted_Items = case get_value(Pubsub_Item#pubsub_item_dev.options, 'notify_retract', Notify_Retract) of true -> [{ItemId, Pubsub_Item#pubsub_item_dev.options} | Retracted_Items]; false -> Retracted_Items end, %% _Publishers_ItemIds = lists:foldl(fun %% (Item_Owner, {Entities_ItemIds, Publisher_ItemIds}) when Item_Owner == Publisher -> {Entities_ItemIds, [ItemId | Publisher_ItemIds]}; %% (_Item_Owner = Entity, {Entities_ItemIds, Publisher_ItemIds}) -> {_Entities_ItemIds = case lists:keyfind(Entity, 1, Entities_ItemIds) of {_Entity, Entity_ItemIds} -> lists:keyreplace(Entity, 1, Entities_ItemIds, {Entity, [ItemId | Entity_ItemIds]}); false -> [{Entity, [ItemId]} | Entities_ItemIds] end, Publisher_ItemIds} end, Publishers_ItemIds, Pubsub_Item#pubsub_item_dev.owners)}; [] -> {Retracted_Items, Publishers_ItemIds} end). %-- Retract_Item --% -spec(retract_item/5 :: ( Suffix :: atom(), Pubsub_Node :: mod_pubsub_dev:pubsub_node(), Pubsub_State :: mod_pubsub_dev:pubsub_state_owner() | mod_pubsub_dev:pubsub_state_member() | mod_pubsub_dev:pubsub_state_publish_only() | mod_pubsub_dev:pubsub_state_publisher() | mod_pubsub_dev:pubsub_state_none(), ItemId :: exmpp_pubsub:itemId(), Adhoc_Notify_Retract :: undefined | boolean()) -> {ok, undefined} % | {ok, Item_Options :: [] | pubsub_options:options_item(), Pubsub_States :: mod_pubsub_dev:pubsub_states()} %%% | {error, 'forbidden'} | {error, 'item-not-found'} ). retract_item(Suffix, Pubsub_Node, #pubsub_state_dev{id = {Entity, NodeIdx}} = Pubsub_State, ItemId, Adhoc_Notify_Retract) -> Table_Pubsub_Item = table('pubsub_item', Suffix), case mnesia:read(Table_Pubsub_Item, {ItemId, NodeIdx}, write) of [Pubsub_Item] when Pubsub_State#pubsub_state_dev.affiliation == 'owner' orelse Pubsub_State#pubsub_state_dev.affiliation == 'publisher' -> mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write), Table_Pubsub_Last_Item = table('pubsub_last_item', Suffix), case mnesia:read(Table_Pubsub_Last_Item, NodeIdx, read) of [Pubsub_Last_Item] when Pubsub_Last_Item#pubsub_last_item_dev.id == ItemId -> mnesia:delete_object(Table_Pubsub_Last_Item, Pubsub_Last_Item, write); _ -> ok end, Table_Pubsub_State = table('pubsub_state', Suffix), case retract_item(NodeIdx, ItemId, Table_Pubsub_State, Entity, _Item_Owners = Pubsub_Item#pubsub_item_dev.owners, false) of true -> mnesia:write(Table_Pubsub_State, Pubsub_State#pubsub_state_dev{ itemids = lists:delete(ItemId, Pubsub_State#pubsub_state_dev.itemids) }, write); false -> ok end, mnesia:write(table('pubsub_node', Suffix), Pubsub_Node#pubsub_node_dev{ itemids = lists:delete(ItemId, Pubsub_Node#pubsub_node_dev.itemids) }, write), case Adhoc_Notify_Retract == true orelse get_value( {Pubsub_Node#pubsub_node_dev.options, Pubsub_Item#pubsub_item_dev.options}, 'notify_retract', false) of true -> {ok, _Item_Options = Pubsub_Item#pubsub_item_dev.options, _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)}; false -> {ok, undefined} end; %% [#pubsub_item_dev{creation = {_DateTime, _Creator = Entity}} = Pubsub_Item] -> mnesia:delete_object(Table_Pubsub_Item, Pubsub_Item, write), Table_Pubsub_Last_Item = table('pubsub_last_item', Suffix), case mnesia:read(Table_Pubsub_Last_Item, NodeIdx, read) of [Pubsub_Last_Item] when Pubsub_Last_Item#pubsub_last_item_dev.id == ItemId -> mnesia:delete_object(Table_Pubsub_Last_Item, Pubsub_Last_Item, write); _ -> ok end, Table_Pubsub_State = table('pubsub_state', Suffix), case retract_item(NodeIdx, ItemId, Table_Pubsub_State, Entity, _Item_Owners = Pubsub_Item#pubsub_item_dev.owners, false) of true -> mnesia:write(Table_Pubsub_State, Pubsub_State#pubsub_state_dev{ itemids = lists:delete(ItemId, Pubsub_State#pubsub_state_dev.itemids) }, write); false -> ok end, mnesia:write(table('pubsub_node', Suffix), Pubsub_Node#pubsub_node_dev{ itemids = lists:delete(ItemId, Pubsub_Node#pubsub_node_dev.itemids) }, write), case Adhoc_Notify_Retract == true orelse get_value( {Pubsub_Node#pubsub_node_dev.options, Pubsub_Item#pubsub_item_dev.options}, 'notify_retract', false) of true -> {ok, _Item_Options = Pubsub_Item#pubsub_item_dev.options, _Pubsub_States = mnesia:index_read(Table_Pubsub_State, NodeIdx, nodeidx)}; false -> {ok, undefined} end; [_Pubsub_Item] -> {error, 'forbidden'}; %% [] -> {error, 'item-not-found'} end. %% -spec(retract_item/6 :: ( NodeIdx :: exmpp_pubsub:nodeIdx(), ItemId :: exmpp_pubsub:itemId(), Table_Pubsub_State :: atom(), Entity :: xmpp_jid:usr_bare() | undefined, Item_Owners :: [Entity::xmpp_jid:usr_bare(),...], Retract_ItemId :: boolean()) -> Retract_ItemId :: boolean() ). retract_item(_NodeIdx, _ItemId, _Table_Pubsub_State, _Entity, [] = _Item_Owners, Retract_ItemId) -> Retract_ItemId; %% retract_item(NodeIdx, ItemId, Table_Pubsub_State, Entity, [_Item_Owner = Entity | Item_Owners], _Retract_ItemId) -> retract_item(NodeIdx, ItemId, Table_Pubsub_State, undefined, Item_Owners, true); %% retract_item(NodeIdx, ItemId, Table_Pubsub_State, _Entity, [_Item_Owner = Entity | Item_Owners], Retract_ItemId) -> case mnesia:read(Table_Pubsub_State, {Entity, NodeIdx}, write) of [Pubsub_State] -> mnesia:write(Table_Pubsub_State, Pubsub_State#pubsub_state_dev{ itemids = lists:delete(ItemId, Pubsub_State#pubsub_state_dev.itemids) }, write); [] -> ok end, retract_item(NodeIdx, ItemId, Table_Pubsub_State, _Entity, Item_Owners, Retract_ItemId). %-- Subscribe_Node --% -spec(subscribe_node/8 :: ( Suffix :: atom(), Host :: xmpp_jid:raw_jid_component_bare(), Node_Options :: pubsub_options:options_node(), Node_Owners :: [Entity::xmpp_jid:usr_bare(),...], Pubsub_State :: mod_pubsub_dev:pubsub_state(), Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()}, Subscription_Options :: [] | pubsub_options:options_subscription(), Pubsub_Features :: exmpp_pubsub:pubsub_features()) -> {result, Subscription_Subscribed :: exmpp_pubsub:subscription_subscribed(), Pubsub_Last_Item :: undefined | mod_pubsub_dev:pubsub_last_item()} % | {result, Subscription_Pending :: exmpp_pubsub:subscription_pending(), Pubsub_Last_Item :: undefined} %%% | {error, 'forbidden'} | {error, 'not-authorized', 'presence-subscription-required'} | {error, 'not-authorized', 'not-in-roster-group'} | {error, 'not-authorized', 'pending-subscription'} | {error, 'not-allowed', 'closed-node'} ). subscribe_node(Suffix, Host, Node_Options, Node_Owners, #pubsub_state_dev{id = {{_U,S,_R} = Entity, _NodeIdx}} = Pubsub_State, Resource, Subscription_Options, Pubsub_Features) -> Affiliation = Pubsub_State#pubsub_state_dev.affiliation, Subscriptions = Pubsub_State#pubsub_state_dev.subscriptions, case options_on_subscribe_node(Host, Node_Options, Node_Owners, Entity, Resource, Affiliation, Subscriptions, Subscription_Options, Pubsub_Features) of %% {ok, 'new', {'pending', SubId, _Resource, _Subscription_Options} = Subscription} -> mnesia:write(table('pubsub_state', Suffix), Pubsub_State#pubsub_state_dev{ affiliation = case Affiliation of 'none' -> 'member'; Affiliation -> Affiliation end, subscriptions = [Subscription | Subscriptions] }, write), Table_Pubsub_Subscription_Pending = table('pubsub_subscription_pending', Suffix), case mnesia:read(Table_Pubsub_Subscription_Pending, Pubsub_State#pubsub_state_dev.id, write) of [] -> mnesia:write(Table_Pubsub_Subscription_Pending, #pubsub_subscription_pending{ id = Pubsub_State#pubsub_state_dev.id, nodeidx = Pubsub_State#pubsub_state_dev.nodeidx, subids = [SubId] }, write); [Pubsub_Subscription_Pending] -> mnesia:write(Table_Pubsub_Subscription_Pending, Pubsub_Subscription_Pending#pubsub_subscription_pending{ subids = [SubId | Pubsub_Subscription_Pending #pubsub_subscription_pending.subids] }, write) end, {result, Subscription, _Pubsub_Last_Item = undefined}; %% {ok, Type, Subscription} -> case Type of 'new' -> mnesia:write(table('pubsub_state', Suffix), Pubsub_State#pubsub_state_dev{ affiliation = case Affiliation of 'none' -> 'member'; Affiliation -> Affiliation end, subscriptions = [Subscription | Subscriptions] }, write); 'old' -> ok end, {result, Subscription, _Pubsub_Last_Item = case (get_value(Subscription_Options, 'deliver', true) == true) andalso (get_value(Subscription_Options, 'show-values') =/= []) andalso (get_value(Node_Options, 'send_last_published_item', 'never') =/= 'never') of true -> case mnesia:read(table('pubsub_last_item', Suffix), Pubsub_State#pubsub_state_dev.nodeidx, read) of [] -> undefined; [Pubsub_Last_Item] -> Pubsub_Last_Item end; false -> undefined end}; Error -> Error end. %% -spec(options_on_subscribe_node/9 :: ( Host :: xmpp_jid:raw_jid_component_bare(), Node_Options :: pubsub_options:options_node(), Node_Owners :: [Entity::xmpp_jid:usr_bare(),...], Entity :: xmpp_jid:usr_bare(), Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()}, Affiliation :: exmpp_pubsub:affiliation(), Subscriptions :: [] | exmpp_pubsub:subscriptions(), Subscription_Options :: [] | pubsub_options:options_subscription(), Pubsub_Features :: exmpp_pubsub:pubsub_features()) -> {ok, Type :: 'new' | 'old', Subscription :: exmpp_pubsub:subscription_subscribed()} % | {ok, Type :: 'new', Subscription :: exmpp_pubsub:subscription_pending()} %%% | {error, 'forbidden'} | {error, 'not-authorized', 'presence-subscription-required'} | {error, 'not-authorized', 'not-in-roster-group'} | {error, 'not-authorized', 'pending-subscription'} | {error, 'not-allowed', 'closed-node'} ). options_on_subscribe_node(Host, Node_Options, Node_Owners, Entity, Resource, Affiliation, Subscriptions, Subscription_Options, Pubsub_Features) -> options_on_subscribe_node(get_option(Node_Options, 'subscribe'), Host, Node_Options, Node_Owners, Entity, Resource, Affiliation, Subscriptions, Subscription_Options, _Multi_Subscribe = lists:member(<<"multi-subscribe">>, Pubsub_Features)). %% -spec(options_on_subscribe_node/10 :: ( Option :: pubsub_options:option_node_access_model() | pubsub_options:option_node_subscribe(), Host :: xmpp_jid:raw_jid_component_bare(), Node_Options :: pubsub_options:options_node(), Node_Owners :: [Entity::xmpp_jid:usr_bare(),...], Entity :: xmpp_jid:usr_bare(), Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()}, Affiliation :: exmpp_pubsub:affiliation(), Subscriptions :: [] | exmpp_pubsub:subscriptions(), Subscription_Options :: [] | pubsub_options:options_subscription(), Multi_Subscribe :: boolean()) -> {ok, Type :: 'new' | 'old', Subscription :: exmpp_pubsub:subscription_subscribed()} % | {ok, Type :: 'new', Subscription :: exmpp_pubsub:subscription_pending()} %%% | {error, 'forbidden'} | {error, 'not-authorized', 'presence-subscription-required'} | {error, 'not-authorized', 'not-in-roster-group'} | {error, 'not-authorized', 'pending-subscription'} | {error, 'not-allowed', 'closed-node'} ). options_on_subscribe_node({'subscribe', Subscribe}, _Host, _Node_Options, _Node_Owners, _Entity, _Resource, Affiliation, _Subscriptions, _Subscription_Options, _Multi_Subscribe) when Subscribe == false orelse Affiliation == 'outcast' orelse Affiliation == 'publish-only' -> {error, 'forbidden'}; %% options_on_subscribe_node({'subscribe', true}, Host, Node_Options, Node_Owners, Entity, Resource, Affiliation, Subscriptions, Subscription_Options, Multi_Subscribe) -> options_on_subscribe_node(get_option(Node_Options, 'access_model'), Host, Node_Options, Node_Owners, Entity, Resource, Affiliation, Subscriptions, Subscription_Options, Multi_Subscribe); options_on_subscribe_node({'access_model', open}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, _Affiliation, Subscriptions, Subscription_Options, false = _Multi_Subscribe) -> case lists:keyfind(Resource, 3, Subscriptions) of {_Resource, Subscription} -> {ok, 'old', Subscription}; false -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)} end; %% options_on_subscribe_node({'access_model', open}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, _Affiliation, _Subscriptions, Subscription_Options, true = _Multi_Subscribe) -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}; %% options_on_subscribe_node({'access_model', presence}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, Affiliation, Subscriptions, Subscription_Options, false = _Multi_Subscribe) when Affiliation == 'owner' orelse Affiliation == 'publisher' orelse Affiliation == 'member' -> case lists:keyfind(Resource, 3, Subscriptions) of {_Resource, Subscription} -> {ok, 'old', Subscription}; false -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)} end; %% options_on_subscribe_node({'access_model', presence}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, Affiliation, _Subscriptions, Subscription_Options, true = _Multi_Subscribe) when Affiliation == 'owner' orelse Affiliation == 'publisher' orelse Affiliation == 'member' -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}; %% options_on_subscribe_node({'access_model', presence}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, Affiliation, Subscriptions, Subscription_Options, false = _Multi_Subscribe) when Affiliation == 'owner' orelse Affiliation == 'publisher' orelse Affiliation == 'member' -> case lists:keyfind(Resource, 3, Subscriptions) of {_Resource, Subscription} -> {ok, 'old', Subscription}; false -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)} end; %% options_on_subscribe_node({'access_model', presence}, Host, _Node_Options, Node_Owners, Entity, Resource, _Affiliation, _Subscriptions, Subscription_Options, _Multi_Subscribe) -> case is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners) of false -> {error, 'not-authorized', 'presence-subscription-required'}; _Node_Owner -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)} end; %% options_on_subscribe_node({'access_model', roster}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, Affiliation, _Subscriptions, Subscription_Options, true = _Multi_Subscribe) when Affiliation == 'owner' orelse Affiliation == 'publisher' orelse Affiliation == 'member' -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}; %% options_on_subscribe_node({'access_model', roster}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, Affiliation, Subscriptions, Subscription_Options, false = _Multi_Subscribe) when Affiliation == 'owner' orelse Affiliation == 'publisher' orelse Affiliation == 'member' -> case lists:keyfind(Resource, 3, Subscriptions) of {_Resource, Subscription} -> {ok, 'old', Subscription}; false -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)} end; %% options_on_subscribe_node({'access_model', roster}, _Host, Node_Options, _Node_Owners, Entity, Resource, _Affiliation, _Subscriptions, Subscription_Options, _Multi_Subscribe) -> case is_contact_in_allowed_roster_groups(Entity, get_value(Node_Options, 'roster_groups_allowed', [])) of false -> {error, 'not-authorized', 'not-in-roster-group'}; {_Node_Owner, _Roster_Group} -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)} end; %% options_on_subscribe_node({'access_model', authorize}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, Affiliation, _Subscriptions, Subscription_Options, true = _Multi_Subscribe) when Affiliation == 'owner' orelse Affiliation == 'publisher' orelse Affiliation == 'member' -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}; %% options_on_subscribe_node({'access_model', authorize}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, Affiliation, Subscriptions, Subscription_Options, false = _Multi_Subscribe) when Affiliation == 'owner' orelse Affiliation == 'publisher' orelse Affiliation == 'member' -> case lists:keyfind(Resource, 3, Subscriptions) of {_Resource, Subscription} -> {ok, 'old', Subscription}; false -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)} end; %% options_on_subscribe_node({'access_model', authorize}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, _Affiliation, Subscriptions, Subscription_Options, false = _Multi_Subscribe) -> case lists:keyfind(Resource, 3, Subscriptions) of {_Resource, _Subscription} -> {error, 'not-authorized', 'pending-subscription'}; false -> {ok, 'new', exmpp_pubsub:subscription_pending(Resource, Subscription_Options)} end; %% options_on_subscribe_node({'access_model', whitelist}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, Affiliation, _Subscriptions, Subscription_Options, true = _Multi_Subscribe) when Affiliation == 'owner' orelse Affiliation == 'publisher' orelse Affiliation == 'member' -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)}; %% options_on_subscribe_node({'access_model', whitelist}, _Host, _Node_Options, _Node_Owners, _Entity, Resource, Affiliation, Subscriptions, Subscription_Options, false = _Multi_Subscribe) when Affiliation == 'owner' orelse Affiliation == 'publisher' orelse Affiliation == 'member' -> case lists:keyfind(Resource, 3, Subscriptions) of {_Resource, Subscription} -> {ok, 'old', Subscription}; false -> {ok, 'new', exmpp_pubsub:subscription_subscribed(Resource, Subscription_Options)} end; %% options_on_subscribe_node({'access_model', whitelist}, _Host, _Node_Options, _Node_Owners, _Entity, _Resource, _Affiliation, _Subscriptions, _Subscription_Options, _Multi_Subscribe) -> {error, 'not-allowed', 'closed-node'}. %-- Unsubscribe_Node --% -spec(unsubscribe_node/6 :: ( Suffix :: atom(), Host :: xmpp_jid:raw_jid_component_bare(), Node_Access_Model :: pubsub_options:access_model(), Pubsub_State :: mod_pubsub_dev:pubsub_state_member() | mod_pubsub_dev:pubsub_state_outcast() | mod_pubsub_dev:pubsub_state_owner() | mod_pubsub_dev:pubsub_state_publisher(), Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()}, SubId :: undefined | exmpp_pubsub:subId()) -> {ok, Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()}, SubId :: exmpp_pubsub:subId()} %%% | {error, 'unexpected', 'not-subscribed'} | {error, 'not-acceptable', 'invalid-subid'} | {error, 'bad-request', 'subid-required'} | {error, 'not-acceptable', 'invalid-subid'} ). unsubscribe_node(Suffix, Host, Node_Access_Model, #pubsub_state_dev{subscriptions = [{Subscription_State, SubId, Resource, _}]} = Pubsub_State, Resource, _SubId) when _SubId == undefined orelse _SubId == SubId -> mnesia:write(table('pubsub_state', Suffix), Pubsub_State#pubsub_state_dev{ subscriptions = [] }, write), %% unregister pending subscription case Subscription_State of 'pending' -> unregister_pending_subscription(Suffix, Pubsub_State#pubsub_state_dev.id, SubId); _ -> ok end, case Node_Access_Model of 'roster' -> ok; _ -> ok end, {ok, Resource, SubId}; %% unsubscribe_node(Suffix, Host, Node_Access_Model, #pubsub_state_dev{subscriptions = [{_Subscription_State, SubId, _Resource, _}]} = Pubsub_State, Resource, SubId) -> {error, 'unexpected', 'not-subscribed'}; %% unsubscribe_node(Suffix, Host, Node_Access_Model, #pubsub_state_dev{subscriptions = [{_Subscription_State, _SubId, Resource, _}]} = Pubsub_State, Resource, SubId) -> {error, 'not-acceptable', 'invalid-subid'}; %% unsubscribe_node(Suffix, Host, Node_Access_Model, Pubsub_State, Resource, undefined = _SubId) -> case lists:keytake(Resource, 3, Pubsub_State#pubsub_state_dev.subscriptions) of {value, {Subscription_State, SubId, Resource, _} = Subscription, Subscriptions} -> case lists:keymember(Resource, 3, Subscriptions) of false -> mnesia:write(table('pubsub_state', Suffix), Pubsub_State#pubsub_state_dev{ subscriptions = Subscriptions }, write), %% unregister pending subscription case Subscription_State of 'pending' -> unregister_pending_subscription(Suffix, Pubsub_State#pubsub_state_dev.id, SubId); _ -> ok end, case Node_Access_Model of 'roster' -> ok; _ -> ok end, %% TODO : unregister 'pubsub#tempsub' {ok, Resource, SubId}; true -> {error, 'bad-request', 'subid-required'} end; false -> {error, 'unexpected', 'not-subscribed'} end; %% unsubscribe_node(Suffix, Host, Node_Access_Model, Pubsub_State, Resource, SubId) -> case lists:keytake(SubId, 2, Pubsub_State#pubsub_state_dev.subscriptions) of {value, {Subscription_State, SubId, Resource, _} = Subscription, Subscriptions} -> mnesia:write(table('pubsub_state', Suffix), Pubsub_State#pubsub_state_dev{ subscriptions = Subscriptions }, write), %% unregister pending subscription case Subscription_State of 'pending' -> unregister_pending_subscription(Suffix, Pubsub_State#pubsub_state_dev.id, SubId); _ -> ok end, case Node_Access_Model of 'roster' -> ok; _ -> ok end, {ok, Resource, SubId}; {value, {_Subscription_State, SubId, _Resource, _} = Subscription, _} -> {error, 'unexpected', 'not-subscribed'}; false -> {error, 'not-acceptable', 'invalid-subid'} end. %% -spec(register_pending_subscription/3 :: ( Suffix :: atom(), StateId :: {Entity::xmpp_jid:usr_bare(), NodeIdx::exmpp_pubsub:nodeIdx()}, SubId :: exmpp_pubsub:subId()) -> 'ok' ). register_pending_subscription(Suffix, {Entity, NodeIdx} = _StateId, SubId) -> Table_Pubsub_Subscription_Pending = table('pubsub_subscription_pending', Suffix), case mnesia:read(Table_Pubsub_Subscription_Pending, {Entity, NodeIdx}, write) of [] -> mnesia:write(Table_Pubsub_Subscription_Pending, #pubsub_subscription_pending{ id = {Entity, NodeIdx}, nodeidx = NodeIdx, subids = [SubId] }, write); [Pubsub_Subscription_Pending] -> mnesia:write(Table_Pubsub_Subscription_Pending, Pubsub_Subscription_Pending#pubsub_subscription_pending{ subids = [SubId | Pubsub_Subscription_Pending #pubsub_subscription_pending.subids] }, write) end. %% -spec(unregister_pending_subscription/3 :: ( Suffix :: atom(), StateId :: {Entity::xmpp_jid:usr_bare(), NodeIdx::exmpp_pubsub:nodeIdx()}, SubId :: exmpp_pubsub:subId()) -> 'ok' ). unregister_pending_subscription(Suffix, StateId, SubId) -> Table_Pubsub_Subscription_Pending = table('pubsub_subscription_pending', Suffix), case mnesia:read(Table_Pubsub_Subscription_Pending, StateId, write) of %% [#pubsub_subscription_pending{subids = [SubId]} = Pubsub_Subscription_Pending] -> mnesia:delete_object(Table_Pubsub_Subscription_Pending, Pubsub_Subscription_Pending, write); %% [Pubsub_Subscription_Pending] -> mnesia:write(Table_Pubsub_Subscription_Pending, Pubsub_Subscription_Pending#pubsub_subscription_pending{ subids = lists:delete(SubId, Pubsub_Subscription_Pending#pubsub_subscription_pending.subids) }, write); [] -> ok end. %-- Set_Configure_Subscription --% -spec(set_configure_subscription/5 :: ( Suffix :: atom(), Pubsub_State :: mod_pubsub_dev:pubsub_state_member() | mod_pubsub_dev:pubsub_state_owner() | mod_pubsub_dev:pubsub_state_publisher(), SubId :: undefined | exmpp_pubsub:subId(), Resource :: undefined | xmpp_jid:resource_jid() | {'caps', xmpp_jid:resource_jid()}, Subscription_Options :: pubsub_options:options_subscription()) -> 'ok' %%% | {error, 'unexpected-request', 'not-subscribed'} | {error, 'bad-request', 'subid-required'} | {error, 'forbidden'} | {error, 'not-acceptable', 'invalid-subid'} ). set_configure_subscription(Suffix, Pubsub_State, undefined = _SubId, Resource, Subscription_Options) -> case lists:keytake(Resource, 3, Pubsub_State#pubsub_state_dev.subscriptions) of {value, {Subscription_State, SubId, Resource, _Subscription_Options}, Subscriptions} -> case lists:keymember(Resource, 3, Subscriptions) of false when Subscription_State == 'pending' -> {error, 'forbidden'}; false -> mnesia:write(table('pubsub_state', Suffix), Pubsub_State#pubsub_state_dev{ subscriptions = [ {Subscription_State, SubId, Resource, Subscription_Options} | Subscriptions] }, write), 'ok'; true -> {error, 'bad-request', 'subid-required'} end; false -> {error, 'unexpected-request', 'not-subscribed'} end; %% set_configure_subscription(Suffix, Pubsub_State, SubId, Resource, Subscription_Options) -> case lists:keytake(SubId, 2, Pubsub_State#pubsub_state_dev.subscriptions) of {value, {'pending', SubId, Resource, _Subscription_Options}, _Subscriptions} -> {error, 'forbidden'}; {value, {Subscription_State, SubId, Resource, _Subscription_Options}, Subscriptions} -> mnesia:write(table('pubsub_state', Suffix), Pubsub_State#pubsub_state_dev{ subscriptions = [ {Subscription_State, SubId, Resource, Subscription_Options} | Subscriptions] }, write), 'ok'; {value, {_Subscription_State, SubId, _Resource, _Subscription_Options}, _Subscriptions} -> {error, 'unexpected-request', 'not-subscribed'}; false -> case lists:keytake(Resource, 3, Pubsub_State#pubsub_state_dev.subscriptions) of {value, {'pending', _SubId, Resource, _Subscription_Options}, _Subscriptions} -> {error, 'forbidden'}; {value, {_Subscription_State, _SubId, _Resource, _Subscription_Options}, _Subscriptions} -> {error, 'not-acceptable', 'invalid-subid'}; false -> {error, 'unexpected-request', 'not-subscribed'} end end. %-- Get_Items --% -spec(get_items/5 :: ( Suffix :: pubsub_db_mnesia:table(), Host :: xmpp_jid:raw_jid_component_bare(), Node_Parameters :: {NodeIdx :: exmpp_pubsub:nodeIdx(), Node_Owners :: mod_pubsub_dev:node_owners(), Node_ItemIds :: [] | exmpp_pubsub:itemIds(), Node_Access_Model :: pubsub_options:access_model(), Node_Groups :: pubsub_options:rosters_groups_allowed()}, %% Entity_Parameters :: {Entity :: xmpp_jid:usr_bare(), Affiliation :: exmpp_pubsub:affiliation(), Access :: undefined | 'pending' | 'roster' | 'presence' | 'authorize'}, %% Criteria :: {Max_Items :: non_neg_integer(), ItemIds :: undefined, _SubId :: exmpp_pubsub:subId()} | {Max_Items :: undefined, ItemIds :: exmpp_pubsub:itemIds(), _SubId :: exmpp_pubsub:subId()}) -> {ok, Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(), Cache :: {Presence_Cache :: undefined, Groups_Cache :: undefined}} % | {ok, Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(), Cache :: {Presence_Cache :: true, Groups_Cache :: undefined}} % | {ok, Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(), Cache :: {Presence_Cache :: undefined, Groups_Cache :: [{Node_Owner :: xmpp_jid:usr_bare(), Groups :: [Group::binary()]}]}} %%% | {error, 'forbidden'} | {error, 'not-authorized', 'presence-subscription-required'} | {error, 'not-authorized', 'not-in-roster-group'} | {error, 'not-allowed', 'closed-node'} ). get_items(_Suffix, _Host, _Node_Parameters, {_Entity, Affiliation, Access}, _Criteria) when Affiliation == 'outcast' orelse Affiliation == 'publish-only' orelse Access == 'pending' -> {error, 'forbidden'}; %% get_items(Suffix, _Host, {NodeIdx, _Node_Owners, Node_ItemIds, Node_Access_Model, _Node_Groups}, {_Entity, Affiliation, Access}, {Max_Items, ItemIds, _SubId}) when Affiliation == 'owner' % orelse Affiliation == 'publisher' % orelse Affiliation == 'member' % orelse Node_Access_Model == 'open' % orelse (Node_Access_Model == 'presence' andalso Access == 'presence' orelse Access == 'roster') % orelse (Node_Access_Model == 'roster' andalso Access == 'roster') % orelse (Node_Access_Model == 'authorize' andalso Access == 'authorize') -> {ok, _Pusbub_Items = get_items(Suffix, NodeIdx, get_max_itemids(Max_Items, ItemIds, Node_ItemIds)), _Cache = {undefined, undefined}}; % get_items(Suffix, Host, {NodeIdx, Node_Owners, Node_ItemIds, 'presence' = _Node_Access_Model, _Node_Groups}, {Entity, _Affiliation, _Access}, {Max_Items, ItemIds, _SubId}) -> case is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners) of false -> {error, 'not-authorized', 'presence-subscription-required'}; Node_Owner -> {ok, _Pusbub_Items = get_items(Suffix, NodeIdx, get_max_itemids(Max_Items, ItemIds, Node_ItemIds)), _Cache = {_Presence_Cache = true, _Groups_Cache = undefined}} end; %% get_items(Suffix, _Host, {NodeIdx, _Node_Owners, Node_ItemIds, 'roster' = _Node_Access_Model, Node_Groups}, {Entity, _Affiliation, _Access}, {Max_Items, ItemIds, _SubId}) -> case is_contact_in_allowed_roster_groups(Entity, Node_Groups) of {Node_Owner, Roster_Group} -> {ok, _Pusbub_Items = get_items(Suffix, NodeIdx, get_max_itemids(Max_Items, ItemIds, Node_ItemIds)), _Cache = { _Presence_Cache = undefined, _Groups_Cache = [{Node_Owner, [Roster_Group]}] } }; false -> {error, 'not-authorized', 'not-in-roster-group'} end; %% get_items(_Suffix, _Host, {_NodeIdx, _Node_Owners, _Node_ItemIds, 'authorize' = _Node_Access_Model, _Node_Groups}, _Entity_Parameters, _Criteria) -> {error, 'not-allowed', 'closed-node'}; %% get_items(_Suffix, _Host, _Node_Parameters, _Entity_Parameters, _Criteria) -> {error, 'forbidden'}. %% -spec(get_max_itemids/3 :: ( Max_Items :: undefined | non_neg_integer(), ItemIds :: [] | exmpp_pubsub:itemIds(), Node_ItemIds :: [] | exmpp_pubsub:itemIds()) -> Max_ItemIds :: undefined | [] | exmpp_pubsub:itemIds() ). get_max_itemids(undefined = _Max_Items, [] = _ItemIds, _Node_ItemIds) -> _Max_ItemIds = undefined; %% get_max_itemids(undefined = _Max_Items, ItemIds, Node_ItemIds) -> _Max_ItemIds = lists:filter(fun (ItemId) -> lists:member(ItemId, Node_ItemIds) end, ItemIds); %% get_max_itemids(Max_Items, undefined = _ItemIds, Node_ItemIds) -> _Max_ItemIds = get_max_itemids(Max_Items, [], Node_ItemIds); %% get_max_itemids(_Max_Items, ItemIds, _Node_ItemIds) when _Max_Items == 0 orelse _Node_ItemIds == [] -> _Max_ItemIds = lists:reverse(ItemIds); %% get_max_itemids(Max_Items, ItemIds, [ItemId | Node_ItemIds]) -> get_max_itemids(Max_Items - 1, [ItemId | ItemIds], Node_ItemIds). %% -spec(get_items/3 :: ( Suffix :: pubsub_db_mnesia:suffix(), NodeIdx :: exmpp_pubsub:nodeIdx(), ItemIds :: undefined | [] | exmpp_pubsub:itemIds()) -> Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items() ). get_items(Suffix, NodeIdx, undefined = _ItemIds) -> _Pubsub_Items = mnesia:index_read(table('pubsub_item', Suffix), NodeIdx, nodeidx); %% get_items(Suffix, NodeIdx, ItemIds) -> Table_Pubsub_Item = table('pubsub_item', Suffix), _Pubsub_Items = lists:foldl(fun (ItemId, Pubsub_Items) -> case mnesia:index_match_object(Table_Pubsub_Item, #pubsub_item_dev{ id = {ItemId, NodeIdx}, nodeidx = NodeIdx, _ = '_' }, nodeidx, read) of [Pubsub_Item] -> [Pubsub_Item | Pubsub_Items]; _ -> Pubsub_Items end end, [], ItemIds).