J2KImage.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections.Generic;
  29. using OpenMetaverse;
  30. using OpenMetaverse.Imaging;
  31. using OpenSim.Framework;
  32. using OpenSim.Region.Framework.Interfaces;
  33. using OpenSim.Services.Interfaces;
  34. using log4net;
  35. using System.Reflection;
  36. namespace OpenSim.Region.ClientStack.LindenUDP
  37. {
  38. /// <summary>
  39. /// Stores information about a current texture download and a reference to the texture asset
  40. /// </summary>
  41. public class J2KImage
  42. {
  43. private const int IMAGE_PACKET_SIZE = 1000;
  44. private const int FIRST_PACKET_SIZE = 600;
  45. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  46. public uint LastSequence;
  47. public float Priority;
  48. public uint StartPacket;
  49. public sbyte DiscardLevel;
  50. public UUID TextureID;
  51. public IJ2KDecoder J2KDecoder;
  52. public IAssetService AssetService;
  53. public UUID AgentID;
  54. public IInventoryAccessModule InventoryAccessModule;
  55. public OpenJPEG.J2KLayerInfo[] Layers;
  56. public bool IsDecoded;
  57. public bool HasAsset;
  58. public C5.IPriorityQueueHandle<J2KImage> PriorityQueueHandle;
  59. private uint m_currentPacket;
  60. private bool m_decodeRequested;
  61. private bool m_assetRequested;
  62. private bool m_sentInfo;
  63. private uint m_stopPacket;
  64. private byte[] m_asset;
  65. private LLImageManager m_imageManager;
  66. public J2KImage(LLImageManager imageManager)
  67. {
  68. m_imageManager = imageManager;
  69. }
  70. /// <summary>
  71. /// Sends packets for this texture to a client until packetsToSend is
  72. /// hit or the transfer completes
  73. /// </summary>
  74. /// <param name="client">Reference to the client that the packets are destined for</param>
  75. /// <param name="packetsToSend">Maximum number of packets to send during this call</param>
  76. /// <param name="packetsSent">Number of packets sent during this call</param>
  77. /// <returns>True if the transfer completes at the current discard level, otherwise false</returns>
  78. public bool SendPackets(LLClientView client, int packetsToSend, out int packetsSent)
  79. {
  80. packetsSent = 0;
  81. if (m_currentPacket <= m_stopPacket)
  82. {
  83. bool sendMore = true;
  84. if (!m_sentInfo || (m_currentPacket == 0))
  85. {
  86. sendMore = !SendFirstPacket(client);
  87. m_sentInfo = true;
  88. ++m_currentPacket;
  89. ++packetsSent;
  90. }
  91. if (m_currentPacket < 2)
  92. {
  93. m_currentPacket = 2;
  94. }
  95. while (sendMore && packetsSent < packetsToSend && m_currentPacket <= m_stopPacket)
  96. {
  97. sendMore = SendPacket(client);
  98. ++m_currentPacket;
  99. ++packetsSent;
  100. }
  101. }
  102. return (m_currentPacket > m_stopPacket);
  103. }
  104. public void RunUpdate()
  105. {
  106. //This is where we decide what we need to update
  107. //and assign the real discardLevel and packetNumber
  108. //assuming of course that the connected client might be bonkers
  109. if (!HasAsset)
  110. {
  111. if (!m_assetRequested)
  112. {
  113. m_assetRequested = true;
  114. AssetService.Get(TextureID.ToString(), this, AssetReceived);
  115. }
  116. }
  117. else
  118. {
  119. if (!IsDecoded)
  120. {
  121. //We need to decode the requested image first
  122. if (!m_decodeRequested)
  123. {
  124. //Request decode
  125. m_decodeRequested = true;
  126. // Do we have a jpeg decoder?
  127. if (J2KDecoder != null)
  128. {
  129. if (m_asset == null)
  130. {
  131. J2KDecodedCallback(TextureID, new OpenJPEG.J2KLayerInfo[0]);
  132. }
  133. else
  134. {
  135. // Send it off to the jpeg decoder
  136. J2KDecoder.BeginDecode(TextureID, m_asset, J2KDecodedCallback);
  137. }
  138. }
  139. else
  140. {
  141. J2KDecodedCallback(TextureID, new OpenJPEG.J2KLayerInfo[0]);
  142. }
  143. }
  144. }
  145. else
  146. {
  147. // Check for missing image asset data
  148. if (m_asset == null)
  149. {
  150. m_log.Warn("[J2KIMAGE]: RunUpdate() called with missing asset data (no missing image texture?). Canceling texture transfer");
  151. m_currentPacket = m_stopPacket;
  152. return;
  153. }
  154. if (DiscardLevel >= 0 || m_stopPacket == 0)
  155. {
  156. // This shouldn't happen, but if it does, we really can't proceed
  157. if (Layers == null)
  158. {
  159. m_log.Warn("[J2KIMAGE]: RunUpdate() called with missing Layers. Canceling texture transfer");
  160. m_currentPacket = m_stopPacket;
  161. return;
  162. }
  163. int maxDiscardLevel = Math.Max(0, Layers.Length - 1);
  164. // Treat initial texture downloads with a DiscardLevel of -1 a request for the highest DiscardLevel
  165. if (DiscardLevel < 0 && m_stopPacket == 0)
  166. DiscardLevel = (sbyte)maxDiscardLevel;
  167. // Clamp at the highest discard level
  168. DiscardLevel = (sbyte)Math.Min(DiscardLevel, maxDiscardLevel);
  169. //Calculate the m_stopPacket
  170. if (Layers.Length > 0)
  171. {
  172. m_stopPacket = (uint)GetPacketForBytePosition(Layers[(Layers.Length - 1) - DiscardLevel].End);
  173. //I don't know why, but the viewer seems to expect the final packet if the file
  174. //is just one packet bigger.
  175. if (TexturePacketCount() == m_stopPacket + 1)
  176. {
  177. m_stopPacket = TexturePacketCount();
  178. }
  179. }
  180. else
  181. {
  182. m_stopPacket = TexturePacketCount();
  183. }
  184. m_currentPacket = StartPacket;
  185. }
  186. }
  187. }
  188. }
  189. private bool SendFirstPacket(LLClientView client)
  190. {
  191. if (client == null)
  192. return false;
  193. if (m_asset == null)
  194. {
  195. m_log.Warn("[J2KIMAGE]: Sending ImageNotInDatabase for texture " + TextureID);
  196. client.SendImageNotFound(TextureID);
  197. return true;
  198. }
  199. else if (m_asset.Length <= FIRST_PACKET_SIZE)
  200. {
  201. // We have less then one packet's worth of data
  202. client.SendImageFirstPart(1, TextureID, (uint)m_asset.Length, m_asset, 2);
  203. m_stopPacket = 0;
  204. return true;
  205. }
  206. else
  207. {
  208. // This is going to be a multi-packet texture download
  209. byte[] firstImageData = new byte[FIRST_PACKET_SIZE];
  210. try { Buffer.BlockCopy(m_asset, 0, firstImageData, 0, FIRST_PACKET_SIZE); }
  211. catch (Exception)
  212. {
  213. m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}", TextureID, m_asset.Length);
  214. return true;
  215. }
  216. client.SendImageFirstPart(TexturePacketCount(), TextureID, (uint)m_asset.Length, firstImageData, (byte)ImageCodec.J2C);
  217. }
  218. return false;
  219. }
  220. private bool SendPacket(LLClientView client)
  221. {
  222. if (client == null)
  223. return false;
  224. bool complete = false;
  225. int imagePacketSize = ((int)m_currentPacket == (TexturePacketCount())) ? LastPacketSize() : IMAGE_PACKET_SIZE;
  226. try
  227. {
  228. if ((CurrentBytePosition() + IMAGE_PACKET_SIZE) > m_asset.Length)
  229. {
  230. imagePacketSize = LastPacketSize();
  231. complete = true;
  232. if ((CurrentBytePosition() + imagePacketSize) > m_asset.Length)
  233. {
  234. imagePacketSize = m_asset.Length - CurrentBytePosition();
  235. complete = true;
  236. }
  237. }
  238. // It's concievable that the client might request packet one
  239. // from a one packet image, which is really packet 0,
  240. // which would leave us with a negative imagePacketSize..
  241. if (imagePacketSize > 0)
  242. {
  243. byte[] imageData = new byte[imagePacketSize];
  244. int currentPosition = CurrentBytePosition();
  245. try { Buffer.BlockCopy(m_asset, currentPosition, imageData, 0, imagePacketSize); }
  246. catch (Exception e)
  247. {
  248. m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}, currentposition={2}, imagepacketsize={3}, exception={4}",
  249. TextureID, m_asset.Length, currentPosition, imagePacketSize, e.Message);
  250. return false;
  251. }
  252. //Send the packet
  253. client.SendImageNextPart((ushort)(m_currentPacket - 1), TextureID, imageData);
  254. }
  255. return !complete;
  256. }
  257. catch (Exception)
  258. {
  259. return false;
  260. }
  261. }
  262. private ushort TexturePacketCount()
  263. {
  264. if (!IsDecoded)
  265. return 0;
  266. if (m_asset == null)
  267. return 0;
  268. if (m_asset.Length <= FIRST_PACKET_SIZE)
  269. return 1;
  270. return (ushort)(((m_asset.Length - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1);
  271. }
  272. private int GetPacketForBytePosition(int bytePosition)
  273. {
  274. return ((bytePosition - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1;
  275. }
  276. private int LastPacketSize()
  277. {
  278. if (m_currentPacket == 1)
  279. return m_asset.Length;
  280. int lastsize = (m_asset.Length - FIRST_PACKET_SIZE) % IMAGE_PACKET_SIZE;
  281. //If the last packet size is zero, it's really cImagePacketSize, it sits on the boundary
  282. if (lastsize == 0)
  283. {
  284. lastsize = IMAGE_PACKET_SIZE;
  285. }
  286. return lastsize;
  287. }
  288. private int CurrentBytePosition()
  289. {
  290. if (m_currentPacket == 0)
  291. return 0;
  292. if (m_currentPacket == 1)
  293. return FIRST_PACKET_SIZE;
  294. int result = FIRST_PACKET_SIZE + ((int)m_currentPacket - 2) * IMAGE_PACKET_SIZE;
  295. if (result < 0)
  296. {
  297. result = FIRST_PACKET_SIZE;
  298. }
  299. return result;
  300. }
  301. private void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers)
  302. {
  303. Layers = layers;
  304. IsDecoded = true;
  305. RunUpdate();
  306. }
  307. private void AssetDataCallback(UUID AssetID, AssetBase asset)
  308. {
  309. HasAsset = true;
  310. if (asset == null || asset.Data == null)
  311. {
  312. if (m_imageManager.MissingImage != null)
  313. {
  314. m_asset = m_imageManager.MissingImage.Data;
  315. }
  316. else
  317. {
  318. m_asset = null;
  319. IsDecoded = true;
  320. }
  321. }
  322. else
  323. {
  324. m_asset = asset.Data;
  325. }
  326. RunUpdate();
  327. }
  328. private void AssetReceived(string id, Object sender, AssetBase asset)
  329. {
  330. UUID assetID = UUID.Zero;
  331. if (asset != null)
  332. assetID = asset.FullID;
  333. else if ((InventoryAccessModule != null) && (sender != InventoryAccessModule))
  334. {
  335. // Unfortunately we need this here, there's no other way.
  336. // This is due to the fact that textures opened directly from the agent's inventory
  337. // don't have any distinguishing feature. As such, in order to serve those when the
  338. // foreign user is visiting, we need to try again after the first fail to the local
  339. // asset service.
  340. string assetServerURL = string.Empty;
  341. if (InventoryAccessModule.IsForeignUser(AgentID, out assetServerURL))
  342. {
  343. m_log.DebugFormat("[J2KIMAGE]: texture {0} not found in local asset storage. Trying user's storage.", id);
  344. AssetService.Get(assetServerURL + "/" + id, InventoryAccessModule, AssetReceived);
  345. return;
  346. }
  347. }
  348. AssetDataCallback(assetID, asset);
  349. }
  350. }
  351. }