clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 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 {
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 /// <summary>
49 /// If we've requested an asset but not received it in this ticks timeframe, then allow a duplicate
50 /// request from the client to trigger a fresh asset request.
51 /// </summary>
52 /// <remarks>
53 /// There are 10,000 ticks in a millisecond
54 /// </remarks>
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;
64 public IJ2KDecoder J2KDecoder;
65 public IAssetService AssetService;
66 public UUID AgentID;
67 public IInventoryAccessModule InventoryAccessModule;
68 private OpenJPEG.J2KLayerInfo[] m_layers;
69  
70 /// <summary>
71 /// Has this request decoded the asset data?
72 /// </summary>
73 public bool IsDecoded { get; private set; }
74  
75 /// <summary>
76 /// Has this request received the required asset data?
77 /// </summary>
78 public bool HasAsset { get; private set; }
79  
80 /// <summary>
81 /// Time in milliseconds at which the asset was requested.
82 /// </summary>
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  
100 /// <summary>
101 /// Sends packets for this texture to a client until packetsToSend is
102 /// hit or the transfer completes
103 /// </summary>
104 /// <param name="client">Reference to the client that the packets are destined for</param>
105 /// <param name="packetsToSend">Maximum number of packets to send during this call</param>
106 /// <param name="packetsSent">Number of packets sent during this call</param>
107 /// <returns>True if the transfer completes at the current discard level, otherwise false</returns>
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  
140 /// <summary>
141 /// This is where we decide what we need to update
142 /// and assign the real discardLevel and packetNumber
143 /// assuming of course that the connected client might be bonkers
144 /// </summary>
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 m_currentPacket = StartPacket;
238 }
239 }
240 }
241 }
242  
243 private bool SendFirstPacket(IClientAPI client)
244 {
245 if (client == null)
246 return false;
247  
248 if (m_asset == null)
249 {
250 m_log.Warn("[J2KIMAGE]: Sending ImageNotInDatabase for texture " + TextureID);
251 client.SendImageNotFound(TextureID);
252 return true;
253 }
254 else if (m_asset.Length <= FIRST_PACKET_SIZE)
255 {
256 // We have less then one packet's worth of data
257 client.SendImageFirstPart(1, TextureID, (uint)m_asset.Length, m_asset, 2);
258 m_stopPacket = 0;
259 return true;
260 }
261 else
262 {
263 // This is going to be a multi-packet texture download
264 byte[] firstImageData = new byte[FIRST_PACKET_SIZE];
265  
266 try { Buffer.BlockCopy(m_asset, 0, firstImageData, 0, FIRST_PACKET_SIZE); }
267 catch (Exception)
268 {
269 m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}", TextureID, m_asset.Length);
270 return true;
271 }
272  
273 client.SendImageFirstPart(TexturePacketCount(), TextureID, (uint)m_asset.Length, firstImageData, (byte)ImageCodec.J2C);
274 }
275 return false;
276 }
277  
278 private bool SendPacket(IClientAPI client)
279 {
280 if (client == null)
281 return false;
282  
283 bool complete = false;
284 int imagePacketSize = ((int)m_currentPacket == (TexturePacketCount())) ? LastPacketSize() : IMAGE_PACKET_SIZE;
285  
286 try
287 {
288 if ((CurrentBytePosition() + IMAGE_PACKET_SIZE) > m_asset.Length)
289 {
290 imagePacketSize = LastPacketSize();
291 complete = true;
292 if ((CurrentBytePosition() + imagePacketSize) > m_asset.Length)
293 {
294 imagePacketSize = m_asset.Length - CurrentBytePosition();
295 complete = true;
296 }
297 }
298  
299 // It's concievable that the client might request packet one
300 // from a one packet image, which is really packet 0,
301 // which would leave us with a negative imagePacketSize..
302 if (imagePacketSize > 0)
303 {
304 byte[] imageData = new byte[imagePacketSize];
305 int currentPosition = CurrentBytePosition();
306  
307 try { Buffer.BlockCopy(m_asset, currentPosition, imageData, 0, imagePacketSize); }
308 catch (Exception e)
309 {
310 m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}, currentposition={2}, imagepacketsize={3}, exception={4}",
311 TextureID, m_asset.Length, currentPosition, imagePacketSize, e.Message);
312 return false;
313 }
314  
315 //Send the packet
316 client.SendImageNextPart((ushort)(m_currentPacket - 1), TextureID, imageData);
317 }
318  
319 return !complete;
320 }
321 catch (Exception)
322 {
323 return false;
324 }
325 }
326  
327 private ushort TexturePacketCount()
328 {
329 if (!IsDecoded)
330 return 0;
331  
332 if (m_asset == null)
333 return 0;
334  
335 if (m_asset.Length <= FIRST_PACKET_SIZE)
336 return 1;
337  
338 return (ushort)(((m_asset.Length - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1);
339 }
340  
341 private int GetPacketForBytePosition(int bytePosition)
342 {
343 return ((bytePosition - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1;
344 }
345  
346 private int LastPacketSize()
347 {
348 if (m_currentPacket == 1)
349 return m_asset.Length;
350 int lastsize = (m_asset.Length - FIRST_PACKET_SIZE) % IMAGE_PACKET_SIZE;
351 //If the last packet size is zero, it's really cImagePacketSize, it sits on the boundary
352 if (lastsize == 0)
353 {
354 lastsize = IMAGE_PACKET_SIZE;
355 }
356 return lastsize;
357 }
358  
359 private int CurrentBytePosition()
360 {
361 if (m_currentPacket == 0)
362 return 0;
363  
364 if (m_currentPacket == 1)
365 return FIRST_PACKET_SIZE;
366  
367 int result = FIRST_PACKET_SIZE + ((int)m_currentPacket - 2) * IMAGE_PACKET_SIZE;
368  
369 if (result < 0)
370 result = FIRST_PACKET_SIZE;
371  
372 return result;
373 }
374  
375 private void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers)
376 {
377 m_layers = layers;
378 IsDecoded = true;
379 RunUpdate();
380 }
381  
382 private void AssetDataCallback(UUID AssetID, AssetBase asset)
383 {
384 HasAsset = true;
385  
386 if (asset == null || asset.Data == null)
387 {
388 if (m_imageManager.MissingImage != null)
389 {
390 m_asset = m_imageManager.MissingImage.Data;
391 }
392 else
393 {
394 m_asset = null;
395 IsDecoded = true;
396 }
397 }
398 else
399 {
400 m_asset = asset.Data;
401 }
402  
403 RunUpdate();
404 }
405  
406 private void AssetReceived(string id, Object sender, AssetBase asset)
407 {
408 // m_log.DebugFormat(
409 // "[J2KIMAGE]: Received asset {0} ({1} bytes)", id, asset != null ? asset.Data.Length.ToString() : "n/a");
410  
411 UUID assetID = UUID.Zero;
412 if (asset != null)
413 {
414 assetID = asset.FullID;
415 }
416 else if ((InventoryAccessModule != null) && (sender != InventoryAccessModule))
417 {
418 // Unfortunately we need this here, there's no other way.
419 // This is due to the fact that textures opened directly from the agent's inventory
420 // don't have any distinguishing feature. As such, in order to serve those when the
421 // foreign user is visiting, we need to try again after the first fail to the local
422 // asset service.
423 string assetServerURL = string.Empty;
424 if (InventoryAccessModule.IsForeignUser(AgentID, out assetServerURL) && !string.IsNullOrEmpty(assetServerURL))
425 {
426 if (!assetServerURL.EndsWith("/") && !assetServerURL.EndsWith("="))
427 assetServerURL = assetServerURL + "/";
428  
429 // m_log.DebugFormat("[J2KIMAGE]: texture {0} not found in local asset storage. Trying user's storage.", assetServerURL + id);
430 AssetService.Get(assetServerURL + id, InventoryAccessModule, AssetReceived);
431 return;
432 }
433 }
434  
435 AssetDataCallback(assetID, asset);
436 }
437 }
438 }