OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
GetTextureHandler.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;
30 using System.Collections.Specialized;
31 using System.Drawing;
32 using System.Drawing.Imaging;
33 using System.Reflection;
34 using System.IO;
35 using System.Web;
36 using log4net;
37 using Nini.Config;
38 using OpenMetaverse;
39 using OpenMetaverse.StructuredData;
40 using OpenMetaverse.Imaging;
41 using OpenSim.Framework;
42 using OpenSim.Framework.Servers;
43 using OpenSim.Framework.Servers.HttpServer;
44 using OpenSim.Region.Framework.Interfaces;
45 using OpenSim.Services.Interfaces;
47 
48 namespace OpenSim.Capabilities.Handlers
49 {
50  public class GetTextureHandler
51  {
52  private static readonly ILog m_log =
53  LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
54 
55  private IAssetService m_assetService;
56 
57  public const string DefaultFormat = "x-j2c";
58 
59  public GetTextureHandler(IAssetService assService)
60  {
61  m_assetService = assService;
62  }
63 
64  public Hashtable Handle(Hashtable request)
65  {
66  Hashtable ret = new Hashtable();
67  ret["int_response_code"] = (int)System.Net.HttpStatusCode.NotFound;
68  ret["content_type"] = "text/plain";
69  ret["keepalive"] = false;
70  ret["reusecontext"] = false;
71  ret["int_bytes"] = 0;
72  string textureStr = (string)request["texture_id"];
73  string format = (string)request["format"];
74 
75  //m_log.DebugFormat("[GETTEXTURE]: called {0}", textureStr);
76 
77  if (m_assetService == null)
78  {
79  m_log.Error("[GETTEXTURE]: Cannot fetch texture " + textureStr + " without an asset service");
80  }
81 
82  UUID textureID;
83  if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out textureID))
84  {
85 // m_log.DebugFormat("[GETTEXTURE]: Received request for texture id {0}", textureID);
86 
87  string[] formats;
88  if (!string.IsNullOrEmpty(format))
89  {
90  formats = new string[1] { format.ToLower() };
91  }
92  else
93  {
94  formats = new string[1] { DefaultFormat }; // default
95  if (((Hashtable)request["headers"])["Accept"] != null)
96  formats = WebUtil.GetPreferredImageTypes((string)((Hashtable)request["headers"])["Accept"]);
97  if (formats.Length == 0)
98  formats = new string[1] { DefaultFormat }; // default
99 
100  }
101  // OK, we have an array with preferred formats, possibly with only one entry
102  bool foundtexture = false;
103  foreach (string f in formats)
104  {
105  foundtexture = FetchTexture(request, ret, textureID, f);
106  if (foundtexture)
107  break;
108  }
109  if (!foundtexture)
110  {
111  ret["int_response_code"] = 404;
112  ret["error_status_text"] = "not found";
113  ret["str_response_string"] = "not found";
114  ret["content_type"] = "text/plain";
115  ret["keepalive"] = false;
116  ret["reusecontext"] = false;
117  ret["int_bytes"] = 0;
118  }
119  }
120  else
121  {
122  m_log.Warn("[GETTEXTURE]: Failed to parse a texture_id from GetTexture request: " + (string)request["uri"]);
123  }
124 
125 // m_log.DebugFormat(
126 // "[GETTEXTURE]: For texture {0} sending back response {1}, data length {2}",
127 // textureID, httpResponse.StatusCode, httpResponse.ContentLength);
128  return ret;
129  }
130 
139  private bool FetchTexture(Hashtable request, Hashtable response, UUID textureID, string format)
140  {
141 // m_log.DebugFormat("[GETTEXTURE]: {0} with requested format {1}", textureID, format);
142  AssetBase texture;
143 
144  string fullID = textureID.ToString();
145  if (format != DefaultFormat)
146  fullID = fullID + "-" + format;
147 
148  // try the cache
149  texture = m_assetService.GetCached(fullID);
150 
151  if (texture == null)
152  {
153  //m_log.DebugFormat("[GETTEXTURE]: texture was not in the cache");
154 
155  // Fetch locally or remotely. Misses return a 404
156  texture = m_assetService.Get(textureID.ToString());
157 
158  if (texture != null)
159  {
160  if (texture.Type != (sbyte)AssetType.Texture)
161  return true;
162 
163  if (format == DefaultFormat)
164  {
165  WriteTextureData(request, response, texture, format);
166  return true;
167  }
168  else
169  {
170  AssetBase newTexture = new AssetBase(texture.ID + "-" + format, texture.Name, (sbyte)AssetType.Texture, texture.Metadata.CreatorID);
171  newTexture.Data = ConvertTextureData(texture, format);
172  if (newTexture.Data.Length == 0)
173  return false; // !!! Caller try another codec, please!
174 
175  newTexture.Flags = AssetFlags.Collectable;
176  newTexture.Temporary = true;
177  newTexture.Local = true;
178  m_assetService.Store(newTexture);
179  WriteTextureData(request, response, newTexture, format);
180  return true;
181  }
182  }
183  }
184  else // it was on the cache
185  {
186  //m_log.DebugFormat("[GETTEXTURE]: texture was in the cache");
187  WriteTextureData(request, response, texture, format);
188  return true;
189  }
190 
191  //response = new Hashtable();
192 
193 
194  //WriteTextureData(request,response,null,format);
195  // not found
196  //m_log.Warn("[GETTEXTURE]: Texture " + textureID + " not found");
197  return false;
198  }
199 
200  private void WriteTextureData(Hashtable request, Hashtable response, AssetBase texture, string format)
201  {
202  Hashtable headers = new Hashtable();
203  response["headers"] = headers;
204 
205  string range = String.Empty;
206 
207  if (((Hashtable)request["headers"])["range"] != null)
208  range = (string)((Hashtable)request["headers"])["range"];
209 
210  else if (((Hashtable)request["headers"])["Range"] != null)
211  range = (string)((Hashtable)request["headers"])["Range"];
212 
213  if (!String.IsNullOrEmpty(range)) // JP2's only
214  {
215  // Range request
216  int start, end;
217  if (TryParseRange(range, out start, out end))
218  {
219  // Before clamping start make sure we can satisfy it in order to avoid
220  // sending back the last byte instead of an error status
221  if (start >= texture.Data.Length)
222  {
223 // m_log.DebugFormat(
224 // "[GETTEXTURE]: Client requested range for texture {0} starting at {1} but texture has end of {2}",
225 // texture.ID, start, texture.Data.Length);
226 
227  // Stricly speaking, as per http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html, we should be sending back
228  // Requested Range Not Satisfiable (416) here. However, it appears that at least recent implementations
229  // of the Linden Lab viewer (3.2.1 and 3.3.4 and probably earlier), a viewer that has previously
230  // received a very small texture may attempt to fetch bytes from the server past the
231  // range of data that it received originally. Whether this happens appears to depend on whether
232  // the viewer's estimation of how large a request it needs to make for certain discard levels
233  // (http://wiki.secondlife.com/wiki/Image_System#Discard_Level_and_Mip_Mapping), chiefly discard
234  // level 2. If this estimate is greater than the total texture size, returning a RequestedRangeNotSatisfiable
235  // here will cause the viewer to treat the texture as bad and never display the full resolution
236  // However, if we return PartialContent (or OK) instead, the viewer will display that resolution.
237 
238 // response.StatusCode = (int)System.Net.HttpStatusCode.RequestedRangeNotSatisfiable;
239  // viewers don't seem to handle RequestedRangeNotSatisfiable and keep retrying with same parameters
240  response["int_response_code"] = (int)System.Net.HttpStatusCode.NotFound;
241  }
242  else
243  {
244  // Handle the case where no second range value was given. This is equivalent to requesting
245  // the rest of the entity.
246  if (end == -1)
247  end = int.MaxValue;
248 
249  end = Utils.Clamp(end, 0, texture.Data.Length - 1);
250  start = Utils.Clamp(start, 0, end);
251  int len = end - start + 1;
252 
253 // m_log.Debug("Serving " + start + " to " + end + " of " + texture.Data.Length + " bytes for texture " + texture.ID);
254 
255  response["content-type"] = texture.Metadata.ContentType;
256 
257  if (start == 0 && len == texture.Data.Length) // well redudante maybe
258  {
259  response["int_response_code"] = (int)System.Net.HttpStatusCode.OK;
260  response["bin_response_data"] = texture.Data;
261  response["int_bytes"] = texture.Data.Length;
262  }
263  else
264  {
265  response["int_response_code"] = (int)System.Net.HttpStatusCode.PartialContent;
266  headers["Content-Range"] = String.Format("bytes {0}-{1}/{2}", start, end, texture.Data.Length);
267 
268  byte[] d = new byte[len];
269  Array.Copy(texture.Data, start, d, 0, len);
270  response["bin_response_data"] = d;
271  response["int_bytes"] = len;
272  }
273 // response.Body.Write(texture.Data, start, len);
274  }
275  }
276  else
277  {
278  m_log.Warn("[GETTEXTURE]: Malformed Range header: " + range);
279  response["int_response_code"] = (int)System.Net.HttpStatusCode.BadRequest;
280  }
281  }
282  else // JP2's or other formats
283  {
284  // Full content request
285  response["int_response_code"] = (int)System.Net.HttpStatusCode.OK;
286  if (format == DefaultFormat)
287  response["content_type"] = texture.Metadata.ContentType;
288  else
289  response["content_type"] = "image/" + format;
290 
291  response["bin_response_data"] = texture.Data;
292  response["int_bytes"] = texture.Data.Length;
293 
294 // response.Body.Write(texture.Data, 0, texture.Data.Length);
295  }
296 
297 // if (response.StatusCode < 200 || response.StatusCode > 299)
298 // m_log.WarnFormat(
299 // "[GETTEXTURE]: For texture {0} requested range {1} responded {2} with content length {3} (actual {4})",
300 // texture.FullID, range, response.StatusCode, response.ContentLength, texture.Data.Length);
301 // else
302 // m_log.DebugFormat(
303 // "[GETTEXTURE]: For texture {0} requested range {1} responded {2} with content length {3} (actual {4})",
304 // texture.FullID, range, response.StatusCode, response.ContentLength, texture.Data.Length);
305  }
306 
320  private bool TryParseRange(string header, out int start, out int end)
321  {
322  start = end = 0;
323 
324  if (header.StartsWith("bytes="))
325  {
326  string[] rangeValues = header.Substring(6).Split('-');
327 
328  if (rangeValues.Length == 2)
329  {
330  if (!Int32.TryParse(rangeValues[0], out start))
331  return false;
332 
333  string rawEnd = rangeValues[1];
334 
335  if (rawEnd == "")
336  {
337  end = -1;
338  return true;
339  }
340  else if (Int32.TryParse(rawEnd, out end))
341  {
342  return true;
343  }
344  }
345  }
346 
347  start = end = 0;
348  return false;
349  }
350 
351  private byte[] ConvertTextureData(AssetBase texture, string format)
352  {
353  m_log.DebugFormat("[GETTEXTURE]: Converting texture {0} to {1}", texture.ID, format);
354  byte[] data = new byte[0];
355 
356  MemoryStream imgstream = new MemoryStream();
357  Bitmap mTexture = new Bitmap(1, 1);
358  ManagedImage managedImage;
359  Image image = (Image)mTexture;
360 
361  try
362  {
363  // Taking our jpeg2000 data, decoding it, then saving it to a byte array with regular data
364 
365  imgstream = new MemoryStream();
366 
367  // Decode image to System.Drawing.Image
368  if (OpenJPEG.DecodeToImage(texture.Data, out managedImage, out image))
369  {
370  // Save to bitmap
371  mTexture = new Bitmap(image);
372 
373  EncoderParameters myEncoderParameters = new EncoderParameters();
374  myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 95L);
375 
376  // Save bitmap to stream
377  ImageCodecInfo codec = GetEncoderInfo("image/" + format);
378  if (codec != null)
379  {
380  mTexture.Save(imgstream, codec, myEncoderParameters);
381  // Write the stream to a byte array for output
382  data = imgstream.ToArray();
383  }
384  else
385  m_log.WarnFormat("[GETTEXTURE]: No such codec {0}", format);
386 
387  }
388  }
389  catch (Exception e)
390  {
391  m_log.WarnFormat("[GETTEXTURE]: Unable to convert texture {0} to {1}: {2}", texture.ID, format, e.Message);
392  }
393  finally
394  {
395  // Reclaim memory, these are unmanaged resources
396  // If we encountered an exception, one or more of these will be null
397  if (mTexture != null)
398  mTexture.Dispose();
399 
400  if (image != null)
401  image.Dispose();
402 
403  if (imgstream != null)
404  {
405  imgstream.Close();
406  imgstream.Dispose();
407  }
408  }
409 
410  return data;
411  }
412 
413  // From msdn
414  private static ImageCodecInfo GetEncoderInfo(String mimeType)
415  {
416  ImageCodecInfo[] encoders;
417  encoders = ImageCodecInfo.GetImageEncoders();
418  for (int j = 0; j < encoders.Length; ++j)
419  {
420  if (encoders[j].MimeType == mimeType)
421  return encoders[j];
422  }
423  return null;
424  }
425  }
426 }
OpenSim.Framework.Capabilities.Caps Caps
OpenSim.Framework.Capabilities.Caps Caps
sbyte Type
(sbyte) AssetType enum
Definition: AssetBase.cs:198
Asset class. All Assets are reference by this class or a class derived from this class ...
Definition: AssetBase.cs:49
string ID
Asset MetaData ID (transferring from UUID to string ID)
Definition: AssetBase.cs:177