[Server-devel] [PATCH] mod_shared_roster: simplified @online@ patch

martin.langhoff at gmail.com martin.langhoff at gmail.com
Mon Feb 23 01:29:04 EST 2009


From: Martin Langhoff <martin at laptop.org>

New version of the @online@ patch originally by Collabora.

With this patch, @online@ works 100% - @recent@ and @nearby@ are
_broken_ however. The author of this patch doubts they ever
worked reliably.

Notes:

 - fixed a typo in is_user_in_group
 - simplified user_available and unset_presence hook handlers
 - 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.
---
 src/mod_shared_roster.erl |  248 +++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 231 insertions(+), 17 deletions(-)

diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index af123c2..d1b169d 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,
@@ -45,7 +47,7 @@
 	 delete_group/2,
 	 get_group_opts/2,
 	 set_group_opts/3,
-	 get_group_users/2,
+	 get_group_users/3,
 	 get_group_explicit_users/2,
 	 is_user_in_group/3,
 	 add_user_to_group/3,
@@ -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,
@@ -131,7 +141,7 @@ get_user_roster(Items, US) ->
 						   get_group_name(S, Group),
 						   Acc2)
 			    end
-		    end, Acc1, get_group_users(S, Group))
+		    end, Acc1, get_group_users(U, S, Group))
 	  end, dict:new(), DisplayedGroups),
 
     %% If partially subscribed users are also in shared roster, show them as
@@ -267,7 +277,7 @@ get_subscription_lists({F, T}, User, Server) ->
 	lists:usort(
 	  lists:flatmap(
 	    fun(Group) ->
-		    get_group_users(LServer, Group)
+		    get_group_users(LUser, LServer, Group)
 	    end, DisplayedGroups)),
     SRJIDs = [{U1, S1, ""} || {U1, S1} <- SRUsers],
     {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
@@ -286,7 +296,7 @@ get_jid_info({Subscription, Groups}, User, Server, JID) ->
 		    fun(User1, Acc2) ->
 			    dict:append(
 			      User1, get_group_name(LServer, Group), Acc2)
-		    end, Acc1, get_group_users(LServer, Group))
+		    end, Acc1, get_group_users(LUser, LServer, Group))
 	  end, dict:new(), DisplayedGroups),
     case dict:find(US1, SRUsers) of
 	{ok, GroupNames} ->
@@ -316,7 +326,7 @@ process_subscription(Direction, User, Server, JID, _Type, Acc) ->
 	lists:usort(
 	  lists:flatmap(
 	    fun(Group) ->
-		    get_group_users(LServer, Group)
+		    get_group_users(LUser, LServer, Group)
 	    end, DisplayedGroups)),
     case lists:member(US1, SRUsers) of
 	true ->
@@ -414,21 +424,103 @@ get_group_opt(Host, Group, Opt, Default) ->
 	    false
     end.
 
