J2KImage.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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. /// <summary>
  46. /// If we've requested an asset but not received it in this ticks timeframe, then allow a duplicate
  47. /// request from the client to trigger a fresh asset request.
  48. /// </summary>
  49. /// <remarks>
  50. /// There are 10,000 ticks in a millisecond
  51. /// </remarks>
  52. private const int ASSET_REQUEST_TIMEOUT = 100000000;
  53. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  54. public uint LastSequence;
  55. public float Priority;
  56. public uint StartPacket;
  57. public sbyte DiscardLevel;
  58. public UUID TextureID;
  59. public IJ2KDecoder J2KDecoder;
  60. public IAssetService AssetService;
  61. public UUID AgentID;
  62. public IInventoryAccessModule InventoryAccessModule;
  63. private OpenJPEG.J2KLayerInfo[] m_layers;
  64. /// <summary>
  65. /// Has this request decoded the asset data?
  66. /// </summary>
  67. public bool IsDecoded { get; private set; }
  68. /// <summary>
  69. /// Has this request received the required asset data?
  70. /// </summary>
  71. public bool HasAsset { get; private set; }
  72. /// <summary>
  73. /// Time in milliseconds at which the asset was requested.
  74. /// </summary>
  75. public long AssetRequestTime { get; private set; }
  76. public C5.IPriorityQueueHandle<J2KImage> PriorityQueueHandle;
  77. private uint m_currentPacket;
  78. private bool m_decodeRequested;
  79. private bool m_assetRequested;
  80. private bool m_sentInfo;
  81. private uint m_stopPacket;
  82. private byte[] m_asset;
  83. private LLImageManager m_imageManager;
  84. public J2KImage(LLImageManager imageManager)
  85. {
  86. m_imageManager = imageManager;
  87. }
  88. /// <summary>
  89. /// Sends packets for this texture to a client until packetsToSend is
  90. /// hit or the transfer completes
  91. /// </summary>
  92. /// <param name="client">Reference to the client that the packets are destined for</param>
  93. /// <param name="packetsToSend">Maximum number of packets to send during this call</param>
  94. /// <param name="packetsSent">Number of packets sent during this call</param>
  95. /// <returns>True if the transfer completes at the current discard level, otherwise false</returns>
  96. public bool SendPackets(IClientAPI client, int packetsToSend, out int packetsSent)
  97. {
  98. packetsSent = 0;
  99. if (m_currentPacket <= m_stopPacket)
  100. {
  101. bool sendMore = true;
  102. if (!m_sentInfo || (m_currentPacket == 0))
  103. {
  104. sendMore = !SendFirstPacket(client);
  105. m_sentInfo = true;
  106. ++m_currentPacket;
  107. ++packetsSent;
  108. }
  109. if (m_currentPacket < 2)
  110. {
  111. m_currentPacket = 2;
  112. }
  113. while (sendMore && packetsSent < packetsToSend && m_currentPacket <= m_stopPacket)
  114. {
  115. sendMore = SendPacket(client);
  116. ++m_currentPacket;
  117. ++packetsSent;
  118. }
  119. }
  120. return (m_currentPacket > m_stopPacket);
  121. }
  122. /// <summary>
  123. /// This is where we decide what we need to update
  124. /// and assign the real discardLevel and packetNumber
  125. /// assuming of course that the connected client might be bonkers
  126. /// </summary>
  127. public void RunUpdate()
  128. {
  129. if (!HasAsset)
  130. {
  131. if (!m_assetRequested || DateTime.UtcNow.Ticks > AssetRequestTime + ASSET_REQUEST_TIMEOUT)
  132. {
  133. // m_log.DebugFormat(
  134. // "[J2KIMAGE]: Requesting asset {0} from request in packet {1}, already requested? {2}, due to timeout? {3}",
  135. // TextureID, LastSequence, m_assetRequested, DateTime.UtcNow.Ticks > AssetRequestTime + ASSET_REQUEST_TIMEOUT);
  136. m_assetRequested = true;
  137. AssetRequestTime = DateTime.UtcNow.Ticks;
  138. AssetService.Get(TextureID.ToString(), this, AssetReceived);
  139. }
  140. }
  141. else
  142. {
  143. if (!IsDecoded)
  144. {
  145. //We need to decode the requested image first
  146. if (!m_decodeRequested)
  147. {
  148. //Request decode
  149. m_decodeRequested = true;
  150. // m_log.DebugFormat("[J2KIMAGE]: Requesting decode of asset {0}", TextureID);
  151. // Do we have a jpeg decoder?
  152. if (J2KDecoder != null)
  153. {
  154. if (m_asset == null)
  155. {
  156. J2KDecodedCallback(TextureID, new OpenJPEG.J2KLayerInfo[0]);
  157. }
  158. else
  159. {
  160. // Send it off to the jpeg decoder
  161. J2KDecoder.BeginDecode(TextureID, m_asset, J2KDecodedCallback);
  162. }
  163. }
  164. else
  165. {
  166. J2KDecodedCallback(TextureID, new OpenJPEG.J2KLayerInfo[0]);
  167. }
  168. }
  169. }
  170. else
  171. {
  172. // Check for missing image asset data
  173. if (m_asset == null)
  174. {
  175. m_log.Warn("[J2KIMAGE]: RunUpdate() called with missing asset data (no missing image texture?). Canceling texture transfer");
  176. m_currentPacket = m_stopPacket;
  177. return;
  178. }
  179. if (DiscardLevel >= 0 || m_stopPacket == 0)
  180. {
  181. // This shouldn't happen, but if it does, we really can't proceed
  182. if (m_layers == null)
  183. {
  184. m_log.Warn("[J2KIMAGE]: RunUpdate() called with missing Layers. Canceling texture transfer");
  185. m_currentPacket = m_stopPacket;
  186. return;
  187. }
  188. int maxDiscardLevel = Math.Max(0, m_layers.Length - 1);
  189. // Treat initial texture downloads with a DiscardLevel of -1 a request for the highest DiscardLevel
  190. if (DiscardLevel < 0 && m_stopPacket == 0)
  191. DiscardLevel = (sbyte)maxDiscardLevel;
  192. // Clamp at the highest discard level
  193. DiscardLevel = (sbyte)Math.Min(DiscardLevel, maxDiscardLevel);
  194. //Calculate the m_stopPacket
  195. if (m_layers.Length > 0)
  196. {
  197. m_stopPacket = (uint)GetPacketForBytePosition(m_layers[(m_layers.Length - 1) - DiscardLevel].End);
  198. //I don't know why, but the viewer seems to expect the final packet if the file
  199. //is just one packet bigger.
  200. if (TexturePacketCount() == m_stopPacket + 1)
  201. {
  202. m_stopPacket = TexturePacketCount();
  203. }
  204. }
  205. else
  206. {
  207. m_stopPacket = TexturePacketCount();
  208. }
  209. m_currentPacket = StartPacket;
  210. }
  211. }
  212. }
  213. }
  214. private bool SendFirstPacket(IClientAPI client)
  215. {
  216. if (client == null)
  217. return false;
  218. if (m_asset == null)
  219. {
  220. m_log.Warn("[J2KIMAGE]: Sending ImageNotInDatabase for texture " + TextureID);
  221. client.SendImageNotFound(TextureID);
  222. return true;
  223. }
  224. else if (m_asset.Length <= FIRST_PACKET_SIZE)
  225. {
  226. // We have less then one packet's worth of data
  227. client.SendImageFirstPart(1, TextureID, (uint)m_asset.Length, m_asset, 2);
  228. m_stopPacket = 0;
  229. return true;
  230. }
  231. else
  232. {
  233. // This is going to be a multi-packet texture download
  234. byte[] firstImageData = new byte[FIRST_PACKET_SIZE];
  235. try { Buffer.BlockCopy(m_asset, 0, firstImageData, 0, FIRST_PACKET_SIZE); }
  236. catch (Exception)
  237. {
  238. m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}", TextureID, m_asset.Length);
  239. return true;
  240. }
  241. client.SendImageFirstPart(TexturePacketCount(), TextureID, (uint)m_asset.Length, firstImageData, (byte)ImageCodec.J2C);
  242. }
  243. return false;
  244. }
  245. private bool SendPacket(IClientAPI client)
  246. {
  247. if (client == null)
  248. return false;
  249. bool complete = false;
  250. int imagePacketSize = ((int)m_currentPacket == (TexturePacketCount())) ? LastPacketSize() : IMAGE_PACKET_SIZE;
  251. try
  252. {
  253. if ((CurrentBytePosition() + IMAGE_PACKET_SIZE) > m_asset.Length)
  254. {
  255. imagePacketSize = LastPacketSize();
  256. complete = true;
  257. if ((CurrentBytePosition() + imagePacketSize) > m_asset.Length)
  258. {
  259. imagePacketSize = m_asset.Length - CurrentBytePosition();
  260. complete = true;
  261. }
  262. }
  263. // It's concievable that the client might request packet one
  264. // from a one packet image, which is really packet 0,
  265. // which would leave us with a negative imagePacketSize..
  266. if (imagePacketSize > 0)
  267. {
  268. byte[] imageData = new byte[imagePacketSize];
  269. int currentPosition = CurrentBytePosition();
  270. try { Buffer.BlockCopy(m_asset, currentPosition, imageData, 0, imagePacketSize); }
  271. catch (Exception e)
  272. {
  273. m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}, currentposition={2}, imagepacketsize={3}, exception={4}",
  274. TextureID, m_asset.Length, currentPosition, imagePacketSize, e.Message);
  275. return false;
  276. }
  277. //Send the packet
  278. client.SendImageNextPart((ushort)(m_currentPacket - 1), TextureID, imageData);
  279. }
  280. return !complete;
  281. }
  282. catch (Exception)
  283. {
  284. return false;
  285. }
  286. }
  287. private ushort TexturePacketCount()
  288. {
  289. if (!IsDecoded)
  290. return 0;
  291. if (m_asset == null)
  292. return 0;
  293. if (m_asset.Length <= FIRST_PACKET_SIZE)
  294. return 1;
  295. return (ushort)(((m_asset.Length - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1);
  296. }
  297. private int GetPacketForBytePosition(int bytePosition)
  298. {
  299. return ((bytePosition - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1;
  300. }
  301. private int LastPacketSize()
  302. {
  303. if (m_currentPacket == 1)
  304. return m_asset.Length;
  305. int lastsize = (m_asset.Length - FIRST_PACKET_SIZE) % IMAGE_PACKET_SIZE;
  306. //If the last packet size is zero, it's really cImagePacketSize, it sits on the boundary
  307. if (lastsize == 0)
  308. {
  309. lastsize = IMAGE_PACKET_SIZE;
  310. }
  311. return lastsize;
  312. }
  313. private int CurrentBytePosition()
  314. {
  315. if (m_currentPacket == 0)
  316. return 0;
  317. if (m_currentPacket == 1)
  318. return FIRST_PACKET_SIZE;
  319. int result = FIRST_PACKET_SIZE + ((int)m_currentPacket - 2) * IMAGE_PACKET_SIZE;
  320. if (result < 0)
  321. result = FIRST_PACKET_SIZE;
  322. return result;
  323. }
  324. private void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers)
  325. {
  326. m_layers = layers;
  327. IsDecoded = true;
  328. RunUpdate();
  329. }
  330. private void AssetDataCallback(UUID AssetID, AssetBase asset)
  331. {
  332. HasAsset = true;
  333. if (asset == null || asset.Data == null)
  334. {
  335. if (m_imageManager.MissingImage != null)
  336. {
  337. m_asset = m_imageManager.MissingImage.Data;
  338. }
  339. else
  340. {
  341. m_asset = null;
  342. IsDecoded = true;
  343. }
  344. }
  345. else
  346. {
  347. m_asset = asset.Data;
  348. }
  349. RunUpdate();
  350. }
  351. private void AssetReceived(string id, Object sender, AssetBase asset)
  352. {
  353. // m_log.DebugFormat(
  354. // "[J2KIMAGE]: Received asset {0} ({1} bytes)", id, asset != null ? asset.Data.Length.ToString() : "n/a");
  355. UUID assetID = UUID.Zero;
  356. if (asset != null)
  357. {
  358. assetID = asset.FullID;
  359. }
  360. else if ((InventoryAccessModule != null) && (sender != InventoryAccessModule))
  361. {
  362. // Unfortunately we need this here, there's no other way.
  363. // This is due to the fact that textures opened directly from the agent's inventory
  364. // don't have any distinguishing feature. As such, in order to serve those when the
  365. // foreign user is visiting, we need to try again after the first fail to the local
  366. // asset service.
  367. string assetServerURL = string.Empty;
  368. if (InventoryAccessModule.IsForeignUser(AgentID, out assetServerURL))
  369. {
  370. if (!assetServerURL.EndsWith("/") && !assetServerURL.EndsWith("="))
  371. assetServerURL = assetServerURL + "/";
  372. m_log.DebugFormat("[J2KIMAGE]: texture {0} not found in local asset storage. Trying user's storage.", assetServerURL + id);
  373. AssetService.Get(assetServerURL + id, InventoryAccessModule, AssetReceived);
  374. return;
  375. }
  376. }
  377. AssetDataCallback(assetID, asset);
  378. }
  379. }
  380. }