From a89d097355526d4dc52a75a9734c6a02c3008ef4 Mon Sep 17 00:00:00 2001 From: Dr Scofield Date: Mon, 9 Feb 2009 09:16:15 +0000 Subject: starting phase 2 of the OpenSim.Region.Environment commit: relocating OpenSim.Region.Environment.Modules.Agent en bloc to OpenSim.Region.CoreModules --- .../AgentAssetTransactionsManager.cs | 231 +++++++++ .../AssetTransaction/AgentAssetsTransactions.cs | 232 +++++++++ .../AssetTransaction/AssetTransactionModule.cs | 288 +++++++++++ .../Agent/AssetTransaction/AssetXferUploader.cs | 267 +++++++++++ .../Agent/Capabilities/CapabilitiesModule.cs | 210 ++++++++ .../Agent/TextureDownload/TextureDownloadModule.cs | 295 ++++++++++++ .../Agent/TextureDownload/TextureNotFoundSender.cs | 90 ++++ .../TextureDownload/UserTextureDownloadService.cs | 267 +++++++++++ .../Agent/TextureSender/J2KDecoderModule.cs | 533 +++++++++++++++++++++ .../TextureSender/Tests/TextureSenderTests.cs | 177 +++++++ .../Agent/TextureSender/TextureSender.cs | 213 ++++++++ .../Region/CoreModules/Agent/Xfer/XferModule.cs | 232 +++++++++ 12 files changed, 3035 insertions(+) create mode 100644 OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetTransactionsManager.cs create mode 100644 OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetsTransactions.cs create mode 100644 OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetTransactionModule.cs create mode 100644 OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetXferUploader.cs create mode 100644 OpenSim/Region/CoreModules/Agent/Capabilities/CapabilitiesModule.cs create mode 100644 OpenSim/Region/CoreModules/Agent/TextureDownload/TextureDownloadModule.cs create mode 100644 OpenSim/Region/CoreModules/Agent/TextureDownload/TextureNotFoundSender.cs create mode 100644 OpenSim/Region/CoreModules/Agent/TextureDownload/UserTextureDownloadService.cs create mode 100644 OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs create mode 100644 OpenSim/Region/CoreModules/Agent/TextureSender/Tests/TextureSenderTests.cs create mode 100644 OpenSim/Region/CoreModules/Agent/TextureSender/TextureSender.cs create mode 100644 OpenSim/Region/CoreModules/Agent/Xfer/XferModule.cs (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetTransactionsManager.cs b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetTransactionsManager.cs new file mode 100644 index 0000000..2be1eaa --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetTransactionsManager.cs @@ -0,0 +1,231 @@ +/* + * 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 System.Reflection; +//using log4net; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.Agent.AssetTransaction +{ + /* + public class AgentAssetTransactionsManager + { + //private static readonly ILog m_log + // = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Each agent has its own singleton collection of transactions + /// + private Dictionary AgentTransactions = + new Dictionary(); + + /// + /// Should we dump uploaded assets to the filesystem? + /// + private bool m_dumpAssetsToFile; + + public Scene MyScene; + + public AgentAssetTransactionsManager(Scene scene, bool dumpAssetsToFile) + { + MyScene = scene; + m_dumpAssetsToFile = dumpAssetsToFile; + } + + /// + /// Get the collection of asset transactions for the given user. If one does not already exist, it + /// is created. + /// + /// + /// + private AgentAssetTransactions GetUserTransactions(UUID userID) + { + lock (AgentTransactions) + { + if (!AgentTransactions.ContainsKey(userID)) + { + AgentAssetTransactions transactions = null; + //= new AgentAssetTransactions(userID, this, m_dumpAssetsToFile); + AgentTransactions.Add(userID, transactions); + } + + return AgentTransactions[userID]; + } + } + + /// + /// Remove the given agent asset transactions. This should be called when a client is departing + /// from a scene (and hence won't be making any more transactions here). + /// + /// + public void RemoveAgentAssetTransactions(UUID userID) + { + // m_log.DebugFormat("Removing agent asset transactions structure for agent {0}", userID); + + lock (AgentTransactions) + { + AgentTransactions.Remove(userID); + } + } + + /// + /// Create an inventory item from data that has been received through a transaction. + /// + /// This is called when new clothing or body parts are created. It may also be called in other + /// situations. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void HandleItemCreationFromTransaction(IClientAPI remoteClient, UUID transactionID, UUID folderID, + uint callbackID, string description, string name, sbyte invType, + sbyte type, byte wearableType, uint nextOwnerMask) + { +// m_log.DebugFormat( +// "[TRANSACTIONS MANAGER] Called HandleItemCreationFromTransaction with item {0}", name); + + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + transactions.RequestCreateInventoryItem( + remoteClient, transactionID, folderID, callbackID, description, + name, invType, type, wearableType, nextOwnerMask); + } + + /// + /// Update an inventory item with data that has been received through a transaction. + /// + /// This is called when clothing or body parts are updated (for instance, with new textures or + /// colours). It may also be called in other situations. + /// + /// + /// + /// + public void HandleItemUpdateFromTransaction(IClientAPI remoteClient, UUID transactionID, + InventoryItemBase item) + { +// m_log.DebugFormat( +// "[TRANSACTIONS MANAGER] Called HandleItemUpdateFromTransaction with item {0}", +// item.Name); + + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + transactions.RequestUpdateInventoryItem(remoteClient, transactionID, item); + } + + /// + /// Update a task inventory item with data that has been received through a transaction. + /// + /// This is currently called when, for instance, a notecard in a prim is saved. The data is sent + /// up through a single AssetUploadRequest. A subsequent UpdateTaskInventory then references the transaction + /// and comes through this method. + /// + /// + /// + /// + public void HandleTaskItemUpdateFromTransaction( + IClientAPI remoteClient, SceneObjectPart part, UUID transactionID, TaskInventoryItem item) + { +// m_log.DebugFormat( +// "[TRANSACTIONS MANAGER] Called HandleTaskItemUpdateFromTransaction with item {0}", +// item.Name); + + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + transactions.RequestUpdateTaskInventoryItem(remoteClient, part, transactionID, item); + } + + /// + /// Request that a client (agent) begin an asset transfer. + /// + /// + /// + /// + /// + /// + /// + public void HandleUDPUploadRequest(IClientAPI remoteClient, UUID assetID, UUID transaction, sbyte type, + byte[] data, bool storeLocal, bool tempFile) + { + //System.Console.WriteLine("HandleUDPUploadRequest - assetID: " + assetID.ToString() + " transaction: " + transaction.ToString() + " type: " + type.ToString() + " storelocal: " + storeLocal + " tempFile: " + tempFile); + if (((AssetType)type == AssetType.Texture || + (AssetType)type == AssetType.Sound || + (AssetType)type == AssetType.TextureTGA || + (AssetType)type == AssetType.Animation) && + tempFile == false) + { + Scene scene = (Scene)remoteClient.Scene; + IMoneyModule mm = scene.RequestModuleInterface(); + + if (mm != null) + { + if (!mm.UploadCovered(remoteClient)) + { + remoteClient.SendAgentAlertMessage("Unable to upload asset. Insufficient funds.", false); + return; + } + } + } + + //Console.WriteLine("asset upload of " + assetID); + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + AssetXferUploader uploader = transactions.RequestXferUploader(transaction); + if (uploader != null) + { + uploader.Initialise(remoteClient, assetID, transaction, type, data, storeLocal, tempFile); + } + } + + /// + /// Handle asset transfer data packets received in response to the asset upload request in + /// HandleUDPUploadRequest() + /// + /// + /// + /// + /// + public void HandleXfer(IClientAPI remoteClient, ulong xferID, uint packetID, byte[] data) + { + //System.Console.WriteLine("xferID: " + xferID + " packetID: " + packetID + " data!"); + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + transactions.HandleXfer(xferID, packetID, data); + } + } + */ +} diff --git a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetsTransactions.cs b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetsTransactions.cs new file mode 100644 index 0000000..b4dbaa1 --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetsTransactions.cs @@ -0,0 +1,232 @@ +/* + * 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; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Communications.Cache; + +namespace OpenSim.Region.CoreModules.Agent.AssetTransaction +{ + /// + /// Manage asset transactions for a single agent. + /// + public class AgentAssetTransactions + { + private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + // Fields + private bool m_dumpAssetsToFile; + public AssetTransactionModule Manager; + public UUID UserID; + public Dictionary XferUploaders = new Dictionary(); + + // Methods + public AgentAssetTransactions(UUID agentID, AssetTransactionModule manager, bool dumpAssetsToFile) + { + UserID = agentID; + Manager = manager; + m_dumpAssetsToFile = dumpAssetsToFile; + } + + public AssetXferUploader RequestXferUploader(UUID transactionID) + { + if (!XferUploaders.ContainsKey(transactionID)) + { + AssetXferUploader uploader = new AssetXferUploader(this, m_dumpAssetsToFile); + + lock (XferUploaders) + { + XferUploaders.Add(transactionID, uploader); + } + + return uploader; + } + return null; + } + + public void HandleXfer(ulong xferID, uint packetID, byte[] data) + { + lock (XferUploaders) + { + foreach (AssetXferUploader uploader in XferUploaders.Values) + { + if (uploader.XferID == xferID) + { + uploader.HandleXferPacket(xferID, packetID, data); + break; + } + } + } + } + + public void RequestCreateInventoryItem(IClientAPI remoteClient, UUID transactionID, UUID folderID, + uint callbackID, string description, string name, sbyte invType, + sbyte type, byte wearableType, uint nextOwnerMask) + { + if (XferUploaders.ContainsKey(transactionID)) + { + XferUploaders[transactionID].RequestCreateInventoryItem(remoteClient, transactionID, folderID, + callbackID, description, name, invType, type, + wearableType, nextOwnerMask); + } + } + + + + /// + /// Get an uploaded asset. If the data is successfully retrieved, the transaction will be removed. + /// + /// + /// The asset if the upload has completed, null if it has not. + public AssetBase GetTransactionAsset(UUID transactionID) + { + if (XferUploaders.ContainsKey(transactionID)) + { + AssetXferUploader uploader = XferUploaders[transactionID]; + AssetBase asset = uploader.GetAssetData(); + + lock (XferUploaders) + { + XferUploaders.Remove(transactionID); + } + + return asset; + } + + return null; + } + + //private void CreateItemFromUpload(AssetBase asset, IClientAPI ourClient, UUID inventoryFolderID, uint nextPerms, uint wearableType) + //{ + // Manager.MyScene.CommsManager.AssetCache.AddAsset(asset); + // CachedUserInfo userInfo = Manager.MyScene.CommsManager.UserProfileCacheService.GetUserDetails( + // ourClient.AgentId); + + // if (userInfo != null) + // { + // InventoryItemBase item = new InventoryItemBase(); + // item.Owner = ourClient.AgentId; + // item.Creator = ourClient.AgentId; + // item.ID = UUID.Random(); + // item.AssetID = asset.FullID; + // item.Description = asset.Description; + // item.Name = asset.Name; + // item.AssetType = asset.Type; + // item.InvType = asset.Type; + // item.Folder = inventoryFolderID; + // item.BasePermissions = 0x7fffffff; + // item.CurrentPermissions = 0x7fffffff; + // item.EveryOnePermissions = 0; + // item.NextPermissions = nextPerms; + // item.Flags = wearableType; + // item.CreationDate = Util.UnixTimeSinceEpoch(); + + // userInfo.AddItem(item); + // ourClient.SendInventoryItemCreateUpdate(item); + // } + // else + // { + // m_log.ErrorFormat( + // "[ASSET TRANSACTIONS]: Could not find user {0} for inventory item creation", + // ourClient.AgentId); + // } + //} + + public void RequestUpdateTaskInventoryItem( + IClientAPI remoteClient, SceneObjectPart part, UUID transactionID, TaskInventoryItem item) + { + if (XferUploaders.ContainsKey(transactionID)) + { + AssetBase asset = XferUploaders[transactionID].GetAssetData(); + if (asset != null) + { + m_log.DebugFormat( + "[ASSET TRANSACTIONS]: Updating task item {0} in {1} with asset in transaction {2}", + item.Name, part.Name, transactionID); + + asset.Metadata.Name = item.Name; + asset.Metadata.Description = item.Description; + asset.Metadata.Type = (sbyte)item.Type; + item.AssetID = asset.Metadata.FullID; + + Manager.MyScene.CommsManager.AssetCache.AddAsset(asset); + + if (part.Inventory.UpdateInventoryItem(item)) + part.GetProperties(remoteClient); + } + } + } + + + public void RequestUpdateInventoryItem(IClientAPI remoteClient, UUID transactionID, + InventoryItemBase item) + { + if (XferUploaders.ContainsKey(transactionID)) + { + CachedUserInfo userInfo = Manager.MyScene.CommsManager.UserProfileCacheService.GetUserDetails( + remoteClient.AgentId); + + if (userInfo != null) + { + UUID assetID = UUID.Combine(transactionID, remoteClient.SecureSessionId); + + AssetBase asset + = Manager.MyScene.CommsManager.AssetCache.GetAsset( + assetID, (item.AssetType == (int)AssetType.Texture ? true : false)); + + if (asset == null) + { + asset = GetTransactionAsset(transactionID); + } + + if (asset != null && asset.Metadata.FullID == assetID) + { + // Assets never get updated, new ones get created + asset.Metadata.FullID = UUID.Random(); + asset.Metadata.Name = item.Name; + asset.Metadata.Description = item.Description; + asset.Metadata.Type = (sbyte)item.AssetType; + item.AssetID = asset.Metadata.FullID; + + Manager.MyScene.CommsManager.AssetCache.AddAsset(asset); + } + + userInfo.UpdateItem(item); + } + else + { + m_log.ErrorFormat( + "[ASSET TRANSACTIONS]: Could not find user {0} for inventory item update", + remoteClient.AgentId); + } + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetTransactionModule.cs b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetTransactionModule.cs new file mode 100644 index 0000000..77dfd44 --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetTransactionModule.cs @@ -0,0 +1,288 @@ +/* + * 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; +using System.Collections.Generic; +using OpenMetaverse; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Agent.AssetTransaction +{ + public class AssetTransactionModule : IRegionModule, IAgentAssetTransactions + { + private readonly Dictionary RegisteredScenes = new Dictionary(); + private bool m_dumpAssetsToFile = false; + private Scene m_scene = null; + + public Scene MyScene + { + get{ return m_scene;} + } + + /// + /// Each agent has its own singleton collection of transactions + /// + private Dictionary AgentTransactions = + new Dictionary(); + + + public AssetTransactionModule() + { + // System.Console.WriteLine("creating AgentAssetTransactionModule"); + } + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + if (!RegisteredScenes.ContainsKey(scene.RegionInfo.RegionID)) + { + // System.Console.WriteLine("initialising AgentAssetTransactionModule"); + RegisteredScenes.Add(scene.RegionInfo.RegionID, scene); + scene.RegisterModuleInterface(this); + + scene.EventManager.OnNewClient += NewClient; + } + + if (m_scene == null) + { + m_scene = scene; + if (config.Configs["StandAlone"] != null) + { + try + { + m_dumpAssetsToFile = config.Configs["StandAlone"].GetBoolean("dump_assets_to_file", false); + } + catch (Exception) + { + } + } + else + { + } + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "AgentTransactionModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + public void NewClient(IClientAPI client) + { + client.OnAssetUploadRequest += HandleUDPUploadRequest; + client.OnXferReceive += HandleXfer; + } + + #region AgentAssetTransactions + /// + /// Get the collection of asset transactions for the given user. If one does not already exist, it + /// is created. + /// + /// + /// + private AgentAssetTransactions GetUserTransactions(UUID userID) + { + lock (AgentTransactions) + { + if (!AgentTransactions.ContainsKey(userID)) + { + AgentAssetTransactions transactions = new AgentAssetTransactions(userID, this, m_dumpAssetsToFile); + AgentTransactions.Add(userID, transactions); + } + + return AgentTransactions[userID]; + } + } + + /// + /// Remove the given agent asset transactions. This should be called when a client is departing + /// from a scene (and hence won't be making any more transactions here). + /// + /// + public void RemoveAgentAssetTransactions(UUID userID) + { + // m_log.DebugFormat("Removing agent asset transactions structure for agent {0}", userID); + + lock (AgentTransactions) + { + AgentTransactions.Remove(userID); + } + } + + /// + /// Create an inventory item from data that has been received through a transaction. + /// + /// This is called when new clothing or body parts are created. It may also be called in other + /// situations. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void HandleItemCreationFromTransaction(IClientAPI remoteClient, UUID transactionID, UUID folderID, + uint callbackID, string description, string name, sbyte invType, + sbyte type, byte wearableType, uint nextOwnerMask) + { + // m_log.DebugFormat( + // "[TRANSACTIONS MANAGER] Called HandleItemCreationFromTransaction with item {0}", name); + + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + transactions.RequestCreateInventoryItem( + remoteClient, transactionID, folderID, callbackID, description, + name, invType, type, wearableType, nextOwnerMask); + } + + /// + /// Update an inventory item with data that has been received through a transaction. + /// + /// This is called when clothing or body parts are updated (for instance, with new textures or + /// colours). It may also be called in other situations. + /// + /// + /// + /// + public void HandleItemUpdateFromTransaction(IClientAPI remoteClient, UUID transactionID, + InventoryItemBase item) + { + // m_log.DebugFormat( + // "[TRANSACTIONS MANAGER] Called HandleItemUpdateFromTransaction with item {0}", + // item.Name); + + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + transactions.RequestUpdateInventoryItem(remoteClient, transactionID, item); + } + + /// + /// Update a task inventory item with data that has been received through a transaction. + /// + /// This is currently called when, for instance, a notecard in a prim is saved. The data is sent + /// up through a single AssetUploadRequest. A subsequent UpdateTaskInventory then references the transaction + /// and comes through this method. + /// + /// + /// + /// + public void HandleTaskItemUpdateFromTransaction( + IClientAPI remoteClient, SceneObjectPart part, UUID transactionID, TaskInventoryItem item) + { + // m_log.DebugFormat( + // "[TRANSACTIONS MANAGER] Called HandleTaskItemUpdateFromTransaction with item {0}", + // item.Name); + + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + transactions.RequestUpdateTaskInventoryItem(remoteClient, part, transactionID, item); + } + + /// + /// Request that a client (agent) begin an asset transfer. + /// + /// + /// + /// + /// + /// + /// + public void HandleUDPUploadRequest(IClientAPI remoteClient, UUID assetID, UUID transaction, sbyte type, + byte[] data, bool storeLocal, bool tempFile) + { + //System.Console.WriteLine("HandleUDPUploadRequest - assetID: " + assetID.ToString() + " transaction: " + transaction.ToString() + " type: " + type.ToString() + " storelocal: " + storeLocal + " tempFile: " + tempFile); + if (((AssetType)type == AssetType.Texture || + (AssetType)type == AssetType.Sound || + (AssetType)type == AssetType.TextureTGA || + (AssetType)type == AssetType.Animation) && + tempFile == false) + { + Scene scene = (Scene)remoteClient.Scene; + IMoneyModule mm = scene.RequestModuleInterface(); + + if (mm != null) + { + if (!mm.UploadCovered(remoteClient)) + { + remoteClient.SendAgentAlertMessage("Unable to upload asset. Insufficient funds.", false); + return; + } + } + } + + //Console.WriteLine("asset upload of " + assetID); + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + AssetXferUploader uploader = transactions.RequestXferUploader(transaction); + if (uploader != null) + { + uploader.Initialise(remoteClient, assetID, transaction, type, data, storeLocal, tempFile); + } + } + + /// + /// Handle asset transfer data packets received in response to the asset upload request in + /// HandleUDPUploadRequest() + /// + /// + /// + /// + /// + public void HandleXfer(IClientAPI remoteClient, ulong xferID, uint packetID, byte[] data) + { + //System.Console.WriteLine("xferID: " + xferID + " packetID: " + packetID + " data!"); + AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId); + + transactions.HandleXfer(xferID, packetID, data); + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetXferUploader.cs b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetXferUploader.cs new file mode 100644 index 0000000..aef2664 --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetXferUploader.cs @@ -0,0 +1,267 @@ +/* + * 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; +using System.IO; +using System.Reflection; +using log4net; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Agent.AssetTransaction +{ + public class AssetXferUploader + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private AssetBase m_asset; + private UUID InventFolder = UUID.Zero; + private sbyte invType = 0; + private bool m_createItem = false; + private string m_description = String.Empty; + private bool m_dumpAssetToFile; + private bool m_finished = false; + private string m_name = String.Empty; + private bool m_storeLocal; + private AgentAssetTransactions m_userTransactions; + private uint nextPerm = 0; + private IClientAPI ourClient; + private UUID TransactionID = UUID.Zero; + private sbyte type = 0; + private byte wearableType = 0; + public ulong XferID; + + public AssetXferUploader(AgentAssetTransactions transactions, bool dumpAssetToFile) + { + m_userTransactions = transactions; + m_dumpAssetToFile = dumpAssetToFile; + } + + /// + /// Process transfer data received from the client. + /// + /// + /// + /// + /// True if the transfer is complete, false otherwise or if the xferID was not valid + public bool HandleXferPacket(ulong xferID, uint packetID, byte[] data) + { + if (XferID == xferID) + { + if (m_asset.Data.Length > 1) + { + byte[] destinationArray = new byte[m_asset.Data.Length + data.Length]; + Array.Copy(m_asset.Data, 0, destinationArray, 0, m_asset.Data.Length); + Array.Copy(data, 0, destinationArray, m_asset.Data.Length, data.Length); + m_asset.Data = destinationArray; + } + else + { + byte[] buffer2 = new byte[data.Length - 4]; + Array.Copy(data, 4, buffer2, 0, data.Length - 4); + m_asset.Data = buffer2; + } + + ourClient.SendConfirmXfer(xferID, packetID); + + if ((packetID & 0x80000000) != 0) + { + SendCompleteMessage(); + return true; + } + } + + return false; + } + + /// + /// Initialise asset transfer from the client + /// + /// + /// + /// + /// True if the transfer is complete, false otherwise + public bool Initialise(IClientAPI remoteClient, UUID assetID, UUID transaction, sbyte type, byte[] data, + bool storeLocal, bool tempFile) + { + ourClient = remoteClient; + m_asset = new AssetBase(); + m_asset.Metadata.FullID = assetID; + m_asset.Metadata.Type = type; + m_asset.Data = data; + m_asset.Metadata.Name = "blank"; + m_asset.Metadata.Description = "empty"; + m_asset.Metadata.Local = storeLocal; + m_asset.Metadata.Temporary = tempFile; + + TransactionID = transaction; + m_storeLocal = storeLocal; + + if (m_asset.Data.Length > 2) + { + SendCompleteMessage(); + return true; + } + else + { + RequestStartXfer(); + } + + return false; + } + + protected void RequestStartXfer() + { + XferID = Util.GetNextXferID(); + ourClient.SendXferRequest(XferID, m_asset.Metadata.Type, m_asset.Metadata.FullID, 0, new byte[0]); + } + + protected void SendCompleteMessage() + { + ourClient.SendAssetUploadCompleteMessage(m_asset.Metadata.Type, true, m_asset.Metadata.FullID); + + m_finished = true; + if (m_createItem) + { + DoCreateItem(); + } + else if (m_storeLocal) + { + m_userTransactions.Manager.MyScene.CommsManager.AssetCache.AddAsset(m_asset); + } + + m_log.DebugFormat("[ASSET TRANSACTIONS]: Uploaded asset data for transaction {0}", TransactionID); + + if (m_dumpAssetToFile) + { + DateTime now = DateTime.Now; + string filename = + String.Format("{6}_{7}_{0:d2}{1:d2}{2:d2}_{3:d2}{4:d2}{5:d2}.dat", now.Year, now.Month, now.Day, + now.Hour, now.Minute, now.Second, m_asset.Metadata.Name, m_asset.Metadata.Type); + SaveAssetToFile(filename, m_asset.Data); + } + } + + private void SaveAssetToFile(string filename, byte[] data) + { + string assetPath = "UserAssets"; + if (!Directory.Exists(assetPath)) + { + Directory.CreateDirectory(assetPath); + } + FileStream fs = File.Create(Path.Combine(assetPath, filename)); + BinaryWriter bw = new BinaryWriter(fs); + bw.Write(data); + bw.Close(); + fs.Close(); + } + + public void RequestCreateInventoryItem(IClientAPI remoteClient, UUID transactionID, UUID folderID, + uint callbackID, string description, string name, sbyte invType, + sbyte type, byte wearableType, uint nextOwnerMask) + { + if (TransactionID == transactionID) + { + InventFolder = folderID; + m_name = name; + m_description = description; + this.type = type; + this.invType = invType; + this.wearableType = wearableType; + nextPerm = nextOwnerMask; + m_asset.Metadata.Name = name; + m_asset.Metadata.Description = description; + m_asset.Metadata.Type = type; + + if (m_finished) + { + DoCreateItem(); + } + else + { + m_createItem = true; //set flag so the inventory item is created when upload is complete + } + } + } + + + private void DoCreateItem() + { + m_userTransactions.Manager.MyScene.CommsManager.AssetCache.AddAsset(m_asset); + CachedUserInfo userInfo = + m_userTransactions.Manager.MyScene.CommsManager.UserProfileCacheService.GetUserDetails( + ourClient.AgentId); + + if (userInfo != null) + { + InventoryItemBase item = new InventoryItemBase(); + item.Owner = ourClient.AgentId; + item.Creator = ourClient.AgentId; + item.ID = UUID.Random(); + item.AssetID = m_asset.Metadata.FullID; + item.Description = m_description; + item.Name = m_name; + item.AssetType = type; + item.InvType = invType; + item.Folder = InventFolder; + item.BasePermissions = 0x7fffffff; + item.CurrentPermissions = 0x7fffffff; + item.GroupPermissions=0; + item.EveryOnePermissions=0; + item.NextPermissions = nextPerm; + item.Flags = (uint) wearableType; + item.CreationDate = Util.UnixTimeSinceEpoch(); + + userInfo.AddItem(item); + ourClient.SendInventoryItemCreateUpdate(item); + } + else + { + m_log.ErrorFormat( + "[ASSET TRANSACTIONS]: Could not find user {0} for inventory item creation", + ourClient.AgentId); + } + } + + /// + /// Get the asset data uploaded in this transfer. + /// + /// null if the asset has not finished uploading + public AssetBase GetAssetData() + { + if (m_finished) + { + return m_asset; + } + + return null; + } + } +} diff --git a/OpenSim/Region/CoreModules/Agent/Capabilities/CapabilitiesModule.cs b/OpenSim/Region/CoreModules/Agent/Capabilities/CapabilitiesModule.cs new file mode 100644 index 0000000..4f7f177 --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/Capabilities/CapabilitiesModule.cs @@ -0,0 +1,210 @@ +/* + * 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 copyrightD + * 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; +using System.Collections.Generic; +using System.Reflection; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Communications.Capabilities; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using Caps = OpenSim.Framework.Communications.Capabilities.Caps; + +namespace OpenSim.Region.CoreModules.Agent.Capabilities +{ + public class CapabilitiesModule : IRegionModule, ICapabilitiesModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene m_scene; + + /// + /// Each agent has its own capabilities handler. + /// + protected Dictionary m_capsHandlers = new Dictionary(); + + protected Dictionary capsPaths = new Dictionary(); + protected Dictionary> childrenSeeds + = new Dictionary>(); + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + m_scene.RegisterModuleInterface(this); + } + + public void PostInitialise() {} + public void Close() {} + public string Name { get { return "Capabilities Module"; } } + public bool IsSharedModule { get { return false; } } + + public void AddCapsHandler(UUID agentId) + { + if (m_scene.RegionInfo.EstateSettings.IsBanned(agentId)) + return; + + String capsObjectPath = GetCapsPath(agentId); + + if (m_capsHandlers.ContainsKey(agentId)) + { + Caps oldCaps = m_capsHandlers[agentId]; + + m_log.DebugFormat( + "[CAPS]: Reregistering caps for agent {0}. Old caps path {1}, new caps path {2}. ", + agentId, oldCaps.CapsObjectPath, capsObjectPath); + // This should not happen. The caller code is confused. We need to fix that. + // CAPs can never be reregistered, or the client will be confused. + // Hence this return here. + //return; + } + + Caps caps + = new Caps( + m_scene.AssetCache, m_scene.CommsManager.HttpServer, m_scene.RegionInfo.ExternalHostName, + m_scene.CommsManager.HttpServer.Port, + capsObjectPath, agentId, m_scene.DumpAssetsToFile, m_scene.RegionInfo.RegionName); + + caps.RegisterHandlers(); + + m_scene.EventManager.TriggerOnRegisterCaps(agentId, caps); + + caps.AddNewInventoryItem = m_scene.AddUploadedInventoryItem; + caps.ItemUpdatedCall = m_scene.CapsUpdateInventoryItemAsset; + caps.TaskScriptUpdatedCall = m_scene.CapsUpdateTaskInventoryScriptAsset; + caps.CAPSFetchInventoryDescendents = m_scene.HandleFetchInventoryDescendentsCAPS; + caps.GetClient = m_scene.SceneContents.GetControllingClient; + + m_capsHandlers[agentId] = caps; + } + + public void RemoveCapsHandler(UUID agentId) + { + if (childrenSeeds.ContainsKey(agentId)) + { + childrenSeeds.Remove(agentId); + } + + lock (m_capsHandlers) + { + if (m_capsHandlers.ContainsKey(agentId)) + { + m_capsHandlers[agentId].DeregisterHandlers(); + m_scene.EventManager.TriggerOnDeregisterCaps(agentId, m_capsHandlers[agentId]); + m_capsHandlers.Remove(agentId); + } + else + { + m_log.WarnFormat( + "[CAPS]: Received request to remove CAPS handler for root agent {0} in {1}, but no such CAPS handler found!", + agentId, m_scene.RegionInfo.RegionName); + } + } + } + + public Caps GetCapsHandlerForUser(UUID agentId) + { + lock (m_capsHandlers) + { + if (m_capsHandlers.ContainsKey(agentId)) + { + return m_capsHandlers[agentId]; + } + } + + return null; + } + + public void NewUserConnection(AgentCircuitData agent) + { + capsPaths[agent.AgentID] = agent.CapsPath; + childrenSeeds[agent.AgentID] + = ((agent.ChildrenCapSeeds == null) ? new Dictionary() : agent.ChildrenCapSeeds); + } + + public string GetCapsPath(UUID agentId) + { + if (capsPaths.ContainsKey(agentId)) + { + return capsPaths[agentId]; + } + + return null; + } + + public Dictionary GetChildrenSeeds(UUID agentID) + { + Dictionary seeds = null; + if (childrenSeeds.TryGetValue(agentID, out seeds)) + return seeds; + return new Dictionary(); + } + + public void DropChildSeed(UUID agentID, ulong handle) + { + Dictionary seeds; + if (childrenSeeds.TryGetValue(agentID, out seeds)) + { + seeds.Remove(handle); + } + } + + public string GetChildSeed(UUID agentID, ulong handle) + { + Dictionary seeds; + string returnval; + if (childrenSeeds.TryGetValue(agentID, out seeds)) + { + if (seeds.TryGetValue(handle, out returnval)) + return returnval; + } + return null; + } + + public void SetChildrenSeed(UUID agentID, Dictionary seeds) + { + //Console.WriteLine(" !!! Setting child seeds in {0} to {1}", RegionInfo.RegionName, value.Count); + childrenSeeds[agentID] = seeds; + } + + public void DumpChildrenSeeds(UUID agentID) + { + Console.WriteLine("================ ChildrenSeed {0} ================", m_scene.RegionInfo.RegionName); + foreach (KeyValuePair kvp in childrenSeeds[agentID]) + { + uint x, y; + Utils.LongToUInts(kvp.Key, out x, out y); + x = x / Constants.RegionSize; + y = y / Constants.RegionSize; + Console.WriteLine(" >> {0}, {1}: {2}", x, y, kvp.Value); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Agent/TextureDownload/TextureDownloadModule.cs b/OpenSim/Region/CoreModules/Agent/TextureDownload/TextureDownloadModule.cs new file mode 100644 index 0000000..107855d --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/TextureDownload/TextureDownloadModule.cs @@ -0,0 +1,295 @@ +/* + * 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; +using System.Collections.Generic; +using System.Threading; +using OpenMetaverse; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Communications.Cache; +using BlockingQueue = OpenSim.Framework.BlockingQueue; + +namespace OpenSim.Region.CoreModules.Agent.TextureDownload +{ + public class TextureDownloadModule : IRegionModule + { + private static readonly log4net.ILog m_log + = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// There is one queue for all textures waiting to be sent, regardless of the requesting user. + /// + private readonly OpenSim.Framework.BlockingQueue m_queueSenders + = new OpenSim.Framework.BlockingQueue(); + + /// + /// Each user has their own texture download service. + /// + private readonly Dictionary m_userTextureServices = + new Dictionary(); + + private Scene m_scene; + private List m_scenes = new List(); + + private Thread m_thread; + + public TextureDownloadModule() + { + } + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + if (m_scene == null) + { + //Console.WriteLine("Creating Texture download module"); + m_scene = scene; + m_thread = new Thread(new ThreadStart(ProcessTextureSenders)); + m_thread.Name = "ProcessTextureSenderThread"; + m_thread.IsBackground = true; + m_thread.Start(); + ThreadTracker.Add(m_thread); + } + + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + m_scene = scene; + m_scene.EventManager.OnNewClient += NewClient; + m_scene.EventManager.OnRemovePresence += EventManager_OnRemovePresence; + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "TextureDownloadModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + + /// + /// Cleanup the texture service related objects for the removed presence. + /// + /// + private void EventManager_OnRemovePresence(UUID agentId) + { + UserTextureDownloadService textureService; + + lock (m_userTextureServices) + { + if (m_userTextureServices.TryGetValue(agentId, out textureService)) + { + textureService.Close(); + //m_log.DebugFormat("[TEXTURE MODULE]: Removing UserTextureServices from {0}", m_scene.RegionInfo.RegionName); + m_userTextureServices.Remove(agentId); + } + } + } + + public void NewClient(IClientAPI client) + { + UserTextureDownloadService textureService; + + lock (m_userTextureServices) + { + if (m_userTextureServices.TryGetValue(client.AgentId, out textureService)) + { + textureService.Close(); + //m_log.DebugFormat("[TEXTURE MODULE]: Removing outdated UserTextureServices from {0}", m_scene.RegionInfo.RegionName); + m_userTextureServices.Remove(client.AgentId); + } + m_userTextureServices.Add(client.AgentId, new UserTextureDownloadService(client, m_scene, m_queueSenders)); + } + + client.OnRequestTexture += TextureRequest; + } + + /// I'm commenting this out, and replacing it with the implementation below, which + /// may return a null value. This is necessary for avoiding race conditions + /// recreating UserTextureServices for clients that have just been closed. + /// That behavior of always returning a UserTextureServices was causing the + /// A-B-A problem (mantis #2855). + /// + ///// + ///// Does this user have a registered texture download service? + ///// + ///// + ///// + ///// Always returns true, since a service is created if one does not already exist + //private bool TryGetUserTextureService( + // IClientAPI client, out UserTextureDownloadService textureService) + //{ + // lock (m_userTextureServices) + // { + // if (m_userTextureServices.TryGetValue(client.AgentId, out textureService)) + // { + // //m_log.DebugFormat("[TEXTURE MODULE]: Found existing UserTextureServices in ", m_scene.RegionInfo.RegionName); + // return true; + // } + + // m_log.DebugFormat("[TEXTURE MODULE]: Creating new UserTextureServices in ", m_scene.RegionInfo.RegionName); + // textureService = new UserTextureDownloadService(client, m_scene, m_queueSenders); + // m_userTextureServices.Add(client.AgentId, textureService); + + // return true; + // } + //} + + /// + /// Does this user have a registered texture download service? + /// + /// + /// + /// A UserTextureDownloadService or null in the output parameter, and true or false accordingly. + private bool TryGetUserTextureService(IClientAPI client, out UserTextureDownloadService textureService) + { + lock (m_userTextureServices) + { + if (m_userTextureServices.TryGetValue(client.AgentId, out textureService)) + { + //m_log.DebugFormat("[TEXTURE MODULE]: Found existing UserTextureServices in ", m_scene.RegionInfo.RegionName); + return true; + } + + textureService = null; + return false; + } + } + + /// + /// Start the process of requesting a given texture. + /// + /// + /// + public void TextureRequest(Object sender, TextureRequestArgs e) + { + IClientAPI client = (IClientAPI)sender; + + if (e.Priority == 1016001f) // Preview + { + if (client.Scene is Scene) + { + Scene scene = (Scene)client.Scene; + + CachedUserInfo profile = scene.CommsManager.UserProfileCacheService.GetUserDetails(client.AgentId); + if (profile == null) // Deny unknown user + return; + + if (profile.RootFolder == null) // Deny no inventory + return; + + if (profile.UserProfile.GodLevel < 200 && profile.RootFolder.FindAsset(e.RequestedAssetID) == null) // Deny if not owned + return; + + m_log.Debug("Texture preview"); + } + } + + UserTextureDownloadService textureService; + + if (TryGetUserTextureService(client, out textureService)) + { + textureService.HandleTextureRequest(e); + } + } + + /// + /// Entry point for the thread dedicated to processing the texture queue. + /// + public void ProcessTextureSenders() + { + ITextureSender sender = null; + + try + { + while (true) + { + sender = m_queueSenders.Dequeue(); + + if (sender.Cancel) + { + TextureSent(sender); + + sender.Cancel = false; + } + else + { + bool finished = sender.SendTexturePacket(); + if (finished) + { + TextureSent(sender); + } + else + { + m_queueSenders.Enqueue(sender); + } + } + + // Make sure that any sender we currently have can get garbage collected + sender = null; + + //m_log.InfoFormat("[TEXTURE] Texture sender queue size: {0}", m_queueSenders.Count()); + } + } + catch (Exception e) + { + // TODO: Let users in the sim and those entering it and possibly an external watchdog know what has happened + m_log.ErrorFormat( + "[TEXTURE]: Texture send thread terminating with exception. PLEASE REBOOT YOUR SIM - TEXTURES WILL NOT BE AVAILABLE UNTIL YOU DO. Exception is {0}", + e); + } + } + + /// + /// Called when the texture has finished sending. + /// + /// + private void TextureSent(ITextureSender sender) + { + sender.Sending = false; + //m_log.DebugFormat("[TEXTURE]: Removing download stat for {0}", sender.assetID); + m_scene.AddPendingDownloads(-1); + } + } +} diff --git a/OpenSim/Region/CoreModules/Agent/TextureDownload/TextureNotFoundSender.cs b/OpenSim/Region/CoreModules/Agent/TextureDownload/TextureNotFoundSender.cs new file mode 100644 index 0000000..7309282 --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/TextureDownload/TextureNotFoundSender.cs @@ -0,0 +1,90 @@ +/* + * 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.Reflection; +using log4net; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.Agent.TextureDownload +{ + /// + /// Sends a 'texture not found' packet back to the client + /// + public class TextureNotFoundSender : ITextureSender + { + // private static readonly log4net.ILog m_log + // = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + // private IClientAPI m_client; + // private UUID m_textureId; + + public TextureNotFoundSender(IClientAPI client, UUID textureID) + { + //m_client = client; + //m_textureId = textureID; + } + + #region ITextureSender Members + + public bool Sending + { + get { return false; } + set { } + } + + public bool Cancel + { + get { return false; } + set { } + } + + // See ITextureSender + public void UpdateRequest(int discardLevel, uint packetNumber) + { + // No need to implement since priority changes don't affect this operation + } + + // See ITextureSender + public bool SendTexturePacket() + { + // m_log.DebugFormat( + // "[TEXTURE NOT FOUND SENDER]: Informing the client that texture {0} cannot be found", + // m_textureId); + + // XXX Temporarily disabling as this appears to be causing client crashes on at least + // 1.19.0(5) of the Linden Second Life client. + // m_client.SendImageNotFound(m_textureId); + + return true; + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/Agent/TextureDownload/UserTextureDownloadService.cs b/OpenSim/Region/CoreModules/Agent/TextureDownload/UserTextureDownloadService.cs new file mode 100644 index 0000000..675bb0f --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/TextureDownload/UserTextureDownloadService.cs @@ -0,0 +1,267 @@ +/* + * 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 System.Reflection; +using OpenMetaverse; +using log4net; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Limit; +using OpenSim.Framework.Statistics; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Agent.TextureDownload +{ + /// + /// This module sets up texture senders in response to client texture requests, and places them on a + /// processing queue once those senders have the appropriate data (i.e. a texture retrieved from the + /// asset cache). + /// + public class UserTextureDownloadService + { + private static readonly ILog m_log + = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// True if the service has been closed, probably because a user with texture requests still queued + /// logged out. + /// + private bool closed; + + /// + /// We will allow the client to request the same texture n times before dropping further requests + /// + /// This number includes repeated requests for the same texture at different resolutions (which we don't + /// currently handle properly as far as I know). However, this situation should be handled in a more + /// sophisticated way. + /// + private static readonly int MAX_ALLOWED_TEXTURE_REQUESTS = 5; + + /// + /// XXX Also going to limit requests for found textures. + /// + private readonly IRequestLimitStrategy foundTextureLimitStrategy + = new RepeatLimitStrategy(MAX_ALLOWED_TEXTURE_REQUESTS); + + private readonly IClientAPI m_client; + private readonly Scene m_scene; + + /// + /// Texture Senders are placed in this queue once they have received their texture from the asset + /// cache. Another module actually invokes the send. + /// + private readonly OpenSim.Framework.BlockingQueue m_sharedSendersQueue; + + /// + /// Holds texture senders before they have received the appropriate texture from the asset cache. + /// + private readonly Dictionary m_textureSenders = new Dictionary(); + + /// + /// We're going to limit requests for the same missing texture. + /// XXX This is really a temporary solution to deal with the situation where a client continually requests + /// the same missing textures + /// + private readonly IRequestLimitStrategy missingTextureLimitStrategy + = new RepeatLimitStrategy(MAX_ALLOWED_TEXTURE_REQUESTS); + + public UserTextureDownloadService( + IClientAPI client, Scene scene, OpenSim.Framework.BlockingQueue sharedQueue) + { + m_client = client; + m_scene = scene; + m_sharedSendersQueue = sharedQueue; + } + + /// + /// Handle a texture request. This involves creating a texture sender and placing it on the + /// previously passed in shared queue. + /// + /// + public void HandleTextureRequest(TextureRequestArgs e) + { + TextureSender.TextureSender textureSender; + + //TODO: should be working out the data size/ number of packets to be sent for each discard level + if ((e.DiscardLevel >= 0) || (e.Priority != 0)) + { + lock (m_textureSenders) + { + if (m_textureSenders.TryGetValue(e.RequestedAssetID, out textureSender)) + { + // If we've received new non UUID information for this request and it hasn't dispatched + // yet, then update the request accordingly. + textureSender.UpdateRequest(e.DiscardLevel, e.PacketNumber); + } + else + { + // m_log.DebugFormat("[TEXTURE]: Received a request for texture {0}", e.RequestedAssetID); + + if (!foundTextureLimitStrategy.AllowRequest(e.RequestedAssetID)) + { + // m_log.DebugFormat( + // "[TEXTURE]: Refusing request for {0} from client {1}", + // e.RequestedAssetID, m_client.AgentId); + + return; + } + else if (!missingTextureLimitStrategy.AllowRequest(e.RequestedAssetID)) + { + if (missingTextureLimitStrategy.IsFirstRefusal(e.RequestedAssetID)) + { + if (StatsManager.SimExtraStats != null) + StatsManager.SimExtraStats.AddBlockedMissingTextureRequest(); + + // Commenting out this message for now as it causes too much noise with other + // debug messages. + // m_log.DebugFormat( + // "[TEXTURE]: Dropping requests for notified missing texture {0} for client {1} since we have received more than {2} requests", + // e.RequestedAssetID, m_client.AgentId, MAX_ALLOWED_TEXTURE_REQUESTS); + } + + return; + } + + m_scene.AddPendingDownloads(1); + + TextureSender.TextureSender requestHandler = new TextureSender.TextureSender(m_client, e.DiscardLevel, e.PacketNumber); + m_textureSenders.Add(e.RequestedAssetID, requestHandler); + + m_scene.AssetCache.GetAsset(e.RequestedAssetID, TextureCallback, true); + } + } + } + else + { + lock (m_textureSenders) + { + if (m_textureSenders.TryGetValue(e.RequestedAssetID, out textureSender)) + { + textureSender.Cancel = true; + } + } + } + } + + /// + /// The callback for the asset cache when a texture has been retrieved. This method queues the + /// texture sender for processing. + /// + /// + /// + public void TextureCallback(UUID textureID, AssetBase texture) + { + //m_log.DebugFormat("[USER TEXTURE DOWNLOAD SERVICE]: Calling TextureCallback with {0}, texture == null is {1}", textureID, (texture == null ? true : false)); + + // There may still be texture requests pending for a logged out client + if (closed) + return; + + lock (m_textureSenders) + { + TextureSender.TextureSender textureSender; + if (m_textureSenders.TryGetValue(textureID, out textureSender)) + { + // XXX It may be perfectly valid for a texture to have no data... but if we pass + // this on to the TextureSender it will blow up, so just discard for now. + // Needs investigation. + if (texture == null || texture.Data == null) + { + if (!missingTextureLimitStrategy.IsMonitoringRequests(textureID)) + { + missingTextureLimitStrategy.MonitorRequests(textureID); + + // m_log.DebugFormat( + // "[TEXTURE]: Queueing first TextureNotFoundSender for {0}, client {1}", + // textureID, m_client.AgentId); + } + + ITextureSender textureNotFoundSender = new TextureNotFoundSender(m_client, textureID); + EnqueueTextureSender(textureNotFoundSender); + } + else + { + if (!textureSender.ImageLoaded) + { + textureSender.TextureReceived(texture); + EnqueueTextureSender(textureSender); + + foundTextureLimitStrategy.MonitorRequests(textureID); + } + } + + //m_log.InfoFormat("[TEXTURE] Removing texture sender with uuid {0}", textureID); + m_textureSenders.Remove(textureID); + //m_log.InfoFormat("[TEXTURE] Current texture senders in dictionary: {0}", m_textureSenders.Count); + } + else + { + m_log.WarnFormat( + "[TEXTURE]: Got a texture uuid {0} with no sender object to handle it, this shouldn't happen", + textureID); + } + } + } + + /// + /// Place a ready texture sender on the processing queue. + /// + /// + private void EnqueueTextureSender(ITextureSender textureSender) + { + textureSender.Cancel = false; + textureSender.Sending = true; + + if (!m_sharedSendersQueue.Contains(textureSender)) + { + m_sharedSendersQueue.Enqueue(textureSender); + } + } + + /// + /// Close this module. + /// + internal void Close() + { + closed = true; + + lock (m_textureSenders) + { + foreach (TextureSender.TextureSender textureSender in m_textureSenders.Values) + { + textureSender.Cancel = true; + } + + m_textureSenders.Clear(); + } + + // XXX: It might be possible to also remove pending texture requests from the asset cache queues, + // though this might also be more trouble than it's worth. + } + } +} diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs new file mode 100644 index 0000000..df672b8 --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs @@ -0,0 +1,533 @@ +/* + * 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; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Collections.Generic; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.Imaging; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Agent.TextureSender +{ + public class J2KDecoderModule : IRegionModule, IJ2KDecoder + { + #region IRegionModule Members + + private static readonly ILog m_log + = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Cached Decoded Layers + /// + private readonly Dictionary m_cacheddecode = new Dictionary(); + private bool OpenJpegFail = false; + private readonly string CacheFolder = Util.dataDir() + "/j2kDecodeCache"; + private readonly J2KDecodeFileCache fCache; + + /// + /// List of client methods to notify of results of decode + /// + private readonly Dictionary> m_notifyList = new Dictionary>(); + + public J2KDecoderModule() + { + fCache = new J2KDecodeFileCache(CacheFolder); + } + + public void Initialise(Scene scene, IConfigSource source) + { + scene.RegisterModuleInterface(this); + } + + public void PostInitialise() + { + + } + + public void Close() + { + + } + + public string Name + { + get { return "J2KDecoderModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + #region IJ2KDecoder Members + + + public void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn) + { + // Dummy for if decoding fails. + OpenJPEG.J2KLayerInfo[] result = new OpenJPEG.J2KLayerInfo[0]; + + // Check if it's cached + bool cached = false; + lock (m_cacheddecode) + { + if (m_cacheddecode.ContainsKey(AssetId)) + { + cached = true; + result = m_cacheddecode[AssetId]; + } + } + + // If it's cached, return the cached results + if (cached) + { + decodedReturn(AssetId, result); + } + else + { + // not cached, so we need to decode it + // Add to notify list and start decoding. + // Next request for this asset while it's decoding will only be added to the notify list + // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated + bool decode = false; + lock (m_notifyList) + { + if (m_notifyList.ContainsKey(AssetId)) + { + m_notifyList[AssetId].Add(decodedReturn); + } + else + { + List notifylist = new List(); + notifylist.Add(decodedReturn); + m_notifyList.Add(AssetId, notifylist); + decode = true; + } + } + // Do Decode! + if (decode) + { + doJ2kDecode(AssetId, assetData); + } + } + } + + /// + /// Provides a synchronous decode so that caller can be assured that this executes before the next line + /// + /// + /// + public void syncdecode(UUID AssetId, byte[] j2kdata) + { + doJ2kDecode(AssetId, j2kdata); + } + + #endregion + + /// + /// Decode Jpeg2000 Asset Data + /// + /// UUID of Asset + /// Byte Array Asset Data + private void doJ2kDecode(UUID AssetId, byte[] j2kdata) + { + int DecodeTime = 0; + DecodeTime = System.Environment.TickCount; + OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality + + if (!OpenJpegFail) + { + if (!fCache.TryLoadCacheForAsset(AssetId, out layers)) + { + try + { + + AssetTexture texture = new AssetTexture(AssetId, j2kdata); + if (texture.DecodeLayerBoundaries()) + { + bool sane = true; + + // Sanity check all of the layers + for (int i = 0; i < texture.LayerInfo.Length; i++) + { + if (texture.LayerInfo[i].End > texture.AssetData.Length) + { + sane = false; + break; + } + } + + if (sane) + { + layers = texture.LayerInfo; + fCache.SaveFileCacheForAsset(AssetId, layers); + + + // Write out decode time + m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", System.Environment.TickCount - DecodeTime, + AssetId); + + } + else + { + m_log.WarnFormat( + "[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}", + AssetId); + } + } + + else + { + m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}", AssetId); + } + texture = null; // dereference and dispose of ManagedImage + } + catch (DllNotFoundException) + { + m_log.Error( + "[J2KDecoderModule]: OpenJpeg is not installed properly. Decoding disabled! This will slow down texture performance! Often times this is because of an old version of GLIBC. You must have version 2.4 or above!"); + OpenJpegFail = true; + } + catch (Exception ex) + { + m_log.WarnFormat( + "[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}", + AssetId, ex); + } + } + + } + + + // Cache Decoded layers + lock (m_cacheddecode) + { + if (!m_cacheddecode.ContainsKey(AssetId)) + m_cacheddecode.Add(AssetId, layers); + + } + + + + // Notify Interested Parties + lock (m_notifyList) + { + if (m_notifyList.ContainsKey(AssetId)) + { + foreach (DecodedCallback d in m_notifyList[AssetId]) + { + if (d != null) + d.DynamicInvoke(AssetId, layers); + } + m_notifyList.Remove(AssetId); + } + } + } + } + + public class J2KDecodeFileCache + { + private readonly string m_cacheDecodeFolder; + private bool enabled = true; + + private static readonly ILog m_log + = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Creates a new instance of a file cache + /// + /// base folder for the cache. Will be created if it doesn't exist + public J2KDecodeFileCache(string pFolder) + { + m_cacheDecodeFolder = pFolder; + if (!Directory.Exists(pFolder)) + { + Createj2KCacheFolder(pFolder); + } + + } + + /// + /// Save Layers to Disk Cache + /// + /// Asset to Save the layers. Used int he file name by default + /// The Layer Data from OpenJpeg + /// + public bool SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers) + { + if (Layers.Length > 0 && enabled) + { + FileStream fsCache = + new FileStream(String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId)), + FileMode.Create); + StreamWriter fsSWCache = new StreamWriter(fsCache); + StringBuilder stringResult = new StringBuilder(); + string strEnd = "\n"; + for (int i = 0; i < Layers.Length; i++) + { + if (i == (Layers.Length - 1)) + strEnd = ""; + + stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].Size, strEnd); + } + fsSWCache.Write(stringResult.ToString()); + fsSWCache.Close(); + fsSWCache.Dispose(); + fsCache.Dispose(); + return true; + } + + + return false; + } + + + /// + /// Loads the Layer data from the disk cache + /// Returns true if load succeeded + /// + /// AssetId that we're checking the cache for + /// out layers to save to + /// true if load succeeded + public bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers) + { + string filename = String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId)); + Layers = new OpenJPEG.J2KLayerInfo[0]; + + if (!File.Exists(filename)) + return false; + + if (!enabled) + { + return false; + } + + string readResult = string.Empty; + + try + { + FileStream fsCachefile = + new FileStream(filename, + FileMode.Open); + + StreamReader sr = new StreamReader(fsCachefile); + readResult = sr.ReadToEnd(); + + sr.Close(); + sr.Dispose(); + fsCachefile.Dispose(); + + } + catch (IOException ioe) + { + if (ioe is PathTooLongException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Read failed. Path is too long."); + } + else if (ioe is DirectoryNotFoundException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Read failed. Cache Directory does not exist!"); + enabled = false; + } + else + { + m_log.Error( + "[J2KDecodeCache]: Cache Read failed. IO Exception."); + } + return false; + + } + catch (UnauthorizedAccessException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Read failed. UnauthorizedAccessException Exception. Do you have the proper permissions on this file?"); + return false; + } + catch (ArgumentException ae) + { + if (ae is ArgumentNullException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Read failed. No Filename provided"); + } + else + { + m_log.Error( + "[J2KDecodeCache]: Cache Read failed. Filname was invalid"); + } + return false; + } + catch (NotSupportedException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Read failed, not supported. Cache disabled!"); + enabled = false; + + return false; + } + catch (Exception e) + { + m_log.ErrorFormat( + "[J2KDecodeCache]: Cache Read failed, unknown exception. Error: {0}", + e.ToString()); + return false; + } + + string[] lines = readResult.Split('\n'); + + if (lines.Length <= 0) + return false; + + Layers = new OpenJPEG.J2KLayerInfo[lines.Length]; + + for (int i = 0; i < lines.Length; i++) + { + string[] elements = lines[i].Split('|'); + if (elements.Length == 3) + { + int element1, element2; + + try + { + element1 = Convert.ToInt32(elements[0]); + element2 = Convert.ToInt32(elements[1]); + } + catch (FormatException) + { + m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed with ErrorConvert for {0}", AssetId); + Layers = new OpenJPEG.J2KLayerInfo[0]; + return false; + } + + Layers[i] = new OpenJPEG.J2KLayerInfo(); + Layers[i].Start = element1; + Layers[i].End = element2; + + } + else + { + // reading failed + m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed for {0}", AssetId); + Layers = new OpenJPEG.J2KLayerInfo[0]; + return false; + } + } + + + + + return true; + } + + /// + /// Routine which converts assetid to file name + /// + /// asset id of the image + /// string filename + public string FileNameFromAssetId(UUID AssetId) + { + return String.Format("j2kCache_{0}.cache", AssetId); + } + + /// + /// Creates the Cache Folder + /// + /// Folder to Create + public void Createj2KCacheFolder(string pFolder) + { + try + { + Directory.CreateDirectory(pFolder); + } + catch (IOException ioe) + { + if (ioe is PathTooLongException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Directory does not exist and create failed because the path to the cache folder is too long. Cache disabled!"); + } + else if (ioe is DirectoryNotFoundException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Directory does not exist and create failed because the supplied base of the directory folder does not exist. Cache disabled!"); + } + else + { + m_log.Error( + "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an IO Exception. Cache disabled!"); + } + enabled = false; + + } + catch (UnauthorizedAccessException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an UnauthorizedAccessException Exception. Cache disabled!"); + enabled = false; + } + catch (ArgumentException ae) + { + if (ae is ArgumentNullException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Directory does not exist and create failed because the folder provided is invalid! Cache disabled!"); + } + else + { + m_log.Error( + "[J2KDecodeCache]: Cache Directory does not exist and create failed because no cache folder was provided! Cache disabled!"); + } + enabled = false; + } + catch (NotSupportedException) + { + m_log.Error( + "[J2KDecodeCache]: Cache Directory does not exist and create failed because it's not supported. Cache disabled!"); + enabled = false; + } + catch (Exception e) + { + m_log.ErrorFormat( + "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an unknown exception. Cache disabled! Error: {0}", + e.ToString()); + enabled = false; + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/Tests/TextureSenderTests.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/Tests/TextureSenderTests.cs new file mode 100644 index 0000000..f0587bb --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/TextureSender/Tests/TextureSenderTests.cs @@ -0,0 +1,177 @@ +/* + * 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; +using System.Collections; +using NUnit.Framework; +using NUnit.Framework.SyntaxHelpers; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Tests.Common.Mock; + +namespace OpenSim.Region.CoreModules.Agent.TextureSender +{ + [TestFixture] + public class UserTextureSenderTests + { + public UUID uuid1; + public UUID uuid2; + public UUID uuid3; + public UUID uuid4; + public int npackets, testsize; + public TestClient client; + public TextureSender ts; + public static Random random = new Random(); + + [TestFixtureSetUp] + public void Init() + { + AgentCircuitData agent = new AgentCircuitData(); + agent.AgentID = UUID.Random(); + agent.firstname = "testfirstname"; + agent.lastname = "testlastname"; + agent.SessionID = UUID.Zero; + agent.SecureSessionID = UUID.Zero; + agent.circuitcode = 123; + agent.BaseFolder = UUID.Zero; + agent.InventoryFolder = UUID.Zero; + agent.startpos = Vector3.Zero; + agent.CapsPath = "http://wibble.com"; + client = new TestClient(agent, null); + ts = new TextureSender(client, 0, 0); + testsize = random.Next(5000,15000); + npackets = CalculateNumPackets(testsize); + uuid1 = UUID.Random(); + uuid2 = UUID.Random(); + uuid3 = UUID.Random(); + uuid4 = UUID.Random(); + } + + /// + /// Test sending package + /// + [Test] + public void T010_SendPkg() + { + // Normal sending + AssetBase abase = new AssetBase(uuid1, "asset one"); + byte[] abdata = new byte[testsize]; + random.NextBytes(abdata); + abase.Data = abdata; + bool isdone = false; + ts.TextureReceived(abase); + for (int i = 0; i < npackets; i++) { + isdone = ts.SendTexturePacket(); + } + + Assert.That(isdone,Is.False); + isdone = ts.SendTexturePacket(); + Assert.That(isdone,Is.True); + } + + [Test] + public void T011_UpdateReq() + { + // Test packet number start + AssetBase abase = new AssetBase(uuid2, "asset two"); + byte[] abdata = new byte[testsize]; + random.NextBytes(abdata); + abase.Data = abdata; + + bool isdone = false; + ts.TextureReceived(abase); + ts.UpdateRequest(0,3); + + for (int i = 0; i < npackets-3; i++) { + isdone = ts.SendTexturePacket(); + } + + Assert.That(isdone,Is.False); + isdone = ts.SendTexturePacket(); + Assert.That(isdone,Is.True); + + // Test discard level + abase = new AssetBase(uuid3, "asset three"); + abdata = new byte[testsize]; + random.NextBytes(abdata); + abase.Data = abdata; + isdone = false; + ts.TextureReceived(abase); + ts.UpdateRequest(-1,0); + + Assert.That(ts.SendTexturePacket(),Is.True); + + abase = new AssetBase(uuid4, "asset four"); + abdata = new byte[testsize]; + random.NextBytes(abdata); + abase.Data = abdata; + isdone = false; + ts.TextureReceived(abase); + ts.UpdateRequest(0,5); + + for (int i = 0; i < npackets-5; i++) { + isdone = ts.SendTexturePacket(); + } + Assert.That(isdone,Is.False); + isdone = ts.SendTexturePacket(); + Assert.That(isdone,Is.True); + } + + [Test] + public void T999_FinishStatus() + { + // Of the 4 assets "sent", only 2 sent the first part. + Assert.That(client.sentdatapkt.Count,Is.EqualTo(2)); + + // Sum of all packets sent: + int totalpkts = (npackets) + (npackets - 2) + (npackets - 4); + Assert.That(client.sentpktpkt.Count,Is.EqualTo(totalpkts)); + } + + /// + /// Calculate the number of packets that will be required to send the texture loaded into this sender + /// This is actually the number of 1000 byte packets not including an initial 600 byte packet... + /// Borrowed from TextureSender.cs + /// + /// + /// + private int CalculateNumPackets(int length) + { + int numPackets = 0; + + if (length > 600) + { + //over 600 bytes so split up file + int restData = (length - 600); + int restPackets = ((restData + 999) / 1000); + numPackets = restPackets; + } + + return numPackets; + } + } +} diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/TextureSender.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/TextureSender.cs new file mode 100644 index 0000000..b3ac624 --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/TextureSender/TextureSender.cs @@ -0,0 +1,213 @@ +/* + * 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; +using System.Reflection; +using OpenMetaverse.Packets; +using log4net; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.Agent.TextureSender +{ + /// + /// A TextureSender handles the process of receiving a texture requested by the client from the + /// AssetCache, and then sending that texture back to the client. + /// + public class TextureSender : ITextureSender + { + private static readonly ILog m_log + = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Records the number of times texture send has been called. + /// + public int counter = 0; + + public bool ImageLoaded = false; + + /// + /// Holds the texture asset to send. + /// + private AssetBase m_asset; + + //public UUID assetID { get { return m_asset.Metadata.FullID; } } + + // private bool m_cancel = false; + + // See ITextureSender + + // private bool m_sending = false; + + /// + /// This is actually the number of extra packets required to send the texture data! We always assume + /// at least one is required. + /// + private int NumPackets = 0; + + /// + /// Holds the packet number to send next. In this case, each packet is 1000 bytes long and starts + /// at the 600th byte (0th indexed). + /// + private int PacketCounter = 0; + + private int RequestedDiscardLevel = -1; + private IClientAPI RequestUser; + private uint StartPacketNumber = 0; + + public TextureSender(IClientAPI client, int discardLevel, uint packetNumber) + { + RequestUser = client; + RequestedDiscardLevel = discardLevel; + StartPacketNumber = packetNumber; + } + + #region ITextureSender Members + + public bool Cancel + { + get { return false; } + set + { + // m_cancel = value; + } + } + + public bool Sending + { + get { return false; } + set + { + // m_sending = value; + } + } + + // See ITextureSender + public void UpdateRequest(int discardLevel, uint packetNumber) + { + RequestedDiscardLevel = discardLevel; + StartPacketNumber = packetNumber; + PacketCounter = (int)StartPacketNumber; + } + + // See ITextureSender + public bool SendTexturePacket() + { + //m_log.DebugFormat("[TEXTURE SENDER]: Sending packet for {0}", m_asset.Metadata.FullID); + + SendPacket(); + counter++; + if ((NumPackets == 0) || (RequestedDiscardLevel == -1) || (PacketCounter > NumPackets) || + ((RequestedDiscardLevel > 0) && (counter > 50 + (NumPackets / (RequestedDiscardLevel + 1))))) + { + return true; + } + return false; + } + + #endregion + + /// + /// Load up the texture data to send. + /// + /// + public void TextureReceived(AssetBase asset) + { + m_asset = asset; + NumPackets = CalculateNumPackets(asset.Data.Length); + PacketCounter = (int)StartPacketNumber; + ImageLoaded = true; + } + + /// + /// Sends a texture packet to the client. + /// + private void SendPacket() + { + if (PacketCounter <= NumPackets) + { + if (PacketCounter == 0) + { + if (NumPackets == 0) + { + RequestUser.SendImageFirstPart(1, m_asset.Metadata.FullID, (uint)m_asset.Data.Length, m_asset.Data, 2); + PacketCounter++; + } + else + { + byte[] ImageData1 = new byte[600]; + Array.Copy(m_asset.Data, 0, ImageData1, 0, 600); + + RequestUser.SendImageFirstPart( + (ushort)(NumPackets), m_asset.Metadata.FullID, (uint)m_asset.Data.Length, ImageData1, 2); + PacketCounter++; + } + } + else + { + int size = m_asset.Data.Length - 600 - (1000 * (PacketCounter - 1)); + if (size > 1000) size = 1000; + byte[] imageData = new byte[size]; + try + { + Array.Copy(m_asset.Data, 600 + (1000 * (PacketCounter - 1)), imageData, 0, size); + } + catch (ArgumentOutOfRangeException) + { + m_log.Error("[TEXTURE SENDER]: Unable to separate texture into multiple packets: Array bounds failure on asset:" + + m_asset.Metadata.ID); + return; + } + + RequestUser.SendImageNextPart((ushort)PacketCounter, m_asset.Metadata.FullID, imageData); + PacketCounter++; + } + } + } + + /// + /// Calculate the number of packets that will be required to send the texture loaded into this sender + /// This is actually the number of 1000 byte packets not including an initial 600 byte packet... + /// + /// + /// + private int CalculateNumPackets(int length) + { + int numPackets = 0; + + if (length > 600) + { + //over 600 bytes so split up file + int restData = (length - 600); + int restPackets = ((restData + 999) / 1000); + numPackets = restPackets; + } + + return numPackets; + } + } +} diff --git a/OpenSim/Region/CoreModules/Agent/Xfer/XferModule.cs b/OpenSim/Region/CoreModules/Agent/Xfer/XferModule.cs new file mode 100644 index 0000000..cb81c0d --- /dev/null +++ b/OpenSim/Region/CoreModules/Agent/Xfer/XferModule.cs @@ -0,0 +1,232 @@ +/* + * 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; +using System.Collections.Generic; +using OpenMetaverse; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Agent.Xfer +{ + public class XferModule : IRegionModule, IXfer + { + private Scene m_scene; + public Dictionary NewFiles = new Dictionary(); + public Dictionary Transfers = new Dictionary(); + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + m_scene.EventManager.OnNewClient += NewClient; + + m_scene.RegisterModuleInterface(this); + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "XferModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + + #region IXfer Members + + public bool AddNewFile(string fileName, byte[] data) + { + lock (NewFiles) + { + if (NewFiles.ContainsKey(fileName)) + { + NewFiles[fileName] = data; + } + else + { + NewFiles.Add(fileName, data); + } + } + return true; + } + + #endregion + + public void NewClient(IClientAPI client) + { + client.OnRequestXfer += RequestXfer; + client.OnConfirmXfer += AckPacket; + } + + /// + /// + /// + /// + /// + /// + public void RequestXfer(IClientAPI remoteClient, ulong xferID, string fileName) + { + + lock (NewFiles) + { + if (NewFiles.ContainsKey(fileName)) + { + if (!Transfers.ContainsKey(xferID)) + { + byte[] fileData = NewFiles[fileName]; + XferDownLoad transaction = new XferDownLoad(fileName, fileData, xferID, remoteClient); + Transfers.Add(xferID, transaction); + NewFiles.Remove(fileName); + + if (transaction.StartSend()) + { + Transfers.Remove(xferID); + } + } + } + } + } + + public void AckPacket(IClientAPI remoteClient, ulong xferID, uint packet) + { + if (Transfers.ContainsKey(xferID)) + { + if (Transfers[xferID].AckPacket(packet)) + { + { + Transfers.Remove(xferID); + } + } + } + } + + #region Nested type: XferDownLoad + + public class XferDownLoad + { + public IClientAPI Client; + private bool complete; + public byte[] Data = new byte[0]; + public int DataPointer = 0; + public string FileName = String.Empty; + public uint Packet = 0; + public uint Serial = 1; + public ulong XferID = 0; + + public XferDownLoad(string fileName, byte[] data, ulong xferID, IClientAPI client) + { + FileName = fileName; + Data = data; + XferID = xferID; + Client = client; + } + + public XferDownLoad() + { + } + + /// + /// Start a transfer + /// + /// True if the transfer is complete, false if not + public bool StartSend() + { + if (Data.Length < 1000) + { + // for now (testing) we only support files under 1000 bytes + byte[] transferData = new byte[Data.Length + 4]; + Array.Copy(Utils.IntToBytes(Data.Length), 0, transferData, 0, 4); + Array.Copy(Data, 0, transferData, 4, Data.Length); + Client.SendXferPacket(XferID, 0 + 0x80000000, transferData); + + complete = true; + } + else + { + byte[] transferData = new byte[1000 + 4]; + Array.Copy(Utils.IntToBytes(Data.Length), 0, transferData, 0, 4); + Array.Copy(Data, 0, transferData, 4, 1000); + Client.SendXferPacket(XferID, 0, transferData); + Packet++; + DataPointer = 1000; + } + + return complete; + } + + /// + /// Respond to an ack packet from the client + /// + /// + /// True if the transfer is complete, false otherwise + public bool AckPacket(uint packet) + { + if (!complete) + { + if ((Data.Length - DataPointer) > 1000) + { + byte[] transferData = new byte[1000]; + Array.Copy(Data, DataPointer, transferData, 0, 1000); + Client.SendXferPacket(XferID, Packet, transferData); + Packet++; + DataPointer += 1000; + } + else + { + byte[] transferData = new byte[Data.Length - DataPointer]; + Array.Copy(Data, DataPointer, transferData, 0, Data.Length - DataPointer); + uint endPacket = Packet |= (uint) 0x80000000; + Client.SendXferPacket(XferID, endPacket, transferData); + Packet++; + DataPointer += (Data.Length - DataPointer); + + complete = true; + } + } + + return complete; + } + } + + #endregion + } +} -- cgit v1.1