OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
J2KImage.cs
Go to the documentation of this file.
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 
28 using System;
29 using System.Collections.Generic;
30 using OpenMetaverse;
31 using OpenMetaverse.Imaging;
32 using OpenSim.Framework;
33 using OpenSim.Region.Framework.Interfaces;
34 using OpenSim.Services.Interfaces;
35 using log4net;
36 using System.Reflection;
37 
38 namespace OpenSim.Region.ClientStack.LindenUDP
39 {
43  public class J2KImage
44  {
45  private const int IMAGE_PACKET_SIZE = 1000;
46  private const int FIRST_PACKET_SIZE = 600;
47 
55  private const int ASSET_REQUEST_TIMEOUT = 100000000;
56 
57  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
58 
59  public uint LastSequence;
60  public float Priority;
61  public uint StartPacket;
62  public sbyte DiscardLevel;
63  public UUID TextureID;
66  public UUID AgentID;
68  private OpenJPEG.J2KLayerInfo[] m_layers;
69 
73  public bool IsDecoded { get; private set; }
74 
78  public bool HasAsset { get; private set; }
79 
83  public long AssetRequestTime { get; private set; }
84 
85  public C5.IPriorityQueueHandle<J2KImage> PriorityQueueHandle;
86 
87  private uint m_currentPacket;
88  private bool m_decodeRequested;
89  private bool m_assetRequested;
90  private bool m_sentInfo;
91  private uint m_stopPacket;
92  private byte[] m_asset;
93  private LLImageManager m_imageManager;
94 
95  public J2KImage(LLImageManager imageManager)
96  {
97  m_imageManager = imageManager;
98  }
99 
108  public bool SendPackets(IClientAPI client, int packetsToSend, out int packetsSent)
109  {
110  packetsSent = 0;
111 
112  if (m_currentPacket <= m_stopPacket)
113  {
114  bool sendMore = true;
115 
116  if (!m_sentInfo || (m_currentPacket == 0))
117  {
118  sendMore = !SendFirstPacket(client);
119 
120  m_sentInfo = true;
121  ++m_currentPacket;
122  ++packetsSent;
123  }
124  if (m_currentPacket < 2)
125  {
126  m_currentPacket = 2;
127  }
128 
129  while (sendMore && packetsSent < packetsToSend && m_currentPacket <= m_stopPacket)
130  {
131  sendMore = SendPacket(client);
132  ++m_currentPacket;
133  ++packetsSent;
134  }
135  }
136 
137  return (m_currentPacket > m_stopPacket);
138  }
139 
145  public void RunUpdate()
146  {
147  if (!HasAsset)
148  {
149  if (!m_assetRequested || DateTime.UtcNow.Ticks > AssetRequestTime + ASSET_REQUEST_TIMEOUT)
150  {
151 // m_log.DebugFormat(
152 // "[J2KIMAGE]: Requesting asset {0} from request in packet {1}, already requested? {2}, due to timeout? {3}",
153 // TextureID, LastSequence, m_assetRequested, DateTime.UtcNow.Ticks > AssetRequestTime + ASSET_REQUEST_TIMEOUT);
154 
155  m_assetRequested = true;
156  AssetRequestTime = DateTime.UtcNow.Ticks;
157 
158  AssetService.Get(TextureID.ToString(), this, AssetReceived);
159  }
160  }
161  else
162  {
163  if (!IsDecoded)
164  {
165  //We need to decode the requested image first
166  if (!m_decodeRequested)
167  {
168  //Request decode
169  m_decodeRequested = true;
170 
171 // m_log.DebugFormat("[J2KIMAGE]: Requesting decode of asset {0}", TextureID);
172 
173  // Do we have a jpeg decoder?
174  if (J2KDecoder != null)
175  {
176  if (m_asset == null)
177  {
178  J2KDecodedCallback(TextureID, new OpenJPEG.J2KLayerInfo[0]);
179  }
180  else
181  {
182  // Send it off to the jpeg decoder
183  J2KDecoder.BeginDecode(TextureID, m_asset, J2KDecodedCallback);
184  }
185  }
186  else
187  {
188  J2KDecodedCallback(TextureID, new OpenJPEG.J2KLayerInfo[0]);
189  }
190  }
191  }
192  else
193  {
194  // Check for missing image asset data
195  if (m_asset == null)
196  {
197  m_log.Warn("[J2KIMAGE]: RunUpdate() called with missing asset data (no missing image texture?). Canceling texture transfer");
198  m_currentPacket = m_stopPacket;
199  return;
200  }
201 
202  if (DiscardLevel >= 0 || m_stopPacket == 0)
203  {
204  // This shouldn't happen, but if it does, we really can't proceed
205  if (m_layers == null)
206  {
207  m_log.Warn("[J2KIMAGE]: RunUpdate() called with missing Layers. Canceling texture transfer");
208  m_currentPacket = m_stopPacket;
209  return;
210  }
211 
212  int maxDiscardLevel = Math.Max(0, m_layers.Length - 1);
213 
214  // Treat initial texture downloads with a DiscardLevel of -1 a request for the highest DiscardLevel
215  if (DiscardLevel < 0 && m_stopPacket == 0)
216  DiscardLevel = (sbyte)maxDiscardLevel;
217 
218  // Clamp at the highest discard level
219  DiscardLevel = (sbyte)Math.Min(DiscardLevel, maxDiscardLevel);
220 
221  //Calculate the m_stopPacket
222  if (m_layers.Length > 0)
223  {
224  m_stopPacket = (uint)GetPacketForBytePosition(m_layers[(m_layers.Length - 1) - DiscardLevel].End);
225  //I don't know why, but the viewer seems to expect the final packet if the file
226  //is just one packet bigger.
227  if (TexturePacketCount() == m_stopPacket + 1)
228  {
229  m_stopPacket = TexturePacketCount();
230  }
231  }
232  else
233  {
234  m_stopPacket = TexturePacketCount();
235  }
236 
237  //Give them at least two packets, to play nice with some broken viewers (SL also behaves this way)
238  if (m_stopPacket == 1 && m_layers[0].End > FIRST_PACKET_SIZE) m_stopPacket++;
239 
240  m_currentPacket = StartPacket;
241  }
242  }
243  }
244  }
245 
246  private bool SendFirstPacket(IClientAPI client)
247  {
248  if (client == null)
249  return false;
250 
251  if (m_asset == null)
252  {
253  m_log.Warn("[J2KIMAGE]: Sending ImageNotInDatabase for texture " + TextureID);
254  client.SendImageNotFound(TextureID);
255  return true;
256  }
257  else if (m_asset.Length <= FIRST_PACKET_SIZE)
258  {
259  // We have less then one packet's worth of data
260  client.SendImageFirstPart(1, TextureID, (uint)m_asset.Length, m_asset, 2);
261  m_stopPacket = 0;
262  return true;
263  }
264  else
265  {
266  // This is going to be a multi-packet texture download
267  byte[] firstImageData = new byte[FIRST_PACKET_SIZE];
268 
269  try { Buffer.BlockCopy(m_asset, 0, firstImageData, 0, FIRST_PACKET_SIZE); }
270  catch (Exception)
271  {
272  m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}", TextureID, m_asset.Length);
273  return true;
274  }
275 
276  client.SendImageFirstPart(TexturePacketCount(), TextureID, (uint)m_asset.Length, firstImageData, (byte)ImageCodec.J2C);
277  }
278  return false;
279  }
280 
281  private bool SendPacket(IClientAPI client)
282  {
283  if (client == null)
284  return false;
285 
286  bool complete = false;
287  int imagePacketSize = ((int)m_currentPacket == (TexturePacketCount())) ? LastPacketSize() : IMAGE_PACKET_SIZE;
288 
289  try
290  {
291  if ((CurrentBytePosition() + IMAGE_PACKET_SIZE) > m_asset.Length)
292  {
293  imagePacketSize = LastPacketSize();
294  complete = true;
295  if ((CurrentBytePosition() + imagePacketSize) > m_asset.Length)
296  {
297  imagePacketSize = m_asset.Length - CurrentBytePosition();
298  complete = true;
299  }
300  }
301 
302  // It's concievable that the client might request packet one
303  // from a one packet image, which is really packet 0,
304  // which would leave us with a negative imagePacketSize..
305  if (imagePacketSize > 0)
306  {
307  byte[] imageData = new byte[imagePacketSize];
308  int currentPosition = CurrentBytePosition();
309 
310  try { Buffer.BlockCopy(m_asset, currentPosition, imageData, 0, imagePacketSize); }
311  catch (Exception e)
312  {
313  m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}, currentposition={2}, imagepacketsize={3}, exception={4}",
314  TextureID, m_asset.Length, currentPosition, imagePacketSize, e.Message);
315  return false;
316  }
317 
318  //Send the packet
319  client.SendImageNextPart((ushort)(m_currentPacket - 1), TextureID, imageData);
320  }
321 
322  return !complete;
323  }
324  catch (Exception)
325  {
326  return false;
327  }
328  }
329 
330  private ushort TexturePacketCount()
331  {
332  if (!IsDecoded)
333  return 0;
334 
335  if (m_asset == null)
336  return 0;
337 
338  if (m_asset.Length <= FIRST_PACKET_SIZE)
339  return 1;
340 
341  return (ushort)(((m_asset.Length - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1);
342  }
343 
344  private int GetPacketForBytePosition(int bytePosition)
345  {
346  return ((bytePosition - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1;
347  }
348 
349  private int LastPacketSize()
350  {
351  if (m_currentPacket == 1)
352  return m_asset.Length;
353  int lastsize = (m_asset.Length - FIRST_PACKET_SIZE) % IMAGE_PACKET_SIZE;
354  //If the last packet size is zero, it's really cImagePacketSize, it sits on the boundary
355  if (lastsize == 0)
356  {
357  lastsize = IMAGE_PACKET_SIZE;
358  }
359  return lastsize;
360  }
361 
362  private int CurrentBytePosition()
363  {
364  if (m_currentPacket == 0)
365  return 0;
366 
367  if (m_currentPacket == 1)
368  return FIRST_PACKET_SIZE;
369 
370  int result = FIRST_PACKET_SIZE + ((int)m_currentPacket - 2) * IMAGE_PACKET_SIZE;
371 
372  if (result < 0)
373  result = FIRST_PACKET_SIZE;
374 
375  return result;
376  }
377 
378  private void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers)
379  {
380  m_layers = layers;
381  IsDecoded = true;
382  RunUpdate();
383  }
384 
385  private void AssetDataCallback(UUID AssetID, AssetBase asset)
386  {
387  HasAsset = true;
388 
389  if (asset == null || asset.Data == null)
390  {
391  if (m_imageManager.MissingImage != null)
392  {
393  m_asset = m_imageManager.MissingImage.Data;
394  }
395  else
396  {
397  m_asset = null;
398  IsDecoded = true;
399  }
400  }
401  else
402  {
403  m_asset = asset.Data;
404  }
405 
406  RunUpdate();
407  }
408 
409  private void AssetReceived(string id, Object sender, AssetBase asset)
410  {
411 // m_log.DebugFormat(
412 // "[J2KIMAGE]: Received asset {0} ({1} bytes)", id, asset != null ? asset.Data.Length.ToString() : "n/a");
413 
414  UUID assetID = UUID.Zero;
415  if (asset != null)
416  {
417  assetID = asset.FullID;
418  }
419  else if ((InventoryAccessModule != null) && (sender != InventoryAccessModule))
420  {
421  // Unfortunately we need this here, there's no other way.
422  // This is due to the fact that textures opened directly from the agent's inventory
423  // don't have any distinguishing feature. As such, in order to serve those when the
424  // foreign user is visiting, we need to try again after the first fail to the local
425  // asset service.
426  string assetServerURL = string.Empty;
427  if (InventoryAccessModule.IsForeignUser(AgentID, out assetServerURL) && !string.IsNullOrEmpty(assetServerURL))
428  {
429  if (!assetServerURL.EndsWith("/") && !assetServerURL.EndsWith("="))
430  assetServerURL = assetServerURL + "/";
431 
432 // m_log.DebugFormat("[J2KIMAGE]: texture {0} not found in local asset storage. Trying user's storage.", assetServerURL + id);
433  AssetService.Get(assetServerURL + id, InventoryAccessModule, AssetReceived);
434  return;
435  }
436  }
437 
438  AssetDataCallback(assetID, asset);
439  }
440  }
441 }
C5.IPriorityQueueHandle< J2KImage > PriorityQueueHandle
Definition: J2KImage.cs:85
void RunUpdate()
This is where we decide what we need to update and assign the real discardLevel and packetNumber assu...
Definition: J2KImage.cs:145
bool SendPackets(IClientAPI client, int packetsToSend, out int packetsSent)
Sends packets for this texture to a client until packetsToSend is hit or the transfer completes ...
Definition: J2KImage.cs:108
Asset class. All Assets are reference by this class or a class derived from this class ...
Definition: AssetBase.cs:49
Stores information about a current texture download and a reference to the texture asset ...
Definition: J2KImage.cs:43
IInventoryAccessModule InventoryAccessModule
Definition: J2KImage.cs:67
J2KImage(LLImageManager imageManager)
Definition: J2KImage.cs:95
This class handles UDP texture requests.