using System; using System.Collections.Generic; using System.Text; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Framework; using OpenMetaverse; using log4net; using System.Reflection; using OpenSim.Framework.Communications.Cache; using ModularRex.RexFramework; using System.Timers; namespace ModularRex.RexParts { public class RexObjectRezModule : IRegionModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private Scene m_scene; private ModrexObjects m_rexObjects; /// /// Is the deleter currently enabled? /// public bool DeleterEnabled; private Timer m_inventoryTicker = new Timer(2000); private readonly Queue m_inventoryDeletes = new Queue(); #region IRegionModule Members public void Close() { } public void Initialise(Scene scene, Nini.Config.IConfigSource source) { m_scene = scene; } public bool IsSharedModule { get { return false; } } public string Name { get { return "RexObjectRezModule"; } } public void PostInitialise() { m_inventoryTicker.AutoReset = false; m_inventoryTicker.Elapsed += InventoryRunDeleteTimer; m_scene.EventManager.OnNewClient += HandleNewClient; OpenSim.Region.Framework.Interfaces.IRegionModule module = m_scene.Modules["RexObjectsModule"]; if (module != null && module is ModrexObjects) { m_rexObjects = (ModrexObjects)module; } DeleterEnabled = true; } #endregion #region Event Handlers private void HandleNewClient(IClientAPI client) { //Do these to all clients //Other clients might accidentially take object with rex properties //They also might rez in back again ScenePresence avatar = m_scene.GetScenePresence(client.AgentId); if (avatar != null) { avatar.ControllingClient.OnRezObject -= avatar.Scene.RezObject; avatar.ControllingClient.OnDeRezObject -= avatar.Scene.DeRezObject; avatar.ControllingClient.OnRezObject += ClientRezObject; avatar.ControllingClient.OnDeRezObject += DeRezObj; } } private void DeRezObj(IClientAPI remoteClient, List localIDs, UUID groupID, DeRezAction action, UUID destinationID) { foreach (uint id in localIDs) { ClientDeRezObject(remoteClient, id, groupID, action, destinationID); } } private void ClientDeRezObject(IClientAPI remoteClient, uint localID, UUID groupID, DeRezAction action, UUID destinationID) { SceneObjectPart part = m_scene.GetSceneObjectPart(localID); if (part == null) return; if (part.ParentGroup == null || part.ParentGroup.IsDeleted) return; // Can't delete child prims if (part != part.ParentGroup.RootPart) return; SceneObjectGroup grp = part.ParentGroup; //force a database backup/update on this SceneObjectGroup //So that we know the database is upto date, for when deleting the object from it m_scene.ForceSceneObjectBackup(grp); bool permissionToTake = false; bool permissionToDelete = false; if (action == DeRezAction.SaveToExistingUserInventoryItem) { if (grp.OwnerID == remoteClient.AgentId && grp.RootPart.FromUserInventoryItemID != UUID.Zero) { permissionToTake = true; permissionToDelete = false; } } else if (action == DeRezAction.TakeCopy) { permissionToTake = m_scene.Permissions.CanTakeCopyObject( grp.UUID, remoteClient.AgentId); } else if (action == DeRezAction.GodTakeCopy) { permissionToTake = m_scene.Permissions.IsGod( remoteClient.AgentId); } else if (action == DeRezAction.Take) { permissionToTake = m_scene.Permissions.CanTakeObject( grp.UUID, remoteClient.AgentId); //If they can take, they can delete! permissionToDelete = permissionToTake; } else if (action == DeRezAction.Delete) { permissionToTake = m_scene.Permissions.CanDeleteObject( grp.UUID, remoteClient.AgentId); permissionToDelete = permissionToTake; } else if (action == DeRezAction.Return) { if (remoteClient != null) { permissionToTake = m_scene.Permissions.CanReturnObject( grp.UUID, remoteClient.AgentId); permissionToDelete = permissionToTake; if (permissionToDelete) { m_scene.AddReturn(grp.OwnerID, grp.Name, grp.AbsolutePosition, "parcel owner return"); } } else // Auto return passes through here with null agent { permissionToTake = true; permissionToDelete = true; } } else { m_log.DebugFormat( "[AGENT INVENTORY]: Ignoring unexpected derez action {0} for {1}", action, remoteClient.Name); return; } if (permissionToTake) { DeleteToInventory(action, destinationID, grp, remoteClient, permissionToDelete); ////TODO: Change this to some kind of async queue ////The queue was orginally introduced in OpenSim revision 5346: //// * Moves sending items to inventory via a delete into a seperate thread (this thread //// can be expanded to support all sends to inventory from inworld easily enough). //// Thread is temporary and only exists while items are being returned. //// * This should remove the "lag" caused by deleting many objects. //// * Patch brought to you by Joshua Nightshade's bitching at me to fix it. //UUID assetId = m_scene.DeleteToInventory(action, destinationID, grp, remoteClient); ////Get the item id of the asset so the RexObjectProperties can be changed to that id //CachedUserInfo userInfo = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(remoteClient.AgentId); //InventoryItemBase item = userInfo.RootFolder.FindAsset(assetId); ////Clone the old properties //if (m_rexObjects != null) //{ // RexObjectProperties origprops = m_rexObjects.GetObject(part.UUID); // RexObjectProperties cloneprops = m_rexObjects.GetObject(item.ID); // cloneprops.SetRexPrimDataFromObject(origprops); //} //if (permissionToDelete) //{ // grp.DeleteGroup(false); //} } else if (permissionToDelete) { m_scene.DeleteSceneObject(grp, false); } } private void ClientRezObject(IClientAPI remoteClient, UUID itemID, Vector3 RayEnd, Vector3 RayStart, UUID RayTargetID, byte BypassRayCast, bool RayEndIsIntersection, bool RezSelected, bool RemoveItem, UUID fromTaskID) { SceneObjectGroup group = m_scene.RezObject(remoteClient, itemID, RayEnd, RayStart, RayTargetID, BypassRayCast, RayEndIsIntersection, RezSelected, RemoveItem, fromTaskID, false); RexObjectProperties robj = m_rexObjects.GetObject(group.RootPart.FromUserInventoryItemID); RexObjectProperties newprops = m_rexObjects.GetObject(group.RootPart.UUID); newprops.SetRexPrimDataFromObject(robj); } #endregion #region AsyncSceneObjectGroupDeleter /// /// Delete the given object from the scene /// public void DeleteToInventory(DeRezAction action, UUID folderID, SceneObjectGroup objectGroup, IClientAPI remoteClient, bool permissionToDelete) { if (DeleterEnabled) m_inventoryTicker.Stop(); lock (m_inventoryDeletes) { DeleteToInventoryHolder dtis = new DeleteToInventoryHolder(); dtis.action = action; dtis.folderID = folderID; dtis.objectGroup = objectGroup; dtis.remoteClient = remoteClient; dtis.permissionToDelete = permissionToDelete; m_inventoryDeletes.Enqueue(dtis); } if (DeleterEnabled) m_inventoryTicker.Start(); // Visually remove it, even if it isnt really gone yet. This means that if we crash before the object // has gone to inventory, it will reappear in the region again on restart instead of being lost. // This is not ideal since the object will still be available for manipulation when it should be, but it's // better than losing the object for now. if (permissionToDelete) objectGroup.DeleteGroup(false); } private void InventoryRunDeleteTimer(object sender, ElapsedEventArgs e) { m_log.Debug("[SCENE]: Starting send to inventory loop"); while (InventoryDeQueueAndDelete()) { m_log.Debug("[SCENE]: Sent item successfully to inventory, continuing..."); } } /// /// Move the next object in the queue to inventory. Then delete it properly from the scene. /// /// public bool InventoryDeQueueAndDelete() { DeleteToInventoryHolder x = null; try { lock (m_inventoryDeletes) { int left = m_inventoryDeletes.Count; if (left > 0) { m_log.DebugFormat( "[SCENE]: Sending object to user's inventory, {0} item(s) remaining.", left); x = m_inventoryDeletes.Dequeue(); try { //m_scene.DeleteToInventory(x.action, x.folderID, x.objectGroup, x.remoteClient); //if (x.permissionToDelete) // m_scene.DeleteSceneObject(x.objectGroup, false); UUID assetId = m_scene.DeleteToInventory(x.action, x.folderID, x.objectGroup, x.remoteClient); //Get the item id of the asset so the RexObjectProperties can be changed to that id CachedUserInfo userInfo = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(x.remoteClient.AgentId); if (userInfo.RootFolder != null) { InventoryItemBase item = userInfo.RootFolder.FindAsset(assetId); //Clone the old properties if (m_rexObjects != null) { RexObjectProperties origprops = m_rexObjects.GetObject(x.objectGroup.RootPart.UUID); RexObjectProperties cloneprops = m_rexObjects.GetObject(item.ID); cloneprops.SetRexPrimDataFromObject(origprops); } } else m_log.Warn("[REXOBJECTS]: Could not find users root folder from cache. Did not clone rex object"); if (x.permissionToDelete) { m_scene.DeleteSceneObject(x.objectGroup, false); m_rexObjects.DeleteObject(x.objectGroup.RootPart.UUID); } } catch (Exception e) { m_log.DebugFormat("Exception background sending object: " + e); } return true; } } } catch (Exception e) { // We can't put the object group details in here since the root part may have disappeared (which is where these sit). // FIXME: This needs to be fixed. m_log.ErrorFormat( "[SCENE]: Queued sending of scene object to agent {0} {1} failed: {2}", (x != null ? x.remoteClient.Name : "unavailable"), (x != null ? x.remoteClient.AgentId.ToString() : "unavailable"), e.ToString()); } m_log.Debug("[SCENE]: No objects left in inventory send queue."); return false; } #endregion } class DeleteToInventoryHolder { public DeRezAction action; public IClientAPI remoteClient; public SceneObjectGroup objectGroup; public UUID folderID; public bool permissionToDelete; } }