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