Blob Blame History Raw
From 7be587955e435f9ceff4fa63f7a2f25838f1a858 Mon Sep 17 00:00:00 2001
From: Badlop <badlop@process-one.net>
Date: Mon, 21 Feb 2011 22:33:23 +0100
Subject: [PATCH 11/11] Add support for '@online@' Shared Roster Group (thanks to Martin Langhoff)(EJAB-1391)

New version of the @online@ patch originally by Collabora.
Notes:
- the presence push is mediated via the group rather than
  per user - this may reduce memory footprint... _if_ ejabberd
  has some smart optimisation in that codepath
- it assumes that any group with membership @online@ _displays_
  online as well -- this is a simplification and breaks the
  decoupling that ejabberd has in this regard.
---
 doc/guide.tex             |   10 ++--
 src/mod_shared_roster.erl |  113 +++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 111 insertions(+), 12 deletions(-)

diff --git a/doc/guide.tex b/doc/guide.tex
index 0ae3053..8fda148 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -4020,11 +4020,13 @@ has a unique identification and the following parameters:
 \item[Name] The name of the group, which will be displayed in the roster.
 \item[Description] The description of the group. This parameter does not affect
   anything.
-\item[Members] A list of full JIDs of group members, entered one per line in
+\item[Members] A list of JIDs of group members, entered one per line in
   the Web Admin.
-  To put as members all the registered users in the virtual hosts,
-  you can use the special directive: @all@.
-  Note that this directive is designed for a small server with just a few hundred users.
+  The special member directive \term{@all@}
+  represents all the registered users in the virtual host;
+  which is only recommended for a small server with just a few hundred users.
+  The special member directive \term{@online@}
+  represents the online users in the virtual host.
 \item[Displayed groups] A list of groups that will be in the rosters of this
   group's members.
 \end{description}
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index 64a8291..6f58b0e 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -37,6 +37,8 @@
 	 process_item/2,
 	 in_subscription/6,
 	 out_subscription/4,
+	 user_available/1,
+	 unset_presence/4,
 	 register_user/2,
 	 remove_user/2,
 	 list_groups/1,
@@ -85,6 +87,10 @@ start(Host, _Opts) ->
         	       ?MODULE, get_jid_info, 70),
     ejabberd_hooks:add(roster_process_item, Host,
 		       ?MODULE, process_item, 50),
+    ejabberd_hooks:add(user_available_hook, Host,
+		       ?MODULE, user_available, 50),
+    ejabberd_hooks:add(unset_presence_hook, Host,
+		       ?MODULE, unset_presence, 50),
     ejabberd_hooks:add(register_user, Host,
 		       ?MODULE, register_user, 50),
     ejabberd_hooks:add(remove_user, Host,
@@ -109,6 +115,10 @@ stop(Host) ->
         		  ?MODULE, get_jid_info, 70),
     ejabberd_hooks:delete(roster_process_item, Host,
 			  ?MODULE, process_item, 50),
+    ejabberd_hooks:delete(user_available_hook, Host,
+			  ?MODULE, user_available, 50),
+    ejabberd_hooks:delete(unset_presence_hook, Host,
+			  ?MODULE, unset_presence, 50),
     ejabberd_hooks:delete(register_user, Host,
 			  ?MODULE, register_user, 50),
     ejabberd_hooks:delete(remove_user, Host,
@@ -470,21 +480,38 @@ get_group_opt(Host, Group, Opt, Default) ->
 	    Default
     end.
 
+get_online_users(Host) ->
+    lists:usort([{U, S} || {U, S, _} <- ejabberd_sm:get_vh_session_list(Host)]).
+
 get_group_users(Host, Group) ->
     case get_group_opt(Host, Group, all_users, false) of
 	true ->
 	    ejabberd_auth:get_vh_registered_users(Host);
 	false ->
 	    []
-    end ++ get_group_explicit_users(Host, Group).
+    end ++
+    case get_group_opt(Host, Group, online_users, false) of
+	true ->
+	    get_online_users(Host);
+	false ->
+	    []
+    end ++
+    get_group_explicit_users(Host, Group).
 
-get_group_users(_User, Host, Group, GroupOpts) ->
+get_group_users(Host, Group, GroupOpts) ->
     case proplists:get_value(all_users, GroupOpts, false) of
 	true ->
 	    ejabberd_auth:get_vh_registered_users(Host);
 	false ->
 	    []
-    end ++ get_group_explicit_users(Host, Group).
+    end ++
+    case proplists:get_value(online_users, GroupOpts, false) of
+	true ->
+	    get_online_users(Host);
+	false ->
+	    []
+    end ++
+    get_group_explicit_users(Host, Group).
 
 %% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}]
 get_group_explicit_users(Host, Group) ->
@@ -502,11 +529,20 @@ get_group_explicit_users(Host, Group) ->
 get_group_name(Host, Group) ->
     get_group_opt(Host, Group, name, Group).
 
-%% Get list of names of groups that have @all@ in the memberlist
+%% Get list of names of groups that have @all@/@online@/etc in the memberlist
 get_special_users_groups(Host) ->
     lists:filter(
       fun(Group) ->
 	      get_group_opt(Host, Group, all_users, false)
+		  orelse get_group_opt(Host, Group, online_users, false)
+      end,
+      list_groups(Host)).
+
+%% Get list of names of groups that have @online@ in the memberlist
+get_special_users_groups_online(Host) ->
+    lists:filter(
+      fun(Group) ->
+	      get_group_opt(Host, Group, online_users, false)
       end,
       list_groups(Host)).
 
