From 38e8853e5761d09a7e8f580dd277d9b99b834696 Mon Sep 17 00:00:00 2001 From: Homer Horwitz Date: Sat, 1 Nov 2008 22:09:48 +0000 Subject: [PATCH] Megapatch that fixes/adds: friend offer/deny/accept, friendship termination, on-/offline updates, calling cards for friends. This adds methods in the DB layer and changes the MessagingServer, so a full update (incl. UGAIM) is necessary to get it working. Older regions shouldn't break, nor should older UGAIM break newer regions, but friends/presence will only work with all concerned parts (UGAIM, source region and destination region) at this revision (or later). I added the DB code for MSSQL, too, but couldn't test that. BEWARE: May contain bugs. --- OpenSim/Data/MSSQL/MSSQLUserData.cs | 33 + OpenSim/Data/MySQL/MySQLUserData.cs | 43 + OpenSim/Data/NHibernate/NHibernateUserData.cs | 1 + OpenSim/Data/SQLite/SQLiteUserData.cs | 22 +- OpenSim/Data/UserDataBase.cs | 1 + .../Communications/CommunicationsManager.cs | 21 + .../IInterRegionCommunications.cs | 38 + .../Communications/IMessagingService.cs | 37 + .../Communications/UserManagerBase.cs | 24 +- OpenSim/Framework/FriendListItem.cs | 2 +- OpenSim/Framework/FriendRegionInfo.cs | 37 + OpenSim/Framework/IClientAPI.cs | 2 + OpenSim/Framework/IUserData.cs | 11 + OpenSim/Framework/NetworkServersInfo.cs | 4 + OpenSim/Framework/Servers/BaseHttpServer.cs | 13 +- OpenSim/Grid/MessagingServer/Main.cs | 1 + .../Grid/MessagingServer/MessageService.cs | 475 +++---- .../Grid/MessagingServer/UserPresenceData.cs | 2 +- OpenSim/Region/Application/OpenSimBase.cs | 3 +- .../ClientStack/LindenUDP/LLClientView.cs | 23 +- .../Local/CommunicationsLocal.cs | 4 +- .../Local/LocalBackEndServices.cs | 24 +- .../Communications/OGS1/CommunicationsOGS1.cs | 4 +- .../Communications/OGS1/OGS1GridServices.cs | 80 +- .../Communications/OGS1/OGS1UserServices.cs | 60 +- .../Modules/Avatar/Friends/FriendsModule.cs | 1155 +++++++++++------ .../InstantMessage/InstantMessageModule.cs | 10 +- .../Modules/World/NPC/NPCAvatar.cs | 4 + OpenSim/Region/Environment/Scenes/Scene.cs | 14 + .../Examples/SimpleModule/MyNpcCharacter.cs | 4 + bin/OpenSim.Data.addin.xml | 1 + bin/OpenSim.ini.example | 3 + 32 files changed, 1402 insertions(+), 754 deletions(-) create mode 100644 OpenSim/Framework/Communications/IMessagingService.cs create mode 100644 OpenSim/Framework/FriendRegionInfo.cs diff --git a/OpenSim/Data/MSSQL/MSSQLUserData.cs b/OpenSim/Data/MSSQL/MSSQLUserData.cs index ee7765fbe2..4d4b3bc86d 100644 --- a/OpenSim/Data/MSSQL/MSSQLUserData.cs +++ b/OpenSim/Data/MSSQL/MSSQLUserData.cs @@ -570,6 +570,39 @@ namespace OpenSim.Data.MSSQL return friendList; } + override public Dictionary GetFriendRegionInfos (List uuids) + { + Dictionary infos = new Dictionary(); + try + { + foreach (UUID uuid in uuids) + { + using (AutoClosingSqlCommand command = database.Query( + "select agentOnline,currentHandle from " + m_agentsTableName + " where UUID = @uuid")) + { + command.Parameters.Add(database.CreateParameter("@uuid", uuid)); + + using (IDataReader reader = command.ExecuteReader()) + { + while (reader.Read()) + { + FriendRegionInfo fri = new FriendRegionInfo(); + fri.isOnline = (sbyte)reader["agentOnline"] != 0; + fri.regionHandle = (ulong)reader["currentHandle"]; + + infos[uuid] = fri; + } + } + } + } + } + catch (Exception e) + { + m_log.Warn("[MSSQL]: Got exception on trying to find friends regions:", e); + } + + return infos; + } #endregion #region Money functions (not used) diff --git a/OpenSim/Data/MySQL/MySQLUserData.cs b/OpenSim/Data/MySQL/MySQLUserData.cs index e6719896a2..c3aade0d9f 100644 --- a/OpenSim/Data/MySQL/MySQLUserData.cs +++ b/OpenSim/Data/MySQL/MySQLUserData.cs @@ -365,6 +365,49 @@ namespace OpenSim.Data.MySQL return Lfli; } + override public Dictionary GetFriendRegionInfos (List uuids) + { + MySQLSuperManager dbm = GetLockedConnection("GetFriendRegionInfos"); + Dictionary infos = new Dictionary(); + + try + { + foreach (UUID uuid in uuids) + { + Dictionary param = new Dictionary(); + param["?uuid"] = uuid.ToString(); + IDbCommand result = + dbm.Manager.Query("select agentOnline,currentHandle from " + m_agentsTableName + + " where UUID = ?uuid", param); + + IDataReader reader = result.ExecuteReader(); + while (reader.Read()) + { + FriendRegionInfo fri = new FriendRegionInfo(); + fri.isOnline = (sbyte)reader["agentOnline"] != 0; + fri.regionHandle = (ulong)reader["currentHandle"]; + + infos[uuid] = fri; + } + + reader.Dispose(); + result.Dispose(); + } + } + catch (Exception e) + { + m_log.Warn("[MYSQL]: Got exception on trying to find friends regions:", e); + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + } + finally + { + dbm.Release(); + } + + return infos; + } + #endregion public override void UpdateUserCurrentRegion(UUID avatarid, UUID regionuuid, ulong regionhandle) diff --git a/OpenSim/Data/NHibernate/NHibernateUserData.cs b/OpenSim/Data/NHibernate/NHibernateUserData.cs index 459b8e704e..5ef48c8784 100644 --- a/OpenSim/Data/NHibernate/NHibernateUserData.cs +++ b/OpenSim/Data/NHibernate/NHibernateUserData.cs @@ -255,6 +255,7 @@ namespace OpenSim.Data.NHibernate public override void RemoveUserFriend(UUID friendlistowner, UUID friend) { return; } public override void UpdateUserFriendPerms(UUID friendlistowner, UUID friend, uint perms) { return; } public override List GetUserFriendList(UUID friendlistowner) { return new List(); } + public override Dictionary GetFriendRegionInfos (List uuids) { return new Dictionary(); } public override bool MoneyTransferRequest(UUID from, UUID to, uint amount) { return true; } public override bool InventoryTransferRequest(UUID from, UUID to, UUID inventory) { return true; } diff --git a/OpenSim/Data/SQLite/SQLiteUserData.cs b/OpenSim/Data/SQLite/SQLiteUserData.cs index 82db48149f..2f0863e6b1 100644 --- a/OpenSim/Data/SQLite/SQLiteUserData.cs +++ b/OpenSim/Data/SQLite/SQLiteUserData.cs @@ -332,8 +332,28 @@ namespace OpenSim.Data.SQLite return returnlist; } + override public Dictionary GetFriendRegionInfos (List uuids) + { + Dictionary infos = new Dictionary(); - + DataTable agents = ds.Tables["useragents"]; + foreach (UUID uuid in uuids) + { + lock (ds) + { + DataRow row = agents.Rows.Find(Util.ToRawUuidString(uuid)); + if (row == null) infos[uuid] = null; + else + { + FriendRegionInfo fri = new FriendRegionInfo(); + fri.isOnline = (bool)row["agentOnline"]; + fri.regionHandle = Convert.ToUInt64(row["currentHandle"]); + infos[uuid] = fri; + } + } + } + return infos; + } #endregion diff --git a/OpenSim/Data/UserDataBase.cs b/OpenSim/Data/UserDataBase.cs index 33b8a94ed3..a5fc8c874a 100644 --- a/OpenSim/Data/UserDataBase.cs +++ b/OpenSim/Data/UserDataBase.cs @@ -53,6 +53,7 @@ namespace OpenSim.Data public abstract void RemoveUserFriend(UUID friendlistowner, UUID friend); public abstract void UpdateUserFriendPerms(UUID friendlistowner, UUID friend, uint perms); public abstract List GetUserFriendList(UUID friendlistowner); + public abstract Dictionary GetFriendRegionInfos (List uuids); public abstract bool MoneyTransferRequest(UUID from, UUID to, uint amount); public abstract bool InventoryTransferRequest(UUID from, UUID to, UUID inventory); public abstract List GeneratePickerResults(UUID queryID, string query); diff --git a/OpenSim/Framework/Communications/CommunicationsManager.cs b/OpenSim/Framework/Communications/CommunicationsManager.cs index dfe0fdcc24..bb4a853672 100644 --- a/OpenSim/Framework/Communications/CommunicationsManager.cs +++ b/OpenSim/Framework/Communications/CommunicationsManager.cs @@ -50,6 +50,12 @@ namespace OpenSim.Framework.Communications } protected IUserService m_userService; + public IMessagingService MessageService + { + get { return m_messageService; } + } + protected IMessagingService m_messageService; + public IGridServices GridService { get { return m_gridService; } @@ -370,6 +376,21 @@ namespace OpenSim.Framework.Communications return m_userService.GetUserFriendList(friendlistowner); } + public Dictionary GetFriendRegionInfos(List uuids) + { + return m_messageService.GetFriendRegionInfos(uuids); + } + + public List InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List friends, bool online) + { + return m_interRegion.InformFriendsInOtherRegion(agentId, destRegionHandle, friends, online); + } + + public bool TriggerTerminateFriend(ulong regionHandle, UUID agentID, UUID exFriendID) + { + return m_interRegion.TriggerTerminateFriend(regionHandle, agentID, exFriendID); + } + #endregion #region Packet Handlers diff --git a/OpenSim/Framework/Communications/IInterRegionCommunications.cs b/OpenSim/Framework/Communications/IInterRegionCommunications.cs index 3dd556118b..6b589b9d6d 100644 --- a/OpenSim/Framework/Communications/IInterRegionCommunications.cs +++ b/OpenSim/Framework/Communications/IInterRegionCommunications.cs @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using System.Collections.Generic; using OpenMetaverse; namespace OpenSim.Framework.Communications @@ -46,5 +47,42 @@ namespace OpenSim.Framework.Communications bool AcknowledgePrimCrossed(ulong regionHandle, UUID primID); bool TellRegionToCloseChildConnection(ulong regionHandle, UUID agentID); + + /// + /// Try to inform friends in the given region about online status of agent. + /// + /// + /// The of the agent. + /// + /// + /// The regionHandle of the region. + /// + /// + /// A List of s of friends to inform in the given region. + /// + /// + /// Is the agent online or offline + /// + /// + /// A list of friends that couldn't be reached on this region. + /// + List InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List friends, bool online); + + /// + /// Send TerminateFriend of exFriendID to agent agentID in region regionHandle. + /// + /// + /// The handle of the region agentID is in (hopefully). + /// + /// + /// The agent to send the packet to. + /// + /// + /// The ex-friends ID. + /// + /// + /// Whether the packet could be sent. False if the agent couldn't be found in the region. + /// + bool TriggerTerminateFriend(ulong regionHandle, UUID agentID, UUID exFriendID); } } diff --git a/OpenSim/Framework/Communications/IMessagingService.cs b/OpenSim/Framework/Communications/IMessagingService.cs new file mode 100644 index 0000000000..5d4cbf8fd0 --- /dev/null +++ b/OpenSim/Framework/Communications/IMessagingService.cs @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using OpenMetaverse; + +namespace OpenSim.Framework.Communications +{ + public interface IMessagingService + { + Dictionary GetFriendRegionInfos (List uuids); + } +} diff --git a/OpenSim/Framework/Communications/UserManagerBase.cs b/OpenSim/Framework/Communications/UserManagerBase.cs index ba9cf274df..7189eee30c 100644 --- a/OpenSim/Framework/Communications/UserManagerBase.cs +++ b/OpenSim/Framework/Communications/UserManagerBase.cs @@ -35,6 +35,7 @@ using OpenMetaverse; using OpenMetaverse.StructuredData; using log4net; using Nwc.XmlRpc; +using OpenSim.Framework; using OpenSim.Framework.Statistics; namespace OpenSim.Framework.Communications @@ -42,7 +43,7 @@ namespace OpenSim.Framework.Communications /// /// Base class for user management (create, read, etc) /// - public abstract class UserManagerBase : IUserService, IUserServiceAdmin, IAvatarService + public abstract class UserManagerBase : IUserService, IUserServiceAdmin, IAvatarService, IMessagingService { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); @@ -285,6 +286,27 @@ namespace OpenSim.Framework.Communications return null; } + public Dictionary GetFriendRegionInfos (List uuids) + { + foreach (IUserDataPlugin plugin in _plugins) + { + try + { + Dictionary result = plugin.GetFriendRegionInfos(uuids); + + if (result != null) + { + return result; + } + } + catch (Exception e) + { + m_log.Info("[USERSTORAGE]: Unable to GetFriendRegionInfos via " + plugin.Name + "(" + e.ToString() + ")"); + } + } + return null; + } + public void StoreWebLoginKey(UUID agentID, UUID webLoginKey) { foreach (IUserDataPlugin plugin in _plugins) diff --git a/OpenSim/Framework/FriendListItem.cs b/OpenSim/Framework/FriendListItem.cs index 2861ce2437..a60bc22844 100644 --- a/OpenSim/Framework/FriendListItem.cs +++ b/OpenSim/Framework/FriendListItem.cs @@ -35,9 +35,9 @@ namespace OpenSim.Framework public UUID FriendListOwner; // These are what the list owner gives the friend permission to do + public uint FriendListOwnerPerms; // These are what the friend gives the listowner permission to do - public uint FriendListOwnerPerms; public uint FriendPerms; public bool onlinestatus = false; diff --git a/OpenSim/Framework/FriendRegionInfo.cs b/OpenSim/Framework/FriendRegionInfo.cs new file mode 100644 index 0000000000..04e00e8e1b --- /dev/null +++ b/OpenSim/Framework/FriendRegionInfo.cs @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; + +namespace OpenSim.Framework +{ + public class FriendRegionInfo + { + public bool isOnline; + public ulong regionHandle; + } +} diff --git a/OpenSim/Framework/IClientAPI.cs b/OpenSim/Framework/IClientAPI.cs index 538a2e7877..94bf3aaffc 100644 --- a/OpenSim/Framework/IClientAPI.cs +++ b/OpenSim/Framework/IClientAPI.cs @@ -925,6 +925,8 @@ namespace OpenSim.Framework void SendAcceptCallingCard(UUID transactionID); void SendDeclineCallingCard(UUID transactionID); + void SendTerminateFriend(UUID exFriendID); + void KillEndDone(); } } diff --git a/OpenSim/Framework/IUserData.cs b/OpenSim/Framework/IUserData.cs index 40892531f1..07159cc51a 100644 --- a/OpenSim/Framework/IUserData.cs +++ b/OpenSim/Framework/IUserData.cs @@ -135,6 +135,17 @@ namespace OpenSim.Framework /// The agent that we're retreiving the friends Data. List GetUserFriendList(UUID friendlistowner); + /// + /// Returns a list of + /// A of , mapping the s to s. + /// + Dictionary GetFriendRegionInfos(List uuids); + /// /// Attempts to move currency units between accounts (NOT RELIABLE / TRUSTWORTHY. DONT TRY RUN YOUR OWN CURRENCY EXCHANGE WITH REAL VALUES) /// diff --git a/OpenSim/Framework/NetworkServersInfo.cs b/OpenSim/Framework/NetworkServersInfo.cs index 9f3014d0d2..49b8ef9110 100644 --- a/OpenSim/Framework/NetworkServersInfo.cs +++ b/OpenSim/Framework/NetworkServersInfo.cs @@ -53,6 +53,7 @@ namespace OpenSim.Framework public string HttpSSLCN = ""; public uint httpSSLPort = 9001; + public string MessagingURL = String.Empty; public NetworkServersInfo() { @@ -102,6 +103,9 @@ namespace OpenSim.Framework "http://127.0.0.1:" + InventoryConfig.DefaultHttpPort.ToString()); secureInventoryServer = config.Configs["Network"].GetBoolean("secure_inventory_server", true); + + MessagingURL = config.Configs["Network"].GetString("messaging_server_url", + "http://127.0.0.1:" + MessageServerConfig.DefaultHttpPort); } } } diff --git a/OpenSim/Framework/Servers/BaseHttpServer.cs b/OpenSim/Framework/Servers/BaseHttpServer.cs index 871ea57c9e..98b44dbce0 100644 --- a/OpenSim/Framework/Servers/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/BaseHttpServer.cs @@ -586,7 +586,18 @@ namespace OpenSim.Framework.Servers XmlRpcMethod method; if (m_rpcHandlers.TryGetValue(methodName, out method)) { - xmlRpcResponse = method(xmlRprcRequest); + try + { + xmlRpcResponse = method(xmlRprcRequest); + } + catch(Exception e) + { + // if the registered XmlRpc method threw an exception, we pass a fault-code along + xmlRpcResponse = new XmlRpcResponse(); + // Code probably set in accordance with http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php + xmlRpcResponse.SetFault(-32603, String.Format("Requested method [{0}] threw exception: {1}", + methodName, e.Message)); + } // if the method wasn't found, we can't determine KeepAlive state anyway, so lets do it only here response.KeepAlive = m_rpcHandlersKeepAlive[methodName]; } diff --git a/OpenSim/Grid/MessagingServer/Main.cs b/OpenSim/Grid/MessagingServer/Main.cs index 998f3fa20a..84725716f8 100644 --- a/OpenSim/Grid/MessagingServer/Main.cs +++ b/OpenSim/Grid/MessagingServer/Main.cs @@ -87,6 +87,7 @@ namespace OpenSim.Grid.MessagingServer m_httpServer.AddXmlRPCHandler("login_to_simulator", msgsvc.UserLoggedOn); m_httpServer.AddXmlRPCHandler("logout_of_simulator", msgsvc.UserLoggedOff); + m_httpServer.AddXmlRPCHandler("get_presence_info_bulk", msgsvc.GetPresenceInfoBulk); m_httpServer.Start(); m_log.Info("[SERVER]: Userserver registration was successful"); diff --git a/OpenSim/Grid/MessagingServer/MessageService.cs b/OpenSim/Grid/MessagingServer/MessageService.cs index 27892dfe03..0be58e3c3f 100644 --- a/OpenSim/Grid/MessagingServer/MessageService.cs +++ b/OpenSim/Grid/MessagingServer/MessageService.cs @@ -37,8 +37,6 @@ using Nwc.XmlRpc; using OpenSim.Data; using OpenSim.Framework; -//using System.Xml; - namespace OpenSim.Grid.MessagingServer { public class MessageService @@ -48,17 +46,11 @@ namespace OpenSim.Grid.MessagingServer private MessageServerConfig m_cfg; private UserManager m_userManager; - //A hashtable of all current presences this server knows about - private Hashtable m_presences = new Hashtable(); + // a dictionary of all current presences this server knows about + private Dictionary m_presences = new Dictionary(); - //a hashtable of all current regions this server knows about - private Hashtable m_regionInfoCache = new Hashtable(); - - //A hashtable containing lists of UUIDs keyed by UUID for fast backreferencing - private Hashtable m_presence_BackReferences = new Hashtable(); - - // Hashtable containing work units that need to be processed - // private Hashtable m_unProcessedWorkUnits = new Hashtable(); + // a dictionary of all current regions this server knows about + private Dictionary m_regionInfoCache = new Dictionary(); public MessageService(MessageServerConfig cfg) { @@ -78,30 +70,60 @@ namespace OpenSim.Grid.MessagingServer /// Process Friendlist subscriptions for a user /// The login method calls this for a User /// - /// The Agent we're processing the friendlist subscriptions - public void ProcessFriendListSubscriptions(UserPresenceData userpresence) + /// The Agent we're processing the friendlist subscriptions for + private void ProcessFriendListSubscriptions(UserPresenceData userpresence) { lock (m_presences) { - if (!m_presences.Contains(userpresence.agentData.AgentID)) - m_presences.Add(userpresence.agentData.AgentID, userpresence); - else - m_presences[userpresence.agentData.AgentID] = userpresence; + m_presences[userpresence.agentData.AgentID] = userpresence; } - List uFriendList = userpresence.friendData; - for (int i = 0; i < uFriendList.Count; i++) + Dictionary uFriendList = userpresence.friendData; + foreach (KeyValuePair pair in uFriendList) { - //m_presence_BackReferences.Add(userpresence.agentData.AgentID, uFriendList[i].Friend); - // m_presence_BackReferences.Add(uFriendList[i].Friend, userpresence.agentData.AgentID); - - if (m_presences.Contains(uFriendList[i].Friend)) + UserPresenceData friendup = null; + lock (m_presences) { - UserPresenceData friendup = (UserPresenceData)m_presences[uFriendList[i].Friend]; - // Add backreference - - SubscribeToPresenceUpdates(userpresence, friendup, uFriendList[i],i); + m_presences.TryGetValue(pair.Key, out friendup); } + if (friendup != null) + { + SubscribeToPresenceUpdates(userpresence, friendup, pair.Value); + } + } + } + + /// + /// Enqueues a presence update, sending info about user 'talkingAbout' to user 'receiver'. + /// + /// We are sending presence information about this user. + /// We are sending the presence update to this user + private void enqueuePresenceUpdate(UserPresenceData talkingAbout, UserPresenceData receiver) + { + UserAgentData p2Handle = m_userManager.GetUserAgentData(receiver.agentData.AgentID); + if (p2Handle != null) + { + if (receiver.lookupUserRegionYN) + { + receiver.regionData.regionHandle = p2Handle.Handle; + } + else + { + receiver.lookupUserRegionYN = true; // TODO Huh? + } + + PresenceInformer friendlistupdater = new PresenceInformer(); + friendlistupdater.presence1 = talkingAbout; + friendlistupdater.presence2 = receiver; + friendlistupdater.OnGetRegionData += GetRegionInfo; + friendlistupdater.OnDone += PresenceUpdateDone; + WaitCallback cb = new WaitCallback(friendlistupdater.go); + ThreadPool.QueueUserWorkItem(cb); + } + else + { + m_log.WarnFormat("no data found for user {0}", receiver.agentData.AgentID); + // Skip because we can't find any data on the user } } @@ -113,139 +135,34 @@ namespace OpenSim.Grid.MessagingServer /// P1 /// P2 /// - /// - public void SubscribeToPresenceUpdates(UserPresenceData userpresence, UserPresenceData friendpresence, - FriendListItem uFriendListItem, int uFriendListIndex) + private void SubscribeToPresenceUpdates(UserPresenceData userpresence, + UserPresenceData friendpresence, + FriendListItem uFriendListItem) { + // Can the friend see me online? if ((uFriendListItem.FriendListOwnerPerms & (uint)FriendRights.CanSeeOnline) != 0) { - // Subscribe and Send Out updates - if (!friendpresence.subscriptionData.Contains(friendpresence.agentData.AgentID)) + // tell user to update friend about user's presence changes + if (!userpresence.subscriptionData.Contains(friendpresence.agentData.AgentID)) { - userpresence.subscriptionData.Add(friendpresence.agentData.AgentID); - //Send Region Notice.... - } - else - { - // we need to send out online status update, but the user is already subscribed - } - UserAgentData p2Handle = m_userManager.GetUserAgentData(userpresence.agentData.AgentID); - if (p2Handle != null) - { - if (userpresence.lookupUserRegionYN) - { - userpresence.regionData.regionHandle = p2Handle.Handle; - } - else - { - userpresence.lookupUserRegionYN = true; - } - PresenceInformer friendlistupdater = new PresenceInformer(); - friendlistupdater.presence1 = friendpresence; - //friendlistupdater.gridserverurl = m_cfg.GridServerURL; - //friendlistupdater.gridserversendkey = m_cfg.GridSendKey; - //friendlistupdater.gridserverrecvkey = m_cfg.GridRecvKey; - friendlistupdater.presence2 = userpresence; - friendlistupdater.OnGetRegionData += GetRegionInfo; - friendlistupdater.OnDone += PresenceUpdateDone; - WaitCallback cb = new WaitCallback(friendlistupdater.go); - ThreadPool.QueueUserWorkItem(cb); - - } - else - { - // Skip because we can't find any data on the user + userpresence.subscriptionData.Add(friendpresence.agentData.AgentID); } - //SendRegionPresenceUpdate(friendpresence, userpresence); + // send an update about user's presence to the friend + enqueuePresenceUpdate(userpresence, friendpresence); } + // Can I see the friend online? if ((uFriendListItem.FriendPerms & (uint)FriendRights.CanSeeOnline) != 0) { + // tell friend to update user about friend's presence changes if (!friendpresence.subscriptionData.Contains(userpresence.agentData.AgentID)) { friendpresence.subscriptionData.Add(userpresence.agentData.AgentID); - //Send Region Notice.... - } - else - { - // we need to send out online status update, but the user is already subscribed } - UserAgentData p2Handle = m_userManager.GetUserAgentData(friendpresence.agentData.AgentID); - - if (p2Handle != null) - { - - friendpresence.regionData.regionHandle = p2Handle.Handle; - PresenceInformer friendlistupdater = new PresenceInformer(); - friendlistupdater.presence1 = userpresence; - friendlistupdater.presence2 = friendpresence; - //friendlistupdater.gridserverurl = m_cfg.GridServerURL; - //friendlistupdater.gridserversendkey = m_cfg.GridSendKey; - //friendlistupdater.gridserverrecvkey = m_cfg.GridRecvKey; - friendlistupdater.OnGetRegionData += GetRegionInfo; - friendlistupdater.OnDone += PresenceUpdateDone; - WaitCallback cb2 = new WaitCallback(friendlistupdater.go); - ThreadPool.QueueUserWorkItem(cb2); - } - else - { - // skip, agent doesn't appear to exist anymore - } - - - - //SendRegionPresenceUpdate(userpresence, friendpresence); - } - } - - /// - /// Adds a backreference so presence specific data doesn't have to be - /// enumerated for each logged in user every time someone logs on or off. - /// - /// - /// - public void addBackReference(UUID agentID, UUID friendID) - { - if (m_presence_BackReferences.Contains(friendID)) - { - List presenseBackReferences = (List)m_presence_BackReferences[friendID]; - if (!presenseBackReferences.Contains(agentID)) - { - presenseBackReferences.Add(agentID); - } - m_presence_BackReferences[friendID] = presenseBackReferences; - } - else - { - List presenceBackReferences = new List(); - presenceBackReferences.Add(agentID); - m_presence_BackReferences[friendID] = presenceBackReferences; - } - } - - /// - /// Removes a backreference to free up some memory - /// - /// - /// - public void removeBackReference(UUID agentID, UUID friendID) - { - if (m_presence_BackReferences.Contains(friendID)) - { - List presenseBackReferences = (List)m_presence_BackReferences[friendID]; - if (presenseBackReferences.Contains(agentID)) - { - presenseBackReferences.Remove(agentID); - } - - // If there are no more backreferences for this agent, - // remove it to free up memory. - if (presenseBackReferences.Count == 0) - { - m_presence_BackReferences.Remove(agentID); - } + // send an update about friend's presence to user. + enqueuePresenceUpdate(friendpresence, userpresence); } } @@ -256,90 +173,42 @@ namespace OpenSim.Grid.MessagingServer private void ProcessLogOff(UUID AgentID) { m_log.Info("[LOGOFF]: Processing Logoff"); - UserPresenceData AgentData = null; - List AgentsNeedingNotification = new List(); - UserPresenceData friendd = null; + + UserPresenceData userPresence = null; lock (m_presences) { - if (m_presences.Contains(AgentID)) - { - AgentData = (UserPresenceData)m_presences[AgentID]; - } + m_presences.TryGetValue(AgentID, out userPresence); } - if (AgentData != null) + if (userPresence != null) // found the user { - AgentsNeedingNotification = AgentData.subscriptionData; - AgentData.OnlineYN = false; - //lock (m_presence_BackReferences) - //{ - //if (m_presence_BackReferences.Contains(AgentID)) - //{ - //AgentsNeedingNotification = (List)m_presence_BackReferences[AgentID]; - //} - //} + List AgentsNeedingNotification = userPresence.subscriptionData; + userPresence.OnlineYN = false; for (int i = 0; i < AgentsNeedingNotification.Count; i++) { - // TODO: Do Region Notifications + UserPresenceData friendPresence = null; lock (m_presences) { - if (m_presences.Contains(AgentsNeedingNotification[i])) - { - friendd = (UserPresenceData)m_presences[AgentsNeedingNotification[i]]; - } + m_presences.TryGetValue(AgentsNeedingNotification[i], out friendPresence); } // This might need to be enumerated and checked before we try to remove it. - if (friendd != null) + if (friendPresence != null) { - lock (friendd) + lock (friendPresence) { - friendd.subscriptionData.Remove(AgentID); + // no updates for this user anymore + friendPresence.subscriptionData.Remove(AgentID); - List fl = friendd.friendData; - for (int j = 0; j < fl.Count; j++) + // set user's entry in the friend's list to offline (if it exists) + if (friendPresence.friendData.ContainsKey(AgentID)) { - if (fl[j].Friend == AgentID) - { - fl[j].onlinestatus = false; - } + friendPresence.friendData[AgentID].onlinestatus = false; } - - friendd.friendData = fl; - m_presences[AgentsNeedingNotification[i]] = friendd; } - UserAgentData p2Handle = m_userManager.GetUserAgentData(friendd.agentData.AgentID); - if (p2Handle != null) - { - - - friendd.regionData.regionHandle = p2Handle.Handle; - PresenceInformer friendlistupdater = new PresenceInformer(); - friendlistupdater.presence1 = AgentData; - friendlistupdater.presence2 = friendd; - - //friendlistupdater.gridserverurl = m_cfg.GridServerURL; - //friendlistupdater.gridserversendkey = m_cfg.GridSendKey; - //friendlistupdater.gridserverrecvkey = m_cfg.GridRecvKey; - - friendlistupdater.OnGetRegionData += GetRegionInfo; - friendlistupdater.OnDone += PresenceUpdateDone; - - WaitCallback cb3 = new WaitCallback(friendlistupdater.go); - ThreadPool.QueueUserWorkItem(cb3); - - - - } - else - { - // skip, agent can't be found - } - //SendRegionPresenceUpdate(AgentData, friendd); - - //removeBackReference(AgentID, AgentsNeedingNotification[i]); + enqueuePresenceUpdate(userPresence, friendPresence); } } } @@ -347,7 +216,7 @@ namespace OpenSim.Grid.MessagingServer #endregion - public void PresenceUpdateDone(PresenceInformer obj) + private void PresenceUpdateDone(PresenceInformer obj) { obj.OnGetRegionData -= GetRegionInfo; obj.OnDone -= PresenceUpdateDone; @@ -356,12 +225,13 @@ namespace OpenSim.Grid.MessagingServer #region UserServer Comms /// - /// Returns a list of FriendsListItems that describe the friends and permissions in the friend relationship for UUID friendslistowner + /// Returns a list of FriendsListItems that describe the friends and permissions in the friend + /// relationship for UUID friendslistowner. For faster lookup, we index by friend's UUID. /// - /// The agent that we're retreiving the friends Data. - public List GetUserFriendList(UUID friendlistowner) + /// The agent that we're retreiving the friends Data for. + private Dictionary GetUserFriendList(UUID friendlistowner) { - List buddylist = new List(); + Dictionary buddies = new Dictionary(); try { @@ -376,7 +246,7 @@ namespace OpenSim.Grid.MessagingServer if (respData.Contains("avcount")) { - buddylist = ConvertXMLRPCDataToFriendListItemList(respData); + buddies = ConvertXMLRPCDataToFriendListItemList(respData); } } @@ -386,7 +256,7 @@ namespace OpenSim.Grid.MessagingServer e.Message); // Return Empty list (no friends) } - return buddylist; + return buddies; } /// @@ -394,9 +264,9 @@ namespace OpenSim.Grid.MessagingServer /// /// XMLRPC response data Hashtable /// - public List ConvertXMLRPCDataToFriendListItemList(Hashtable data) + public Dictionary ConvertXMLRPCDataToFriendListItemList(Hashtable data) { - List buddylist = new List(); + Dictionary buddies = new Dictionary(); int buddycount = Convert.ToInt32((string)data["avcount"]); for (int i = 0; i < buddycount; i++) @@ -408,10 +278,10 @@ namespace OpenSim.Grid.MessagingServer buddylistitem.FriendListOwnerPerms = (uint)Convert.ToInt32((string)data["ownerPerms" + i.ToString()]); buddylistitem.FriendPerms = (uint)Convert.ToInt32((string)data["friendPerms" + i.ToString()]); - buddylist.Add(buddylistitem); + buddies.Add(buddylistitem.Friend, buddylistitem); } - return buddylist; + return buddies; } /// @@ -423,20 +293,8 @@ namespace OpenSim.Grid.MessagingServer /// public XmlRpcResponse UserLoggedOn(XmlRpcRequest request) { - m_log.Info("[LOGON]: User logged on, building indexes for user"); Hashtable requestData = (Hashtable)request.Params[0]; - //requestData["sendkey"] = serv.sendkey; - //requestData["agentid"] = agentID.ToString(); - //requestData["sessionid"] = sessionID.ToString(); - //requestData["regionid"] = RegionID.ToString(); - //requestData["regionhandle"] = regionhandle.ToString(); - //requestData["positionx"] = positionX.ToString(); - //requestData["positiony"] = positionY.ToString(); - //requestData["positionz"] = positionZ.ToString(); - //requestData["firstname"] = firstname; - //requestData["lastname"] = lastname; - AgentCircuitData agentData = new AgentCircuitData(); agentData.SessionID = new UUID((string)requestData["sessionid"]); agentData.SecureSessionID = new UUID((string)requestData["secure_session_id"]); @@ -461,12 +319,13 @@ namespace OpenSim.Grid.MessagingServer ulong regionHandle = Convert.ToUInt64((string)requestData["regionhandle"]); + m_log.InfoFormat("[LOGON]: User {0} {1} logged into region {2} as {3} agent, building indexes for user", + agentData.firstname, agentData.lastname, regionHandle, agentData.child ? "child" : "root"); + UserPresenceData up = new UserPresenceData(); up.agentData = agentData; - List flData = GetUserFriendList(agentData.AgentID); - up.friendData = flData; - RegionProfileData riData = GetRegionInfo(regionHandle); - up.regionData = riData; + up.friendData = GetUserFriendList(agentData.AgentID); + up.regionData = GetRegionInfo(regionHandle); up.OnlineYN = true; up.lookupUserRegionYN = false; ProcessFriendListSubscriptions(up); @@ -486,7 +345,6 @@ namespace OpenSim.Grid.MessagingServer Hashtable requestData = (Hashtable)request.Params[0]; UUID AgentID = new UUID((string)requestData["agentid"]); - ProcessLogOff(AgentID); return new XmlRpcResponse(); @@ -494,6 +352,49 @@ namespace OpenSim.Grid.MessagingServer #endregion + public XmlRpcResponse GetPresenceInfoBulk(XmlRpcRequest request) + { + Hashtable paramHash = (Hashtable)request.Params[0]; + Hashtable result = new Hashtable(); + + // TODO check access (recv_key/send_key) + + IList list = (IList)paramHash["uuids"]; + + // convert into List + List uuids = new List(); + for (int i = 0; i < list.Count; ++i) + { + UUID uuid; + if (UUID.TryParse((string)list[i], out uuid)) + { + uuids.Add(uuid); + } + } + + try { + Dictionary infos = m_userManager.GetFriendRegionInfos(uuids); + m_log.DebugFormat("[FRIEND]: Got {0} region entries back.", infos.Count); + int count = 0; + foreach (KeyValuePair pair in infos) + { + result["uuid_" + count] = pair.Key.ToString(); + result["isOnline_" + count] = pair.Value.isOnline; + result["regionHandle_" + count] = pair.Value.regionHandle.ToString(); // XML-RPC doesn't know ulongs + ++count; + } + result["count"] = count; + + XmlRpcResponse response = new XmlRpcResponse(); + response.Value = result; + return response; + } + catch(Exception e) { + m_log.Error("Got exception:", e); + throw e; + } + } + #region regioninfo gathering /// @@ -506,37 +407,21 @@ namespace OpenSim.Grid.MessagingServer public RegionProfileData GetRegionInfo(ulong regionhandle) { RegionProfileData regionInfo = null; - bool lookup = false; lock (m_regionInfoCache) { - if (m_regionInfoCache.Contains(regionhandle)) - { - regionInfo = (RegionProfileData)m_regionInfoCache[regionhandle]; - } - else - { - // Don't lock the cache while we're looking up the region! - lookup = true; - } + m_regionInfoCache.TryGetValue(regionhandle, out regionInfo); } - if (lookup) + if (regionInfo == null) // not found in cache { regionInfo = RequestRegionInfo(regionhandle); - if (regionInfo != null) + if (regionInfo != null) // lookup was successful { lock (m_regionInfoCache) { - if (m_regionInfoCache.Contains(regionhandle)) - { - m_regionInfoCache[regionhandle] = regionInfo; - } - else - { - m_regionInfoCache.Add(regionhandle, regionInfo); - } + m_regionInfoCache[regionhandle] = regionInfo; } } } @@ -558,21 +443,25 @@ namespace OpenSim.Grid.MessagingServer } /// - /// Get RegionProfileData from the GridServer - /// We'll Cache this information and use it for presence updates + /// Get RegionProfileData from the GridServer. + /// We'll cache this information in GetRegionInfo and use it for presence updates /// /// /// public RegionProfileData RequestRegionInfo(ulong regionHandle) - { RegionProfileData regionProfile = null; + { + RegionProfileData regionProfile = null; try { Hashtable requestData = new Hashtable(); requestData["region_handle"] = regionHandle.ToString(); requestData["authkey"] = m_cfg.GridSendKey; + ArrayList SendParams = new ArrayList(); SendParams.Add(requestData); + XmlRpcRequest GridReq = new XmlRpcRequest("simulator_data_request", SendParams); + XmlRpcResponse GridResp = GridReq.Send(m_cfg.GridServerURL, 3000); Hashtable responseData = (Hashtable)GridResp.Value; @@ -586,9 +475,6 @@ namespace OpenSim.Grid.MessagingServer uint regX = Convert.ToUInt32((string)responseData["region_locx"]); uint regY = Convert.ToUInt32((string)responseData["region_locy"]); string internalIpStr = (string)responseData["sim_ip"]; - // uint port = Convert.ToUInt32(responseData["sim_port"]); - // string externalUri = (string)responseData["sim_uri"]; - // string neighbourExternalUri = externalUri; regionProfile = new RegionProfileData(); regionProfile.httpPort = (uint)Convert.ToInt32((string)responseData["http_port"]); @@ -600,20 +486,12 @@ namespace OpenSim.Grid.MessagingServer regionProfile.remotingPort = Convert.ToUInt32((string)responseData["remoting_port"]); regionProfile.UUID = new UUID((string)responseData["region_UUID"]); regionProfile.regionName = (string)responseData["region_name"]; - lock (m_regionInfoCache) - { - if (!m_regionInfoCache.Contains(regionHandle)) - { - m_regionInfoCache.Add(regionHandle, regionProfile); - } - } } catch (WebException) { m_log.Error("[GRID]: " + "Region lookup failed for: " + regionHandle.ToString() + " - Is the GridServer down?"); - return null; } return regionProfile; @@ -641,30 +519,21 @@ namespace OpenSim.Grid.MessagingServer SendParams.Add(UserParams); // Send Request - XmlRpcRequest UserReq; - XmlRpcResponse UserResp; try { - UserReq = new XmlRpcRequest("register_messageserver", SendParams); - UserResp = UserReq.Send(m_cfg.UserServerURL, 16000); - } catch (Exception ex) + XmlRpcRequest UserReq = new XmlRpcRequest("register_messageserver", SendParams); + XmlRpcResponse UserResp = UserReq.Send(m_cfg.UserServerURL, 16000); + + // Process Response + Hashtable GridRespData = (Hashtable)UserResp.Value; + // if we got a response, we were successful + return GridRespData.ContainsKey("responsestring"); + } + catch (Exception ex) { - m_log.Error("Unable to connect to grid. Grid server not running?"); + m_log.Error("Unable to connect to grid. User server not running?"); throw(ex); } - Hashtable GridRespData = (Hashtable)UserResp.Value; - // Hashtable griddatahash = GridRespData; - - // Process Response - if (GridRespData.ContainsKey("responsestring")) - { - - return true; - } - else - { - return false; - } } public bool deregisterWithUserServer() @@ -689,30 +558,20 @@ namespace OpenSim.Grid.MessagingServer SendParams.Add(UserParams); // Send Request - XmlRpcRequest UserReq; - XmlRpcResponse UserResp; try { - UserReq = new XmlRpcRequest("deregister_messageserver", SendParams); - UserResp = UserReq.Send(m_cfg.UserServerURL, 16000); + XmlRpcRequest UserReq = new XmlRpcRequest("deregister_messageserver", SendParams); + XmlRpcResponse UserResp = UserReq.Send(m_cfg.UserServerURL, 16000); + // Process Response + Hashtable UserRespData = (Hashtable)UserResp.Value; + // if we got a response, we were successful + return UserRespData.ContainsKey("responsestring"); } catch (Exception ex) { - m_log.Error("Unable to connect to grid. Grid server not running?"); + m_log.Error("Unable to connect to grid. User server not running?"); throw (ex); } - Hashtable UserRespData = (Hashtable)UserResp.Value; - // Hashtable userdatahash = UserRespData; - - // Process Response - if (UserRespData.ContainsKey("responsestring")) - { - return true; - } - else - { - return false; - } } #endregion diff --git a/OpenSim/Grid/MessagingServer/UserPresenceData.cs b/OpenSim/Grid/MessagingServer/UserPresenceData.cs index 2119c13bf0..d548fc6871 100644 --- a/OpenSim/Grid/MessagingServer/UserPresenceData.cs +++ b/OpenSim/Grid/MessagingServer/UserPresenceData.cs @@ -38,7 +38,7 @@ namespace OpenSim.Grid.MessagingServer public AgentCircuitData agentData = new AgentCircuitData(); public RegionProfileData regionData = new RegionProfileData(); public string httpURI = String.Empty; - public List friendData = new List (); + public Dictionary friendData = new Dictionary(); public List subscriptionData = new List(); public bool OnlineYN = true; public bool lookupUserRegionYN = true; diff --git a/OpenSim/Region/Application/OpenSimBase.cs b/OpenSim/Region/Application/OpenSimBase.cs index 4f1eed87cd..2bb4b5716f 100644 --- a/OpenSim/Region/Application/OpenSimBase.cs +++ b/OpenSim/Region/Application/OpenSimBase.cs @@ -364,7 +364,8 @@ namespace OpenSim m_commsManager = new CommunicationsLocal( m_networkServersInfo, m_httpServer, m_assetCache, userService, userService, - inventoryService, backendService, backendService, libraryRootFolder, m_dumpAssetsToFile); + inventoryService, backendService, backendService, userService, + libraryRootFolder, m_dumpAssetsToFile); // set up XMLRPC handler for client's initial login request message m_httpServer.AddXmlRPCHandler("login_to_simulator", loginService.XmlRpcLoginMethod); diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 5b3bc982ee..363b09cd32 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs @@ -4129,7 +4129,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP handlerApproveFriendRequest(this, agentID, transactionID, callingCardFolders); } break; - + + case PacketType.DeclineFriendship: + DeclineFriendshipPacket dfriendpack = (DeclineFriendshipPacket)Pack; + + if (OnDenyFriendRequest != null) + { + OnDenyFriendRequest(this, + dfriendpack.AgentData.AgentID, + dfriendpack.TransactionBlock.TransactionID, + null); + } + break; + case PacketType.TerminateFriendship: TerminateFriendshipPacket tfriendpack = (TerminateFriendshipPacket)Pack; UUID listOwnerAgentID = tfriendpack.AgentData.AgentID; @@ -7648,6 +7660,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP OutPacket(p, ThrottleOutPacketType.Task); } + public void SendTerminateFriend(UUID exFriendID) + { + TerminateFriendshipPacket p = (TerminateFriendshipPacket)PacketPool.Instance.GetPacket(PacketType.TerminateFriendship); + p.AgentData.AgentID = AgentId; + p.AgentData.SessionID = SessionId; + p.ExBlock.OtherID = exFriendID; + OutPacket(p, ThrottleOutPacketType.Task); + } + public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) { int i; diff --git a/OpenSim/Region/Communications/Local/CommunicationsLocal.cs b/OpenSim/Region/Communications/Local/CommunicationsLocal.cs index 71c44e5d4e..48a635a827 100644 --- a/OpenSim/Region/Communications/Local/CommunicationsLocal.cs +++ b/OpenSim/Region/Communications/Local/CommunicationsLocal.cs @@ -42,7 +42,8 @@ namespace OpenSim.Region.Communications.Local IUserServiceAdmin userServiceAdmin, LocalInventoryService inventoryService, IInterRegionCommunications interRegionService, - IGridServices gridService, LibraryRootFolder libraryRootFolder, bool dumpAssetsToFile) + IGridServices gridService, IMessagingService messageService, + LibraryRootFolder libraryRootFolder, bool dumpAssetsToFile) : base(serversInfo, httpServer, assetCache, dumpAssetsToFile, libraryRootFolder) { AddInventoryService(inventoryService); @@ -53,6 +54,7 @@ namespace OpenSim.Region.Communications.Local m_avatarService = (IAvatarService)userService; m_gridService = gridService; m_interRegion = interRegionService; + m_messageService = messageService; } } } diff --git a/OpenSim/Region/Communications/Local/LocalBackEndServices.cs b/OpenSim/Region/Communications/Local/LocalBackEndServices.cs index 9034e49ed1..4980378a8d 100644 --- a/OpenSim/Region/Communications/Local/LocalBackEndServices.cs +++ b/OpenSim/Region/Communications/Local/LocalBackEndServices.cs @@ -39,7 +39,7 @@ namespace OpenSim.Region.Communications.Local public class LocalBackEndServices : IGridServices, IInterRegionCommunications { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - + protected Dictionary m_regions = new Dictionary(); protected Dictionary m_regionListeners = @@ -50,7 +50,7 @@ namespace OpenSim.Region.Communications.Local private Dictionary m_queuedGridSettings = new Dictionary(); public string _gdebugRegionName = String.Empty; - + public bool RegionLoginsEnabled { get { return m_regionLoginsEnabled; } @@ -523,11 +523,27 @@ namespace OpenSim.Region.Communications.Local if (info.RegionName.StartsWith(name)) { regions.Add(info); - if (regions.Count >= maxNumber) break; + if (regions.Count >= maxNumber) break; } } - + return regions; } + + public List InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List friends, bool online) + { + // if we get to here, something is wrong: We are in standalone mode, but have users that are not on our server? + m_log.WarnFormat("[INTERREGION STANDALONE] Did find {0} users on a region not on our server: {1} ???", + friends.Count, destRegionHandle); + return new List(); + } + + public bool TriggerTerminateFriend (ulong regionHandle, UUID agentID, UUID exFriendID) + { + // if we get to here, something is wrong: We are in standalone mode, but have users that are not on our server? + m_log.WarnFormat("[INTERREGION STANDALONE] Did find user {0} on a region not on our server: {1} ???", + agentID, regionHandle); + return true; + } } } diff --git a/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs b/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs index d76f076007..7f6fbc84db 100644 --- a/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs +++ b/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs @@ -56,7 +56,9 @@ namespace OpenSim.Region.Communications.OGS1 m_defaultInventoryHost = invService.Host; } - m_userService = new OGS1UserServices(this); + OGS1UserServices userServices = new OGS1UserServices(this); + m_userService = userServices; + m_messageService = userServices; m_avatarService = (IAvatarService)m_userService; } diff --git a/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs b/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs index 32628f81b8..d9e0370865 100644 --- a/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs +++ b/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs @@ -1785,5 +1785,83 @@ namespace OpenSim.Region.Communications.OGS1 return null; } } + + public List InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List friends, bool online) + { + List tpdAway = new List(); + + // destRegionHandle is a region on another server + RegionInfo info = RequestNeighbourInfo(destRegionHandle); + if (info != null) + { + string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk"; + + Hashtable reqParams = new Hashtable(); + reqParams["agentID"] = agentId.ToString(); + reqParams["agentOnline"] = online; + int count = 0; + foreach (UUID uuid in friends) + { + reqParams["friendID_" + count++] = uuid.ToString(); + } + reqParams["friendCount"] = count; + + IList parameters = new ArrayList(); + parameters.Add(reqParams); + try + { + XmlRpcRequest request = new XmlRpcRequest("presence_update_bulk", parameters); + XmlRpcResponse response = request.Send(httpServer, 5000); + Hashtable respData = (Hashtable)response.Value; + + count = (int)respData["friendCount"]; + for (int i = 0; i < count; ++i) + { + UUID uuid; + if(UUID.TryParse((string)respData["friendID_" + i], out uuid)) tpdAway.Add(uuid); + } + } + catch(Exception e) + { + m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e); + } + } + else m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}???", destRegionHandle); + + return tpdAway; + } + + public bool TriggerTerminateFriend(ulong destRegionHandle, UUID agentID, UUID exFriendID) + { + // destRegionHandle is a region on another server + RegionInfo info = RequestNeighbourInfo(destRegionHandle); + if (info == null) + { + m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}", destRegionHandle); + return false; // region not found??? + } + + string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk"; + + Hashtable reqParams = new Hashtable(); + reqParams["agentID"] = agentID.ToString(); + reqParams["friendID"] = exFriendID.ToString(); + + IList parameters = new ArrayList(); + parameters.Add(reqParams); + try + { + XmlRpcRequest request = new XmlRpcRequest("terminate_friend", parameters); + XmlRpcResponse response = request.Send(httpServer, 5000); + Hashtable respData = (Hashtable)response.Value; + + return (bool)respData["success"]; + } + catch(Exception e) + { + m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e); + return false; + } + } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs b/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs index 28177d0842..595c4a9958 100644 --- a/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs +++ b/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs @@ -39,7 +39,7 @@ using OpenSim.Framework.Communications; namespace OpenSim.Region.Communications.OGS1 { - public class OGS1UserServices : IUserService, IAvatarService + public class OGS1UserServices : IUserService, IAvatarService, IMessagingService { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); @@ -722,6 +722,64 @@ namespace OpenSim.Region.Communications.OGS1 return buddylist; } + public Dictionary GetFriendRegionInfos (List uuids) + { + Dictionary result = new Dictionary(); + + // ask MessageServer about the current on-/offline status and regions the friends are in + ArrayList parameters = new ArrayList(); + Hashtable map = new Hashtable(); + + ArrayList list = new ArrayList(); + foreach (UUID uuid in uuids) + { + list.Add(uuid.ToString()); + list.Add(uuid.ToString()); + } + map["uuids"] = list; + + map["recv_key"] = m_parent.NetworkServersInfo.UserRecvKey; + map["send_key"] = m_parent.NetworkServersInfo.UserRecvKey; + + parameters.Add(map); + + try { + XmlRpcRequest req = new XmlRpcRequest("get_presence_info_bulk", parameters); + XmlRpcResponse resp = req.Send(m_parent.NetworkServersInfo.MessagingURL, 8000); + Hashtable respData = (Hashtable) resp.Value; + + if (respData.ContainsKey("faultMessage")) + { + m_log.WarnFormat("[OGS1 USER SERVICES]: Contacting MessageServer about user-regions resulted in error: {0}", + respData["faultMessage"]); + } + else + { + int count = (int)respData["count"]; + m_log.DebugFormat("[OGS1 USER SERVICES]: Request returned {0} results.", count); + for (int i = 0; i < count; ++i) + { + UUID uuid; + if (UUID.TryParse((string)respData["uuid_" + i], out uuid)) + { + FriendRegionInfo info = new FriendRegionInfo(); + info.isOnline = (bool)respData["isOnline_" + i]; + if (info.isOnline) info.regionHandle = Convert.ToUInt64(respData["regionHandle_" + i]); + + result.Add(uuid, info); + } + } + } + } + catch (WebException e) + { + m_log.ErrorFormat("[OGS1 USER SERVICES]: Network problems when trying to fetch friend infos: {0}", e.Message); + } + + m_log.DebugFormat("[OGS1 USER SERVICES]: Returning {0} entries", result.Count); + return result; + } + #endregion /// Appearance diff --git a/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs index a3807009bc..e2cb2e0163 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs @@ -34,43 +34,100 @@ using log4net; using Nini.Config; using Nwc.XmlRpc; using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Servers; using OpenSim.Region.Environment.Interfaces; using OpenSim.Region.Environment.Scenes; namespace OpenSim.Region.Environment.Modules.Avatar.Friends { + /* + This module handles adding/removing friends, and the the presence + notification process for login/logoff of friends. + + The presence notification works as follows: + - After the user initially connects to a region (so we now have a UDP + connection to work with), this module fetches the friends of user + (those are cached), their on-/offline status, and info about the + region they are in from the MessageServer. + - (*) It then informs the user about the on-/offline status of her friends. + - It then informs all online friends currently on this region-server about + user's new online status (this will save some network traffic, as local + messages don't have to be transferred inter-region, and it will be all + that has to be done in Standalone Mode). + - For the rest of the online friends (those not on this region-server), + this module uses the provided region-information to map users to + regions, and sends one notification to every region containing the + friends to inform on that server. + - The region-server will handle that in the following way: + - If it finds the friend, it informs her about the user being online. + - If it doesn't find the friend (maybe she TPed away in the meantime), + it stores that information. + - After it processed all friends, it returns the list of friends it + couldn't find. + - If this list isn't empty, the FriendsModule re-requests information + about those online friends that have been missed and starts at (*) + again until all friends have been found, or until it tried 3 times + (to prevent endless loops due to some uncaught error). + + NOTE: Online/Offline notifications don't need to be sent on region change. + + We implement two XMLRpc handlers here, handling all the inter-region things + we have to handle: + - On-/Offline-Notifications (bulk) + - Terminate Friendship messages (single) + */ + public class FriendsModule : IRegionModule { + private class Transaction + { + public UUID agentID; + public string agentName; + public uint count; + + public Transaction(UUID agentID, string agentName) + { + this.agentID = agentID; + this.agentName = agentName; + this.count = 1; + } + } + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private Dictionary> FriendLists = new Dictionary>(); - private Dictionary m_pendingFriendRequests = new Dictionary(); - private Dictionary m_rootAgents = new Dictionary(); - private Dictionary> StoredFriendListUpdates = new Dictionary>(); + private Cache m_friendLists = new Cache(CacheFlags.AllowUpdate); + private Dictionary m_rootAgents = new Dictionary(); + + private Dictionary m_pendingFriendRequests = new Dictionary(); private Dictionary m_pendingCallingcardRequests = new Dictionary(); - private List m_scene = new List(); + private Scene m_initialScene; // saves a lookup if we don't have a specific scene + private Dictionary m_scenes = new Dictionary(); #region IRegionModule Members public void Initialise(Scene scene, IConfigSource config) { - lock (m_scene) + lock (m_scenes) { - if (m_scene.Count == 0) + if (m_scenes.Count == 0) { - scene.AddXmlRPCHandler("presence_update", processPresenceUpdate); + scene.AddXmlRPCHandler("presence_update_bulk", processPresenceUpdateBulk); + scene.AddXmlRPCHandler("terminate_friend", processTerminateFriend); + m_friendLists.DefaultTTL = new TimeSpan(1, 0, 0); // store entries for one hour max + m_initialScene = scene; } - if (!m_scene.Contains(scene)) - m_scene.Add(scene); + if (!m_scenes.ContainsKey(scene.RegionInfo.RegionHandle)) + m_scenes[scene.RegionInfo.RegionHandle] = scene; } scene.EventManager.OnNewClient += OnNewClient; scene.EventManager.OnGridInstantMessage += OnGridInstantMessage; scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; scene.EventManager.OnMakeChildAgent += MakeChildAgent; - scene.EventManager.OnClientClosed += ClientLoggedOut; + scene.EventManager.OnClientClosed += ClientClosed; } public void PostInitialise() @@ -93,82 +150,104 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends #endregion - public XmlRpcResponse processPresenceUpdate(XmlRpcRequest req) + public XmlRpcResponse processPresenceUpdateBulk(XmlRpcRequest req) { - //m_log.Info("[FRIENDS]: Got Notification about a user! OMG"); Hashtable requestData = (Hashtable)req.Params[0]; - if (requestData.ContainsKey("agent_id") && requestData.ContainsKey("notify_id") && requestData.ContainsKey("status")) + List friendsNotHere = new List(); + + // this is called with the expectation that all the friends in the request are on this region-server. + // But as some time passed since we checked (on the other region-server, via the MessagingServer), + // some of the friends might have teleported away. + // Actually, even now, between this line and the sending below, some people could TP away. So, + // we'll have to lock the m_rootAgents list for the duration to prevent/delay that. + lock(m_rootAgents) { - UUID notifyAgentId = UUID.Zero; - UUID notifyAboutAgentId = UUID.Zero; - bool notifyOnlineStatus = false; - - if ((string)requestData["status"] == "TRUE") - notifyOnlineStatus = true; - - UUID.TryParse((string)requestData["notify_id"], out notifyAgentId); - - UUID.TryParse((string)requestData["agent_id"], out notifyAboutAgentId); - m_log.InfoFormat("[PRESENCE]: Got presence update for {0}, and we're telling {1}, with a status {2}", notifyAboutAgentId.ToString(), notifyAgentId.ToString(), notifyOnlineStatus.ToString()); - ScenePresence avatar = GetRootPresenceFromAgentID(notifyAgentId); - if (avatar != null) + List friendsHere = new List(); + try { - if (avatar.IsChildAgent) + UUID agentID = new UUID((string)requestData["agentID"]); + bool agentOnline = (bool)requestData["agentOnline"]; + int count = (int)requestData["friendCount"]; + for (int i = 0; i < count; ++i) { - StoredFriendListUpdate sob = new StoredFriendListUpdate(); - sob.OnlineYN = notifyOnlineStatus; - sob.storedAbout = notifyAboutAgentId; - sob.storedFor = notifyAgentId; - lock (StoredFriendListUpdates) + UUID uuid; + if (UUID.TryParse((string)requestData["friendID_" + i], out uuid)) { - if (StoredFriendListUpdates.ContainsKey(notifyAgentId)) - { - StoredFriendListUpdates[notifyAgentId].Add(sob); - } - else - { - List newitem = new List(); - newitem.Add(sob); - StoredFriendListUpdates.Add(notifyAgentId, newitem); - } + if (m_rootAgents.ContainsKey(uuid)) friendsHere.Add(GetRootPresenceFromAgentID(uuid)); + else friendsNotHere.Add(uuid); + } + } + + // now send, as long as they are still here... + UUID[] agentUUID = new UUID[] { agentID }; + if (agentOnline) + { + foreach (ScenePresence agent in friendsHere) + { + agent.ControllingClient.SendAgentOnline(agentUUID); } } else { - if (notifyOnlineStatus) - doFriendListUpdateOnline(notifyAboutAgentId); - else - ClientLoggedOut(notifyAboutAgentId); + foreach (ScenePresence agent in friendsHere) + { + agent.ControllingClient.SendAgentOffline(agentUUID); + } } } - else + catch(Exception e) { - StoredFriendListUpdate sob = new StoredFriendListUpdate(); - sob.OnlineYN = notifyOnlineStatus; - sob.storedAbout = notifyAboutAgentId; - sob.storedFor = notifyAgentId; - lock (StoredFriendListUpdates) + m_log.Warn("[FRIENDS]: Got exception while parsing presence_update_bulk request:", e); + } + } + + // no need to lock anymore; if TPs happen now, worst case is that we have an additional agent in this region, + // which should be caught on the next iteration... + Hashtable result = new Hashtable(); + int idx = 0; + foreach (UUID uuid in friendsNotHere) + { + result["friendID_" + idx++] = uuid.ToString(); + } + result["friendCount"] = idx; + + XmlRpcResponse response = new XmlRpcResponse(); + response.Value = result; + + return response; + } + + public XmlRpcResponse processTerminateFriend(XmlRpcRequest req) + { + Hashtable requestData = (Hashtable)req.Params[0]; + + bool success = false; + + UUID agentID; + UUID friendID; + if (requestData.ContainsKey("agentID") && UUID.TryParse((string)requestData["agentID"], out agentID) && + requestData.ContainsKey("friendID") && UUID.TryParse((string)requestData["friendID"], out friendID)) + { + // try to find it and if it is there, prevent it to vanish before we sent the message + lock(m_rootAgents) + { + if (m_rootAgents.ContainsKey(agentID)) { - if (StoredFriendListUpdates.ContainsKey(notifyAgentId)) - { - StoredFriendListUpdates[notifyAgentId].Add(sob); - } - else - { - List newitem = new List(); - newitem.Add(sob); - StoredFriendListUpdates.Add(notifyAgentId, newitem); - } + m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", friendID, agentID); + GetRootPresenceFromAgentID(agentID).ControllingClient.SendTerminateFriend(friendID); + success = true; } } + } - } - else - { - m_log.Warn("[PRESENCE]: Malformed XMLRPC Presence request"); - } - return new XmlRpcResponse(); + // return whether we were successful + Hashtable result = new Hashtable(); + result["success"] = success; + + XmlRpcResponse response = new XmlRpcResponse(); + response.Value = result; + return response; } private void OnNewClient(IClientAPI client) @@ -176,245 +255,52 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends // All friends establishment protocol goes over instant message // There's no way to send a message from the sim // to a user to 'add a friend' without causing dialog box spam - // - // The base set of friends are added when the user signs on in their XMLRPC response - // Generated by LoginService. The friends are retreived from the database by the UserManager // Subscribe to instant messages - client.OnInstantMessage += OnInstantMessage; - client.OnApproveFriendRequest += OnApprovedFriendRequest; + + // Friend list management + client.OnApproveFriendRequest += OnApproveFriendRequest; client.OnDenyFriendRequest += OnDenyFriendRequest; client.OnTerminateFriendship += OnTerminateFriendship; + + // ... calling card handling... client.OnOfferCallingCard += OnOfferCallingCard; client.OnAcceptCallingCard += OnAcceptCallingCard; client.OnDeclineCallingCard += OnDeclineCallingCard; - doFriendListUpdateOnline(client.AgentId); + // we need this one exactly once per agent session (see comments in the handler below) + client.OnEconomyDataRequest += OnEconomyDataRequest; + // if it leaves, we want to know, too + client.OnLogout += OnLogout; } - private void doFriendListUpdateOnline(UUID AgentId) - { - List fl = new List(); - - //bool addFLback = false; - - lock (FriendLists) - { - if (FriendLists.ContainsKey(AgentId)) - { - fl = FriendLists[AgentId]; - } - else - { - fl = m_scene[0].GetFriendList(AgentId); - - //lock (FriendLists) - //{ - if (!FriendLists.ContainsKey(AgentId)) - FriendLists.Add(AgentId, fl); - //} - } - } - - List UpdateUsers = new List(); - - foreach (FriendListItem f in fl) - { - if (m_rootAgents.ContainsKey(f.Friend)) - { - if (f.onlinestatus == false) - { - UpdateUsers.Add(f.Friend); - f.onlinestatus = true; - } - } - } - foreach (UUID user in UpdateUsers) - { - ScenePresence av = GetRootPresenceFromAgentID(user); - if (av != null) - { - List usrfl = new List(); - - lock (FriendLists) - { - usrfl = FriendLists[user]; - } - - lock (usrfl) - { - foreach (FriendListItem fli in usrfl) - { - if (fli.Friend == AgentId) - { - fli.onlinestatus = true; - UUID[] Agents = new UUID[1]; - Agents[0] = AgentId; - av.ControllingClient.SendAgentOnline(Agents); - - } - } - } - } - } - - if (UpdateUsers.Count > 0) - { - ScenePresence avatar = GetRootPresenceFromAgentID(AgentId); - if (avatar != null) - { - avatar.ControllingClient.SendAgentOnline(UpdateUsers.ToArray()); - } - - } - } - - private void ClientLoggedOut(UUID AgentId) + private void ClientClosed(UUID AgentId) { + // agent's client was closed. As we handle logout in OnLogout, this here has only to handle + // TPing away (root agent is closed) or TPing/crossing in a region far enough away (client + // agent is closed). + // NOTE: In general, this doesn't mean that the agent logged out, just that it isn't around + // in one of the regions here anymore. lock (m_rootAgents) { if (m_rootAgents.ContainsKey(AgentId)) { m_rootAgents.Remove(AgentId); - m_log.Info("[FRIEND]: Removing " + AgentId + ". Agent logged out."); + m_log.Info("[FRIEND]: Removing " + AgentId + ". Agent was closed."); } } - List lfli = new List(); - lock (FriendLists) - { - if (FriendLists.ContainsKey(AgentId)) - { - lfli = FriendLists[AgentId]; - } - } - List updateUsers = new List(); - foreach (FriendListItem fli in lfli) - { - if (fli.onlinestatus == true) - { - updateUsers.Add(fli.Friend); - } - } - lock (updateUsers) - { - for (int i = 0; i < updateUsers.Count; i++) - { - List flfli = new List(); - try - { - lock (FriendLists) - { - if (FriendLists.ContainsKey(updateUsers[i])) - flfli = FriendLists[updateUsers[i]]; - } - } - catch (IndexOutOfRangeException) - { - // Ignore the index out of range exception. - // This causes friend lists to get out of sync slightly.. however - // prevents a sim crash. - m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); - } - catch (ArgumentOutOfRangeException) - { - // Ignore the index out of range exception. - // This causes friend lists to get out of sync slightly.. however - // prevents a sim crash. - m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); - } - - for (int j = 0; j < flfli.Count; j++) - { - try - { - if (flfli[i].Friend == AgentId) - { - flfli[i].onlinestatus = false; - } - } - - catch (IndexOutOfRangeException) - { - // Ignore the index out of range exception. - // This causes friend lists to get out of sync slightly.. however - // prevents a sim crash. - m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); - } - catch (ArgumentOutOfRangeException) - { - // Ignore the index out of range exception. - // This causes friend lists to get out of sync slightly.. however - // prevents a sim crash. - m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); - } - } - } - - for (int i = 0; i < updateUsers.Count; i++) - { - ScenePresence av = GetRootPresenceFromAgentID(updateUsers[i]); - if (av != null) - { - UUID[] agents = new UUID[1]; - agents[0] = AgentId; - av.ControllingClient.SendAgentOffline(agents); - } - } - } - lock (FriendLists) - { - FriendLists.Remove(AgentId); - } } private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) { lock (m_rootAgents) { - if (m_rootAgents.ContainsKey(avatar.UUID)) - { - if (avatar.RegionHandle != m_rootAgents[avatar.UUID]) - { - m_rootAgents[avatar.UUID] = avatar.RegionHandle; - m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); - if (avatar.JID.Length > 0) - { - // JId avatarID = new JId(avatar.JID); - // REST Post XMPP Stanzas! - } - // Claim User! my user! Mine mine mine! - } - } - else - { - m_rootAgents.Add(avatar.UUID, avatar.RegionHandle); - m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); - - List updateme = new List(); - lock (StoredFriendListUpdates) - { - if (StoredFriendListUpdates.ContainsKey(avatar.UUID)) - { - updateme = StoredFriendListUpdates[avatar.UUID]; - StoredFriendListUpdates.Remove(avatar.UUID); - } - } - - if (updateme.Count > 0) - { - foreach (StoredFriendListUpdate u in updateme) - { - if (u.OnlineYN) - doFriendListUpdateOnline(u.storedAbout); - else - ClientLoggedOut(u.storedAbout); - } - } - } + m_rootAgents[avatar.UUID] = avatar.RegionHandle; + m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); + // Claim User! my user! Mine mine mine! } - //m_log.Info("[FRIEND]: " + avatar.Name + " status:" + (!avatar.IsChildAgent).ToString()); } private void MakeChildAgent(ScenePresence avatar) @@ -423,6 +309,8 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends { if (m_rootAgents.ContainsKey(avatar.UUID)) { + // only delete if the region matches. As this is a shared module, the avatar could be + // root agent in another region on this server. if (m_rootAgents[avatar.UUID] == avatar.RegionHandle) { m_rootAgents.Remove(avatar.UUID); @@ -435,12 +323,12 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends private ScenePresence GetRootPresenceFromAgentID(UUID AgentID) { ScenePresence returnAgent = null; - lock (m_scene) + lock (m_scenes) { ScenePresence queryagent = null; - for (int i = 0; i < m_scene.Count; i++) + foreach (Scene scene in m_scenes.Values) { - queryagent = m_scene[i].GetScenePresence(AgentID); + queryagent = scene.GetScenePresence(AgentID); if (queryagent != null) { if (!queryagent.IsChildAgent) @@ -457,12 +345,12 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends private ScenePresence GetAnyPresenceFromAgentID(UUID AgentID) { ScenePresence returnAgent = null; - lock (m_scene) + lock (m_scenes) { ScenePresence queryagent = null; - for (int i = 0; i < m_scene.Count; i++) + foreach (Scene scene in m_scenes.Values) { - queryagent = m_scene[i].GetScenePresence(AgentID); + queryagent = scene.GetScenePresence(AgentID); if (queryagent != null) { returnAgent = queryagent; @@ -485,139 +373,158 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends // Friend Requests go by Instant Message.. using the dialog param // https://wiki.secondlife.com/wiki/ImprovedInstantMessage - // 38 == Offer friendship - if (dialog == (byte) 38) + if (dialog == (byte)InstantMessageDialog.FriendshipOffered) // 38 { - UUID friendTransactionID = UUID.Random(); + // this is triggered by the initiating agent and has two parts: + // A local agent offers friendship to some possibly remote friend. + // A IM is triggered, processed here (1), sent to the destination region, + // and processed there in this part of the code again (2). + // (1) has fromAgentSession != UUID.Zero, + // (2) has fromAgentSession == UUID.Zero (don't leak agent sessions to other agents) + // For (1), build the IM to send to the other region (and trigger sending it) + // FOr (2), just store the transaction; we will wait for Approval or Decline - m_pendingFriendRequests.Add(friendTransactionID, fromAgentID); + // some properties are misused here: + // fromAgentName is the *destination* name (the friend we offer friendship to) - m_log.Info("[FRIEND]: 38 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + - message); - GridInstantMessage msg = new GridInstantMessage(); - msg.fromAgentID = fromAgentID.Guid; - msg.fromAgentSession = fromAgentSession.Guid; - msg.toAgentID = toAgentID.Guid; - msg.imSessionID = friendTransactionID.Guid; // This is the item we're mucking with here - m_log.Info("[FRIEND]: Filling Session: " + msg.imSessionID.ToString()); - msg.timestamp = timestamp; - if (client != null) + if (fromAgentSession != UUID.Zero) { - msg.fromAgentName = client.Name; // fromAgentName; + // (1) + // send the friendship-offer to the target + m_log.InfoFormat("[FRIEND]: Offer(38) - From: {0}, FromName: {1} To: {2}, Session: {3}, Message: {4}, Offline {5}", + fromAgentID, fromAgentName, toAgentID, imSessionID, message, offline); + + UUID transactionID = UUID.Random(); + + GridInstantMessage msg = new GridInstantMessage(); + msg.fromAgentID = fromAgentID.Guid; + msg.fromAgentSession = UUID.Zero.Guid; // server IMs don't have a session + msg.toAgentID = toAgentID.Guid; + msg.imSessionID = transactionID.Guid; // Start new transaction + m_log.DebugFormat("[FRIEND]: new transactionID: {0}", msg.imSessionID); + msg.timestamp = timestamp; + if (client != null) + { + msg.fromAgentName = client.Name; // fromAgentName; + } + else + { + msg.fromAgentName = "(hippos)"; // Added for posterity. This means that we can't figure out who sent it + } + msg.message = message; + msg.dialog = dialog; + msg.fromGroup = fromGroup; + msg.offline = offline; + msg.ParentEstateID = ParentEstateID; + msg.Position = Position; + msg.RegionID = RegionID.Guid; + msg.binaryBucket = binaryBucket; + + m_log.DebugFormat("[FRIEND]: storing transactionID {0} on sender side", transactionID); + lock (m_pendingFriendRequests) + { + m_pendingFriendRequests.Add(transactionID, new Transaction(fromAgentID, fromAgentName)); + outPending(); + } + + // we don't want to get that new IM into here if we aren't local, as only on the destination + // should receive it. If we *are* local, *we* are the destination, so we have to receive it. + // As grid-IMs are routed to all modules (in contrast to local IMs), we have to decide here. + InstantMessageReceiver recv = InstantMessageReceiver.IMModule; + if(GetAnyPresenceFromAgentID(toAgentID) != null) recv |= InstantMessageReceiver.FriendsModule; + + // We don't really care which local scene we pipe it through. + m_initialScene.TriggerGridInstantMessage(msg, recv); } else { - msg.fromAgentName = "(hippos)"; // Added for posterity. This means that we can't figure out who sent it + // (2) + // we are on the receiving end here; just add the transactionID to the stored transactions for later lookup + m_log.DebugFormat("[FRIEND]: storing transactionID {0} on receiver side", imSessionID); + lock (m_pendingFriendRequests) + { + // if both are on the same region-server, the transaction is stored already, but we have to update the name + if (m_pendingFriendRequests.ContainsKey(imSessionID)) + { + m_pendingFriendRequests[imSessionID].agentName = fromAgentName; + m_pendingFriendRequests[imSessionID].count++; + } + else m_pendingFriendRequests.Add(imSessionID, new Transaction(fromAgentID, fromAgentName)); + outPending(); + } } - msg.message = message; - msg.dialog = dialog; - msg.fromGroup = fromGroup; - msg.offline = offline; - msg.ParentEstateID = ParentEstateID; - msg.Position = Position; - msg.RegionID = RegionID.Guid; - msg.binaryBucket = binaryBucket; - // We don't really care which scene we pipe it through. - m_scene[0].TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule); } - - // 39 == Accept Friendship - if (dialog == (byte) 39) + else if (dialog == (byte)InstantMessageDialog.FriendshipAccepted) // 39 { - m_log.Info("[FRIEND]: 39 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + - message); - } - - // 40 == Decline Friendship - if (dialog == (byte) 40) - { - m_log.Info("[FRIEND]: 40 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + - message); - } - } - - private void OnApprovedFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List callingCardFolders) - { - if (m_pendingFriendRequests.ContainsKey(transactionID)) - { - // Found Pending Friend Request with that Transaction.. - Scene SceneAgentIn = m_scene[0]; - - // Found Pending Friend Request with that Transaction.. - ScenePresence agentpresence = GetRootPresenceFromAgentID(agentID); - if (agentpresence != null) + // accepting the friendship offer causes a type 39 IM being sent to the (possibly remote) initiator + // toAgentID is initiator, fromAgentID is new friend (which just approved) + m_log.DebugFormat("[FRIEND]: 39 - from client {0}, agentSession {1}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})", + client != null ? client.AgentId.ToString() : "", fromAgentSession, + fromAgentID, fromAgentName, imSessionID, toAgentID, message, dialog); + lock (m_pendingFriendRequests) { - SceneAgentIn = agentpresence.Scene; + if (!m_pendingFriendRequests.ContainsKey(imSessionID)) + { + m_log.DebugFormat("[FRIEND]: Got friendship approval from {0} to {1} without matching transaction {2}", + fromAgentID, toAgentID, imSessionID); + return; // unknown transaction + } + // else found pending friend request with that transaction => remove it if we handled all + if (--m_pendingFriendRequests[imSessionID].count <= 0) m_pendingFriendRequests.Remove(imSessionID); + outPending(); } - // Compose response to other agent. - GridInstantMessage msg = new GridInstantMessage(); - msg.toAgentID = m_pendingFriendRequests[transactionID].Guid; - msg.fromAgentID = agentID.Guid; - msg.fromAgentName = client.Name; - msg.fromAgentSession = client.SessionId.Guid; - msg.fromGroup = false; - msg.imSessionID = transactionID.Guid; - msg.message = agentID.Guid.ToString(); - msg.ParentEstateID = 0; - msg.timestamp = (uint) Util.UnixTimeSinceEpoch(); - msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid; - msg.dialog = (byte) 39; // Approved friend request - msg.Position = Vector3.Zero; - msg.offline = (byte) 0; - msg.binaryBucket = new byte[0]; - // We don't really care which scene we pipe it through, it goes to the shared IM Module and/or the database - - SceneAgentIn.TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule); - SceneAgentIn.StoreAddFriendship(m_pendingFriendRequests[transactionID], agentID, (uint) 1); - - - //UUID[] Agents = new UUID[1]; - //Agents[0] = msg.toAgentID; - //av.ControllingClient.SendAgentOnline(Agents); - - m_pendingFriendRequests.Remove(transactionID); - // TODO: Inform agent that the friend is online - } - } - - private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List callingCardFolders) - { - if (m_pendingFriendRequests.ContainsKey(transactionID)) - { - Scene SceneAgentIn = m_scene[0]; - - // Found Pending Friend Request with that Transaction.. - ScenePresence agentpresence = GetRootPresenceFromAgentID(agentID); - if (agentpresence != null) + // a new friend was added in the initiator's and friend's data, so the cache entries are wrong now. + lock (m_friendLists) { - SceneAgentIn = agentpresence.Scene; + m_friendLists.Invalidate(toAgentID); + m_friendLists.Invalidate(fromAgentID); } - // Compose response to other agent. - GridInstantMessage msg = new GridInstantMessage(); - msg.toAgentID = m_pendingFriendRequests[transactionID].Guid; - msg.fromAgentID = agentID.Guid; - msg.fromAgentName = client.Name; - msg.fromAgentSession = client.SessionId.Guid; - msg.fromGroup = false; - msg.imSessionID = transactionID.Guid; - msg.message = agentID.Guid.ToString(); - msg.ParentEstateID = 0; - msg.timestamp = (uint) Util.UnixTimeSinceEpoch(); - msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid; - msg.dialog = (byte) 40; // Deny friend request - msg.Position = Vector3.Zero; - msg.offline = (byte) 0; - msg.binaryBucket = new byte[0]; - SceneAgentIn.TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule); - m_pendingFriendRequests.Remove(transactionID); - } - } - private void OnTerminateFriendship(IClientAPI client, UUID agent, UUID exfriendID) - { - m_scene[0].StoreRemoveFriendship(agent, exfriendID); - // TODO: Inform the client that the ExFriend is offline + // now send presence update and add a calling card for the new friend + + ScenePresence initiator = GetAnyPresenceFromAgentID(toAgentID); + if (initiator == null) + { + // quite wrong. Shouldn't happen. + m_log.WarnFormat("[FRIEND]: Coudn't find initiator of friend request {0}", toAgentID); + return; + } + + // tell initiator that friend is online + initiator.ControllingClient.SendAgentOnline(new UUID[] { fromAgentID }); + + // find the folder for the friend... + InventoryFolderImpl folder = + initiator.Scene.CommsManager.UserProfileCacheService.GetUserDetails(toAgentID).FindFolderForType((int)InventoryType.CallingCard); + if (folder != null) + { + // ... and add the calling card + CreateCallingCard(initiator.ControllingClient, fromAgentID, folder.ID, fromAgentName); + } + } + else if (dialog == (byte)InstantMessageDialog.FriendshipDeclined) // 40 + { + // declining the friendship offer causes a type 40 IM being sent to the (possibly remote) initiator + // toAgentID is initiator, fromAgentID declined friendship + m_log.DebugFormat("[FRIEND]: 40 - from client {0}, agentSession {1}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})", + client != null ? client.AgentId.ToString() : "", fromAgentSession, + fromAgentID, fromAgentName, imSessionID, toAgentID, message, dialog); + + // not much to do, just clean up the transaction... + lock (m_pendingFriendRequests) + { + if (!m_pendingFriendRequests.ContainsKey(imSessionID)) + { + m_log.DebugFormat("[FRIEND]: Got friendship denial from {0} to {1} without matching transaction {2}", + fromAgentID, toAgentID, imSessionID); + return; // unknown transaction + } + // else found pending friend request with that transaction => remove it if we handled all + if (--m_pendingFriendRequests[imSessionID].count <= 0) m_pendingFriendRequests.Remove(imSessionID); + outPending(); + } + } } private void OnGridInstantMessage(GridInstantMessage msg, InstantMessageReceiver whichModule) @@ -633,6 +540,182 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends msg.binaryBucket); } + private void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List callingCardFolders) + { + m_log.DebugFormat("[FRIEND]: Got approve friendship from {0} {1}, agentID {2}, tid {3}", + client.Name, client.AgentId, agentID, transactionID); + Transaction transaction; + lock (m_pendingFriendRequests) + { + if (!m_pendingFriendRequests.TryGetValue(transactionID, out transaction)) + { + m_log.DebugFormat("[FRIEND]: Got friendship approval {0} from {1} ({2}) without matching transaction {3}", + agentID, client.AgentId, client.Name, transactionID); + return; // unknown transaction + } + // else found pending friend request with that transaction => remove if done with all + if(--m_pendingFriendRequests[transactionID].count <= 0) m_pendingFriendRequests.Remove(transactionID); + outPending(); + } + + UUID friendID = transaction.agentID; + m_log.DebugFormat("[FRIEND]: {0} ({1}) approved friendship request from {2}", + client.Name, client.AgentId, friendID); + + Scene SceneAgentIn = m_initialScene; + // we need any presence to send the packets to, not necessarily the root agent... + ScenePresence agentpresence = GetAnyPresenceFromAgentID(agentID); + if (agentpresence != null) + { + SceneAgentIn = agentpresence.Scene; + } + + // store the new friend persistently for both avatars + SceneAgentIn.StoreAddFriendship(friendID, agentID, (uint) FriendRights.CanSeeOnline); + + // The cache entries aren't valid anymore either, as we just added a friend to both sides. + lock (m_friendLists) + { + m_friendLists.Invalidate(agentID); + m_friendLists.Invalidate(friendID); + } + + // create calling card + CreateCallingCard(client, friendID, callingCardFolders[0], transaction.agentName); + + // Compose response to other agent. + GridInstantMessage msg = new GridInstantMessage(); + msg.toAgentID = friendID.Guid; + msg.fromAgentID = agentID.Guid; + msg.fromAgentName = client.Name; + msg.fromAgentSession = UUID.Zero.Guid; // server IMs don't have a session + msg.fromGroup = false; + msg.imSessionID = transactionID.Guid; + msg.message = agentID.Guid.ToString(); + msg.ParentEstateID = 0; + msg.timestamp = (uint) Util.UnixTimeSinceEpoch(); + msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid; + msg.dialog = (byte) InstantMessageDialog.FriendshipAccepted; + msg.Position = Vector3.Zero; + msg.offline = (byte) 0; + msg.binaryBucket = new byte[0]; + + // we don't want to get that new IM into here if we aren't local, as only on the destination + // should receive it. If we *are* local, *we* are the destination, so we have to receive it. + // As grid-IMs are routed to all modules (in contrast to local IMs), we have to decide here. + InstantMessageReceiver recv = InstantMessageReceiver.IMModule; + if(GetAnyPresenceFromAgentID(friendID) != null) recv |= InstantMessageReceiver.FriendsModule; + + // now we have to inform the agent about the friend. For the opposite direction, this happens in the handler + // of the type 39 IM + SceneAgentIn.TriggerGridInstantMessage(msg, recv); + + // tell client that new friend is online + client.SendAgentOnline(new UUID[] { friendID }); + } + + private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List callingCardFolders) + { + m_log.DebugFormat("[FRIEND]: Got deny friendship from {0} {1}, agentID {2}, tid {3}", + client.Name, client.AgentId, agentID, transactionID); + Transaction transaction; + lock (m_pendingFriendRequests) + { + if(!m_pendingFriendRequests.TryGetValue(transactionID, out transaction)) + { + m_log.DebugFormat("[FRIEND]: Got friendship denial {0} from {1} ({2}) without matching transaction {3}", + agentID, client.AgentId, client.Name, transactionID); + return; + } + // else found pending friend request with that transaction. + if(--m_pendingFriendRequests[transactionID].count <= 0) m_pendingFriendRequests.Remove(transactionID); + outPending(); + } + UUID friendID = transaction.agentID; + + Scene SceneAgentIn = m_initialScene; + ScenePresence agentpresence = GetRootPresenceFromAgentID(agentID); + if (agentpresence != null) + { + SceneAgentIn = agentpresence.Scene; + } + + // Compose response to other agent. + GridInstantMessage msg = new GridInstantMessage(); + msg.toAgentID = friendID.Guid; + msg.fromAgentID = agentID.Guid; + msg.fromAgentName = client.Name; + msg.fromAgentSession = UUID.Zero.Guid; // server IMs don't have a session + msg.fromGroup = false; + msg.imSessionID = transactionID.Guid; + msg.message = agentID.Guid.ToString(); + msg.ParentEstateID = 0; + msg.timestamp = (uint) Util.UnixTimeSinceEpoch(); + msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid; + msg.dialog = (byte) InstantMessageDialog.FriendshipDeclined; + msg.Position = Vector3.Zero; + msg.offline = (byte) 0; + msg.binaryBucket = new byte[0]; + + // we don't want to get that new IM into here if we aren't local, as only on the destination + // should receive it. If we *are* local, *we* are the destination, so we have to receive it. + // As grid-IMs are routed to all modules (in contrast to local IMs), we have to decide here. + InstantMessageReceiver recv = InstantMessageReceiver.IMModule; + if(GetAnyPresenceFromAgentID(friendID) != null) recv |= InstantMessageReceiver.FriendsModule; + + // now we have to inform the agent about the friend. For the opposite direction, this happens in the handler + // of the type 39 IM + SceneAgentIn.TriggerGridInstantMessage(msg, recv); + } + + private void OnTerminateFriendship(IClientAPI client, UUID agentID, UUID exfriendID) + { + // client.AgentId == agentID! + + // this removes the friends from the stored friendlists. After the next login, they will be gone... + m_initialScene.StoreRemoveFriendship(agentID, exfriendID); + + // ... now tell the two involved clients that they aren't friends anymore. + + // I don't know why we have to tell , as this was caused by her, but that's how it works in SL... + client.SendTerminateFriend(exfriendID); + + // now send the friend, if online + ScenePresence presence = GetAnyPresenceFromAgentID(exfriendID); + if (presence != null) + { + m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", agentID, exfriendID); + presence.ControllingClient.SendTerminateFriend(agentID); + } + else + { + // retry 3 times, in case the agent TPed from the last known region... + for(int retry = 0; retry < 3; ++retry) + { + // wasn't sent, so ex-friend wasn't around on this region-server. Fetch info and try to send + UserAgentData data = m_initialScene.CommsManager.UserService.GetAgentByUUID(exfriendID); + if (!data.AgentOnline) + { + m_log.DebugFormat("[FRIEND]: {0} is offline, so not sending TerminateFriend", exfriendID); + break; // if ex-friend isn't online, we don't need to send + } + + m_log.DebugFormat("[FRIEND]: Sending remote terminate friend {0} to agent {1}@{2}", + agentID, exfriendID, data.Handle); + + // try to send to foreign region, retry if it fails (friend TPed away, for example) + if (m_initialScene.TriggerTerminateFriend(data.Handle, exfriendID, agentID)) break; + } + } + + // clean up cache: FriendList is wrong now... + lock (m_friendLists) + { + m_friendLists.Invalidate(agentID); + m_friendLists.Invalidate(exfriendID); + } + } + #endregion #region CallingCards @@ -650,7 +733,10 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends return; } - m_pendingCallingcardRequests[transactionID] = client.AgentId; + lock (m_pendingCallingcardRequests) + { + m_pendingCallingcardRequests[transactionID] = client.AgentId; + } // inform the destination agent about the offer destAgent.ControllingClient.SendOfferCallingCard(client.AgentId, transactionID); } @@ -687,19 +773,25 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends client.FirstName, client.LastName, transactionID, folderID); UUID destID; - if (m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) + lock (m_pendingCallingcardRequests) { + if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) + { + m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.", + client.Name); + return; + } + // else found pending calling card request with that transaction. m_pendingCallingcardRequests.Remove(transactionID); - - ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); - // inform sender of the card that destination declined the offer - if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID); - - // put a calling card into the inventory of receiver - CreateCallingCard(client, destID, folderID, destAgent.Name); } - else m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} {1} without an offer before.", - client.FirstName, client.LastName); + + + ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); + // inform sender of the card that destination declined the offer + if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID); + + // put a calling card into the inventory of receiver + CreateCallingCard(client, destID, folderID, destAgent.Name); } private void OnDeclineCallingCard(IClientAPI client, UUID transactionID) @@ -707,25 +799,230 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends m_log.DebugFormat("[CALLING CARD]: User {0} declined card, tid {2}", client.AgentId, transactionID); UUID destID; - if (m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) + lock (m_pendingCallingcardRequests) { + if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) + { + m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.", + client.Name); + return; + } + // else found pending calling card request with that transaction. m_pendingCallingcardRequests.Remove(transactionID); - - ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); - // inform sender of the card that destination declined the offer - if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID); } - else m_log.WarnFormat("[CALLING CARD]: Got a DeclineCallingCard from {0} {1} without an offer before.", - client.FirstName, client.LastName); + + ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); + // inform sender of the card that destination declined the offer + if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID); + } + + private void SendPresenceState(IClientAPI client, List friendList, bool iAmOnline) + { + m_log.DebugFormat("[FRIEND]: {0} logged {1}; sending presence updates", client.Name, iAmOnline ? "in" : "out"); + + if (friendList == null || friendList.Count == 0) + { + m_log.DebugFormat("[FRIEND]: {0} doesn't have friends.", client.Name); + return; // nothing we can do if she doesn't have friends... + } + + // collect sets of friendIDs; to send to (online and offline), and to receive from + // TODO: If we ever switch to .NET >= 3, replace those Lists with HashSets. + // I can't believe that we have Dictionaries, but no Sets, considering Java introduced them years ago... + List friendIDsToSendTo = new List(); + List friendIDsToReceiveFromOffline = new List(); + List friendIDsToReceiveFromOnline = new List(); + foreach (FriendListItem item in friendList) + { + if (((item.FriendListOwnerPerms | item.FriendPerms) & (uint)FriendRights.CanSeeOnline) != 0) + { + // friend is allowed to see my presence => add + if ((item.FriendListOwnerPerms & (uint)FriendRights.CanSeeOnline) != 0) friendIDsToSendTo.Add(item.Friend); + + // I'm allowed to see friend's presence => add as offline, we might reconsider in a momnet... + if ((item.FriendPerms & (uint)FriendRights.CanSeeOnline) != 0) friendIDsToReceiveFromOffline.Add(item.Friend); + } + } + + + + // we now have a list of "interesting" friends (which we have to find out on-/offline state for), + // friends we want to send our online state to (if *they* are online, too), and + // friends we want to receive online state for (currently unknown whether online or not) + + // as this processing might take some time and friends might TP away, we try up to three times to + // reach them. Most of the time, we *will* reach them, and this loop won't loop + int retry = 0; + do + { + // build a list of friends to look up region-information and on-/offline-state for + List friendIDsToLookup = new List(friendIDsToSendTo); + foreach (UUID uuid in friendIDsToReceiveFromOffline) + { + if(!friendIDsToLookup.Contains(uuid)) friendIDsToLookup.Add(uuid); + } + + m_log.DebugFormat("[FRIEND]: {0} to lookup, {1} to send to, {2} to receive from for agent {3}", + friendIDsToLookup.Count, friendIDsToSendTo.Count, friendIDsToReceiveFromOffline.Count, client.Name); + + // we have to fetch FriendRegionInfos, as the (cached) FriendListItems don't + // necessarily contain the correct online state... + Dictionary friendRegions = m_initialScene.GetFriendRegionInfos(friendIDsToLookup); + m_log.DebugFormat("[FRIEND]: Found {0} regionInfos for {1} friends of {2}", + friendRegions.Count, friendIDsToLookup.Count, client.Name); + + // argument for SendAgentOn/Offline; we shouldn't generate that repeatedly within loops. + UUID[] agentArr = new UUID[] { client.AgentId }; + + // first, send to friend presence state to me, if I'm online... + if (iAmOnline) + { + for (int i = friendIDsToReceiveFromOffline.Count - 1; i >= 0; --i) + { + UUID uuid = friendIDsToReceiveFromOffline[i]; + FriendRegionInfo info; + if (friendRegions.TryGetValue(uuid, out info) && info.isOnline) + { + friendIDsToReceiveFromOffline.RemoveAt(i); + friendIDsToReceiveFromOnline.Add(uuid); + } + } + m_log.DebugFormat("[FRIEND]: Sending {0} offline and {1} online friends to {2}", + friendIDsToReceiveFromOffline.Count, friendIDsToReceiveFromOnline.Count, client.Name); + if (friendIDsToReceiveFromOffline.Count > 0) client.SendAgentOffline(friendIDsToReceiveFromOffline.ToArray()); + if (friendIDsToReceiveFromOnline.Count > 0) client.SendAgentOnline(friendIDsToReceiveFromOnline.ToArray()); + + // clear them for a possible second iteration; we don't have to repeat this + friendIDsToReceiveFromOffline.Clear(); + friendIDsToReceiveFromOnline.Clear(); + } + + // now, send my presence state to my friends + for (int i = friendIDsToSendTo.Count - 1; i >= 0; --i) + { + UUID uuid = friendIDsToSendTo[i]; + FriendRegionInfo info; + if (friendRegions.TryGetValue(uuid, out info) && info.isOnline) + { + // any client is good enough, root or child... + ScenePresence agent = GetAnyPresenceFromAgentID(uuid); + if(agent != null) + { + m_log.DebugFormat("[FRIEND]: Found local agent {0}", agent.Name); + + // friend is online and on this server... + if(iAmOnline) agent.ControllingClient.SendAgentOnline(agentArr); + else agent.ControllingClient.SendAgentOffline(agentArr); + + // done, remove it + friendIDsToSendTo.RemoveAt(i); + } + } + else + { + m_log.DebugFormat("[FRIEND]: Friend {0} ({1}) is offline; not sending.", uuid, i); + + // friend is offline => no need to try sending + friendIDsToSendTo.RemoveAt(i); + } + } + + m_log.DebugFormat("[FRIEND]: Have {0} friends to contact via inter-region comms.", friendIDsToSendTo.Count); + + // we now have all the friends left that are online (we think), but not on this region-server + if (friendIDsToSendTo.Count > 0) + { + // sort them into regions + Dictionary> friendsInRegion = new Dictionary>(); + foreach (UUID uuid in friendIDsToSendTo) + { + ulong handle = friendRegions[uuid].regionHandle; // this can't fail as we filtered above already + List friends; + if (!friendsInRegion.TryGetValue(handle, out friends)) + { + friends = new List(); + friendsInRegion[handle] = friends; + } + friends.Add(uuid); + } + m_log.DebugFormat("[FRIEND]: Found {0} regions to send to.", friendRegions.Count); + + // clear uuids list and collect missed friends in it for the next retry + friendIDsToSendTo.Clear(); + + // send bulk updates to the region + foreach (KeyValuePair> pair in friendsInRegion) + { + m_log.DebugFormat("[FRIEND]: Inform {0} friends in region {1} that user {2} is {3}line", + pair.Value.Count, pair.Key, client.Name, iAmOnline ? "on" : "off"); + + friendIDsToSendTo.AddRange(m_initialScene.InformFriendsInOtherRegion(client.AgentId, pair.Key, pair.Value, iAmOnline)); + } + } + // now we have in friendIDsToSendTo only the agents left that TPed away while we tried to contact them. + // In most cases, it will be empty, and it won't loop here. But sometimes, we have to work harder and try again... + } + while(++retry < 3 && friendIDsToSendTo.Count > 0); + } + + private void OnEconomyDataRequest(UUID agentID) + { + // KLUDGE: This is the only way I found to get a message (only) after login was completed and the + // client is connected enough to receive UDP packets). + // This packet seems to be sent only once, just after connection was established to the first + // region after login. + // We use it here to trigger a presence update; the old update-on-login was never be heard by + // the freshly logged in viewer, as it wasn't connected to the region at that time. + // TODO: Feel free to replace this by a better solution if you find one. + + // get the agent. This should work every time, as we just got a packet from it + //ScenePresence agent = GetRootPresenceFromAgentID(agentID); + // KLUDGE 2: As this is sent quite early, the avatar isn't here as root agent yet. So, we have to cheat a bit + ScenePresence agent = GetAnyPresenceFromAgentID(agentID); + + // just to be paranoid... + if (agent == null) + { + m_log.ErrorFormat("[FRIEND]: Got a packet from agent {0} who can't be found anymore!?", agentID); + return; + } + + List fl; + lock (m_friendLists) + { + fl = (List)m_friendLists.Get(agent.ControllingClient.AgentId, + m_initialScene.GetFriendList); + } + + // tell everyone that we are online + SendPresenceState(agent.ControllingClient, fl, true); + } + + private void OnLogout(IClientAPI remoteClient) + { + m_log.ErrorFormat("[FRIEND]: Client {0} logged out", remoteClient.Name); + + List fl; + lock (m_friendLists) + { + fl = (List)m_friendLists.Get(remoteClient.AgentId, + m_initialScene.GetFriendList); + } + + // tell everyone that we are offline + SendPresenceState(remoteClient, fl, false); + } + + private void outPending() + { + m_log.DebugFormat("[FRIEND]: got {0} requests pending", m_pendingFriendRequests.Count); + foreach (KeyValuePair pair in m_pendingFriendRequests) + { + m_log.DebugFormat("[FRIEND]: tid={0}, agent={1}, name={2}, count={3}", + pair.Key, pair.Value.agentID, pair.Value.agentName, pair.Value.count); + } } } #endregion - - public struct StoredFriendListUpdate - { - public UUID storedFor; - public UUID storedAbout; - public bool OnlineYN; - } } diff --git a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs index adf1103ea3..55973819c5 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs @@ -137,8 +137,14 @@ namespace OpenSim.Region.Environment.Modules.Avatar.InstantMessage // IM dialogs need to be pre-processed and have their sessionID filled by the server // so the sim can match the transaction on the return packet. - // Don't send a Friend Dialog IM with a UUID.Zero session. - if (!dialogHandledElsewhere) + // Don't process IMs that are handled elsewhere (e.g. friend dialog + // IMs) with a non-UUID.Zero agent session, as those have been send + // by a client (either directly or from another region via + // inter-region communication) and will be processed in another + // module (e.g. the friends-module). + // IMs with fromAgentSession == UUID.Zero come from the server, and + // have to be passed to the matching viewer + if (!dialogHandledElsewhere || fromAgentSession == UUID.Zero) { // Try root avatar only first foreach (Scene scene in m_scenes) diff --git a/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs b/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs index da7a10c2c9..369b56c9d4 100644 --- a/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs +++ b/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs @@ -963,5 +963,9 @@ namespace OpenSim.Region.Environment.Modules.World.NPC public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) { } + + public void SendTerminateFriend(UUID exFriendID) + { + } } } diff --git a/OpenSim/Region/Environment/Scenes/Scene.cs b/OpenSim/Region/Environment/Scenes/Scene.cs index f411a7f78f..647682ea21 100644 --- a/OpenSim/Region/Environment/Scenes/Scene.cs +++ b/OpenSim/Region/Environment/Scenes/Scene.cs @@ -3200,6 +3200,20 @@ namespace OpenSim.Region.Environment.Scenes return CommsManager.GetUserFriendList(avatarID); } + public Dictionary GetFriendRegionInfos(List uuids) + { + return CommsManager.GetFriendRegionInfos(uuids); + } + + public List InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List friends, bool online) + { + return CommsManager.InformFriendsInOtherRegion(agentId, destRegionHandle, friends, online); + } + + public bool TriggerTerminateFriend(ulong regionHandle, UUID agentID, UUID exFriendID) + { + return CommsManager.TriggerTerminateFriend(regionHandle, agentID, exFriendID); + } #endregion diff --git a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs index b55e5b6ada..0131109910 100644 --- a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs +++ b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs @@ -962,5 +962,9 @@ namespace OpenSim.Region.Examples.SimpleModule public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) { } + + public void SendTerminateFriend(UUID exFriendID) + { + } } } diff --git a/bin/OpenSim.Data.addin.xml b/bin/OpenSim.Data.addin.xml index c3d016bd83..4859b658e7 100644 --- a/bin/OpenSim.Data.addin.xml +++ b/bin/OpenSim.Data.addin.xml @@ -4,6 +4,7 @@ + diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index 32944ab558..439efbcb9a 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -203,6 +203,9 @@ asset_server_url = "http://127.0.0.1:8003" inventory_server_url = "http://127.0.0.1:8004" +; The MessagingServer is a companion of the UserServer. It uses +; user_send_key and user_recv_key, too +messaging_server_url = "http://127.0.0.1:8006" [Chat] whisper_distance = 10