-get_group_users(Host, Group) ->
+-record(last_activity, {us, timestamp, status}).
+-record(session, {sid, usr, us, priority, info}).
+
+get_recent_users(Host, Days) ->
+    LServer = jlib:nameprep(Host),
+    %% Convert older time
+    SecOlder = Days*24*60*60,
+
+    %% Get current time
+    {MegaSecs, Secs, _MicroSecs} = now(),
+    TimeStampOlder = MegaSecs * 1000000 + Secs - SecOlder,
+
+    %% Get the list of recently connected users
+    RecentUsers = mnesia:dirty_select(last_activity, 
+        [{#last_activity{us={'$1',LServer},timestamp='$2',_='_'},
+          [{'>','$2',TimeStampOlder}],
+          ['$1'] }]),
+    %% Get the list of connected users
+    OnlineUsers = mnesia:dirty_select(session, 
+        [{#session{us={'$1',LServer}, _='_'}, 
+         [], 
+         ['$1']}]),
+    
+    Users = lists:usort(RecentUsers++OnlineUsers),
+    lists:map(fun(User) -> {User, LServer} end, Users).
+
+get_online_users(Host) ->
+    lists:usort([{U, S} || {U, S, _} <- ejabberd_sm:get_vh_session_list(Host)]).
+
+get_nearby_users(User, Host) ->
+    Resources = ejabberd_sm:get_user_resources(User, Host),
+    Sessions = ejabberd_sm:get_vh_session_list(Host),
+    Nearby = lists:foldl(
+      fun(Resource, Acc1) ->
+        lists:foldl(
+          fun({U, S, R}, Acc2) ->
+            if
+              R == Resource -> {U, S} ++ Acc2;
+              true -> Acc2
+            end
+          end, [], Sessions) ++ Acc1
+      end, [], Resources),
+    lists:usort(Nearby).
+
+get_group_users(User, 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).
-
-get_group_users(_User, Host, Group, GroupOpts) ->
+    end ++
+    case get_group_opt(Host, Group, recent_users_days, 0) of
+	0 ->
+	    [];
+	Days when is_integer(Days) ->
+            get_recent_users(Host, Days)
+    end ++
+    case get_group_opt(Host, Group, online_users, false) of
+	true ->
+	    get_online_users(Host);
+	false ->
+	    []
+    end ++
+    case get_group_opt(Host, Group, nearby_users, false) of
+	true ->
+	    get_nearby_users(User, Host);
+	false ->
+	    []
+    end ++
+    get_group_explicit_users(Host, Group).
+    
+get_group_users(User, 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(recent_users_days, GroupOpts, 0) of
+	0 ->
+	    [];
+	Days when is_integer(Days) ->
+            get_recent_users(Host, Days)
+    end ++
+    case proplists:get_value(online_users, GroupOpts, false) of
+	true ->
+	    get_online_users(Host);
+	false ->
+	    []
+    end ++
+    case proplists:get_value(nearby_users, GroupOpts, false) of
+	true ->
+	    get_nearby_users(User, Host);
+	false ->
+	    []
+    end ++
+    get_group_explicit_users(Host, Group).
 
 %% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}]
 get_group_explicit_users(Host, Group) ->
@@ -446,11 +538,22 @@ 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)
+	get_group_opt(Host, Group, all_users, false) orelse
+	get_group_opt(Host, Group, recent_users_days, 0) > 0 orelse
+	get_group_opt(Host, Group, online_users, false) orelse
+	get_group_opt(Host, Group, nearby_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)).
 