@@ -661,7 +697,7 @@ push_user_to_members(User, Server, Subscription) ->
 	      lists:foreach(
 		fun({U, S}) ->
 			push_roster_item(U, S, LUser, LServer, GroupName, Subscription)
-		end, get_group_users(LUser, LServer, Group, GroupOpts))
+		end, get_group_users(LServer, Group, GroupOpts))
       end, lists:usort(SpecialGroups++UserGroups)).
 
 push_user_to_displayed(LUser, LServer, Group, Subscription) ->
@@ -673,7 +709,8 @@ push_user_to_displayed(LUser, LServer, Group, Subscription) ->
 
 push_user_to_group(LUser, LServer, Group, GroupName, Subscription) ->
     lists:foreach(
-      fun({U, S}) ->
+      fun({U, S}) when (U == LUser) and (S == LServer) -> ok;
+         ({U, S}) ->
 	      push_roster_item(U, S, LUser, LServer, GroupName, Subscription)
       end, get_group_users(LServer, Group)).
 
@@ -757,6 +794,51 @@ ask_to_pending(subscribe) -> out;
 ask_to_pending(unsubscribe) -> none;
 ask_to_pending(Ask) -> Ask.
 
+user_available(New) ->
+    LUser = New#jid.luser,
+    LServer = New#jid.lserver,
+    Resources = ejabberd_sm:get_user_resources(LUser, LServer),
+    ?DEBUG("user_available for ~p @ ~p (~p resources)",
+	   [LUser, LServer, length(Resources)]),
+    case length(Resources) of
+	%% first session for this user
+	1 ->
+	    %% This is a simplification - we ignore he 'display'
+	    %% property - @online@ is always reflective.
+	    OnlineGroups = get_special_users_groups_online(LServer),
+	    lists:foreach(
+	      fun(OG) ->
+		      ?DEBUG("user_available: pushing  ~p @ ~p grp ~p",
+			     [LUser, LServer, OG ]),
+		      push_user_to_displayed(LUser, LServer, OG, both)
+	      end, OnlineGroups);
+	_ ->
+	    ok
+    end.
+
+unset_presence(LUser, LServer, Resource, Status) ->
+    Resources = ejabberd_sm:get_user_resources(LUser, LServer),
+    ?DEBUG("unset_presence for ~p @ ~p / ~p -> ~p (~p resources)",
+	   [LUser, LServer, Resource, Status, length(Resources)]),
+    %% if user has no resources left...
+    case length(Resources) of
+	0 ->
+	    %% This is a simplification - we ignore he 'display'
+	    %% property - @online@ is always reflective.
+	    OnlineGroups = get_special_users_groups_online(LServer),
+	    %% for each of these groups...
+	    lists:foreach(
+	      fun(OG) ->
+		      %% Push removal of the old user to members of groups
+		      %% where the group that this uwas members was displayed
+		      push_user_to_displayed(LUser, LServer, OG, remove),
+		      %% Push removal of members of groups that where
+		      %% displayed to the group which thiuser has left
+		      push_displayed_to_user(LUser, LServer, OG, LServer,remove)
+	      end, OnlineGroups);
+	_ ->
+	    ok
+    end.
 
 %%---------------------
 %% Web Admin
@@ -860,6 +942,7 @@ shared_roster_group(Host, Group, Query, Lang) ->
     Name = get_opt(GroupOpts, name, ""),
     Description = get_opt(GroupOpts, description, ""),
     AllUsers = get_opt(GroupOpts, all_users, false),
+    OnlineUsers = get_opt(GroupOpts, online_users, false),
     %%Disabled = false,
     DisplayedGroups = get_opt(GroupOpts, displayed_groups, []),
     Members = mod_shared_roster:get_group_explicit_users(Host, Group),
@@ -869,7 +952,14 @@ shared_roster_group(Host, Group, Query, Lang) ->
 		"@all@\n";
 	    true ->
 		[]
-	end ++ [[us_to_list(Member), $\n] || Member <- Members],
+	end ++
+	if
+	    OnlineUsers ->
+		"@online@\n";
+	    true ->
+		[]
+	end ++
+	[[us_to_list(Member), $\n] || Member <- Members],
     FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups],
     DescNL = length(element(2, regexp:split(Description, "\n"))),
     FGroup =
@@ -953,6 +1043,8 @@ shared_roster_group_parse_query(Host, Group, Query) ->
 			  case SJID of
 			      "@all@" ->
 				  USs;
+			      "@online@" ->
+				  USs;
 			      _ ->
 				  case jlib:string_to_jid(SJID) of
 				      JID when is_record(JID, jid) ->
@@ -967,10 +1059,15 @@ shared_roster_group_parse_query(Host, Group, Query) ->
 		    true -> [{all_users, true}];
 		    false -> []
 		end,
+	    OnlineUsersOpt =
+		case lists:member("@online@", SJIDs) of
+		    true -> [{online_users, true}];
+		    false -> []
+		end,
 
 	    mod_shared_roster:set_group_opts(
 	      Host, Group,
-	      NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt),
+	      NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt ++ OnlineUsersOpt),
 
 	    if
 		NewMembers == error -> error;
-- 
1.7.4.1