@@ -509,7 +612,7 @@ get_user_displayed_groups(US) ->
 is_user_in_group({_U, S} = US, Group, Host) ->
     case catch mnesia:dirty_match_object(
 		 #sr_user{us=US, group_host={Group, Host}}) of
-        [] -> lists:member(US, get_group_users(S, Group));
+        [] -> lists:member(US, get_group_users(_U, S, Group));
 	_  -> true
     end.
 
@@ -551,7 +654,7 @@ push_members_to_user(LUser, LServer, Group, Host, Subscription) ->
     GroupsOpts = groups_with_opts(LServer),
     GroupOpts = proplists:get_value(Group, GroupsOpts, []),
     GroupName = proplists:get_value(name, GroupOpts, Group),
-    Members = get_group_users(Host, Group),
+    Members = get_group_users(LUser, Host, Group),
     lists:foreach(
       fun({U, S}) ->
 	      push_roster_item(LUser, LServer, U, S, GroupName, Subscription)
@@ -593,7 +696,7 @@ push_user_to_group(LUser, LServer, Group, GroupName, Subscription) ->
     lists:foreach(
       fun({U, S}) ->
 	      push_roster_item(U, S, LUser, LServer, GroupName, Subscription)
-      end, get_group_users(LServer, Group)).
+      end, get_group_users(LUser, LServer, Group)).
 
 %% Get list of groups to which this group is displayed
 displayed_to_groups(GroupName, LServer) ->
@@ -675,6 +778,72 @@ ask_to_pending(subscribe) -> out;
 ask_to_pending(unsubscribe) -> none;
 ask_to_pending(Ask) -> Ask.
 
+%% get a roster item for a contact from a particular user's
+%% perspective, considering both normal and shared roster items
+%% FIXME: is there a more efficient way to do this?
+get_user_roster_item(FromUS, ToUS) ->
+    {FUser, FServer} = FromUS,
+    case catch ejabberd_hooks:run_fold(roster_get, FServer, [], [ToUS]) of
+      Items when is_list(Items) ->
+	case [I || I <- Items, I#roster.jid == {FUser, FServer, []}] of
+	  [Item | _ ] ->
+	    Item;
+	  [] ->
+	    false
+	end;
+      _ ->
+	error
+    end.
+
+user_available(New) ->
+    LUser = New#jid.luser,
+    LServer = New#jid.lserver,
+    Resources = ejabberd_sm:get_user_resources(LUser, LServer),
+    ?INFO_MSG("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) ->
+	         ?INFO_MSG("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),
+    ?INFO_MSG("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 user was members was displayed
+             push_user_to_displayed(LUser, LServer, OG, remove),
+             %% Push removal of members of groups that where displayed to the group which this user has left
+             push_displayed_to_user(LUser, LServer, OG, LServer, remove)          
+	     end, OnlineGroups);
+      _ ->
+	ok
+    end.
 
 %%---------------------
 %% Web Admin
@@ -778,6 +947,9 @@ shared_roster_group(Host, Group, Query, Lang) ->
     Name = get_opt(GroupOpts, name, ""),
     Description = get_opt(GroupOpts, description, ""),
     AllUsers = get_opt(GroupOpts, all_users, false),
+    RecentUsersDays = get_opt(GroupOpts, recent_users_days, 0),
+    OnlineUsers = get_opt(GroupOpts, online_users, false),
+    NearbyUsers = get_opt(GroupOpts, nearby_users, false),
     %%Disabled = false,
     DisplayedGroups = get_opt(GroupOpts, displayed_groups, []),
     Members = mod_shared_roster:get_group_explicit_users(Host, Group),
@@ -787,7 +959,26 @@ shared_roster_group(Host, Group, Query, Lang) ->
 		"@all@\n";
 	    true ->
 		[]
-	end ++ [[us_to_list(Member), $\n] || Member <- Members],
+	end ++
+	if
+	    RecentUsersDays > 0 ->
+		"@recent@\n";
+	    true ->
+		[]
+	end ++
+	if
+	    OnlineUsers ->
+		"@online@\n";
+	    true ->
+		[]
+	end ++
+	if
+	    NearbyUsers ->
+		"@nearby@\n";
+	    true ->
+		[]
+	end ++
+	[[us_to_list(Member), $\n] || Member <- Members],
     FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups],
     FGroup =
 	?XAE("table", [],
@@ -870,6 +1061,12 @@ shared_roster_group_parse_query(Host, Group, Query) ->
 			  case SJID of
 			      "@all@" ->
 				  USs;
+			      "@recent@" ->
+				  USs;
+			      "@online@" ->
+				  USs;
+			      "@nearby@" ->
+				  USs;
 			      _ ->
 				  case jlib:string_to_jid(SJID) of
 				      JID when is_record(JID, jid) ->
@@ -884,10 +1081,27 @@ shared_roster_group_parse_query(Host, Group, Query) ->
 		    true -> [{all_users, true}];
 		    false -> []
 		end,
+	    %% FIXME: this should be specified by the user
+	    RecentUsersOpt =
+		case lists:member("@recent@", SJIDs) of
+		    true -> [{recent_users_days, 7}];
+		    false -> []
+		end,
+	    OnlineUsersOpt =
+		case lists:member("@online@", SJIDs) of
+		    true -> [{online_users, true}];
+		    false -> []
+		end,
+	    NearbyUsersOpt =
+		case lists:member("@nearby@", SJIDs) of
+		    true -> [{nearby_users, true}];
+		    false -> []
+		end,
 
 	    mod_shared_roster:set_group_opts(
 	      Host, Group,
-	      NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt),
+	      NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt
+		++ RecentUsersOpt ++ OnlineUsersOpt ++ NearbyUsersOpt),
 
 	    if
 		NewMembers == error -> error;
-- 
1.5.6.6



More information about the Server-devel mailing list