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 System.IO;
31 using System.Reflection;
32 using System.Security.Cryptography; // for computing md5 hash
33 using log4net;
34 using Mono.Addins;
35 using Nini.Config;
36  
37 using OpenMetaverse;
38 using OpenMetaverse.StructuredData;
39  
40 using OpenSim.Framework;
41 using OpenSim.Framework.Servers;
42 using OpenSim.Framework.Servers.HttpServer;
43 using OpenSim.Region.Framework.Interfaces;
44 using OpenSim.Region.Framework.Scenes;
45 using OpenSimAssetType = OpenSim.Framework.SLUtil.OpenSimAssetType;
46  
47 using Ionic.Zlib;
48  
49 // You will need to uncomment these lines if you are adding a region module to some other assembly which does not already
50 // specify its assembly. Otherwise, the region modules in the assembly will not be picked up when OpenSimulator scans
51 // the available DLLs
52 //[assembly: Addin("MaterialsModule", "1.0")]
53 //[assembly: AddinDependency("OpenSim", "0.5")]
54  
55 namespace OpenSim.Region.OptionalModules.Materials
56 {
57 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MaterialsModule")]
58 public class MaterialsModule : INonSharedRegionModule
59 {
60 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
61  
62 public string Name { get { return "MaterialsModule"; } }
63  
64 public Type ReplaceableInterface { get { return null; } }
65  
66 private Scene m_scene = null;
67 private bool m_enabled = false;
68  
69 public Dictionary<UUID, OSDMap> m_regionMaterials = new Dictionary<UUID, OSDMap>();
70  
71 public void Initialise(IConfigSource source)
72 {
73 IConfig config = source.Configs["Materials"];
74 if (config == null)
75 return;
76  
77 m_enabled = config.GetBoolean("enable_materials", true);
78 if (!m_enabled)
79 return;
80  
81 m_log.DebugFormat("[Materials]: Initialized");
82 }
83  
84 public void Close()
85 {
86 if (!m_enabled)
87 return;
88 }
89  
90 public void AddRegion(Scene scene)
91 {
92 if (!m_enabled)
93 return;
94  
95 m_scene = scene;
96 m_scene.EventManager.OnRegisterCaps += OnRegisterCaps;
97 m_scene.EventManager.OnObjectAddedToScene += EventManager_OnObjectAddedToScene;
98 }
99  
100 private void EventManager_OnObjectAddedToScene(SceneObjectGroup obj)
101 {
102 foreach (var part in obj.Parts)
103 if (part != null)
104 GetStoredMaterialsInPart(part);
105 }
106  
107 private void OnRegisterCaps(OpenMetaverse.UUID agentID, OpenSim.Framework.Capabilities.Caps caps)
108 {
109 string capsBase = "/CAPS/" + caps.CapsObjectPath;
110  
111 IRequestHandler renderMaterialsPostHandler
112 = new RestStreamHandler("POST", capsBase + "/",
113 (request, path, param, httpRequest, httpResponse)
114 => RenderMaterialsPostCap(request, agentID),
115 "RenderMaterials", null);
116 caps.RegisterHandler("RenderMaterials", renderMaterialsPostHandler);
117  
118 // OpenSimulator CAPs infrastructure seems to be somewhat hostile towards any CAP that requires both GET
119 // and POST handlers, (at least at the time this was originally written), so we first set up a POST
120 // handler normally and then add a GET handler via MainServer
121  
122 IRequestHandler renderMaterialsGetHandler
123 = new RestStreamHandler("GET", capsBase + "/",
124 (request, path, param, httpRequest, httpResponse)
125 => RenderMaterialsGetCap(request),
126 "RenderMaterials", null);
127 MainServer.Instance.AddStreamHandler(renderMaterialsGetHandler);
128  
129 // materials viewer seems to use either POST or PUT, so assign POST handler for PUT as well
130 IRequestHandler renderMaterialsPutHandler
131 = new RestStreamHandler("PUT", capsBase + "/",
132 (request, path, param, httpRequest, httpResponse)
133 => RenderMaterialsPostCap(request, agentID),
134 "RenderMaterials", null);
135 MainServer.Instance.AddStreamHandler(renderMaterialsPutHandler);
136 }
137  
138 public void RemoveRegion(Scene scene)
139 {
140 if (!m_enabled)
141 return;
142  
143 m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps;
144 m_scene.EventManager.OnObjectAddedToScene -= EventManager_OnObjectAddedToScene;
145 }
146  
147 public void RegionLoaded(Scene scene)
148 {
149 }
150  
151 /// <summary>
152 /// Finds any legacy materials stored in DynAttrs that may exist for this part and add them to 'm_regionMaterials'.
153 /// </summary>
154 /// <param name="part"></param>
155 private void GetLegacyStoredMaterialsInPart(SceneObjectPart part)
156 {
157 if (part.DynAttrs == null)
158 return;
159  
160 OSD OSMaterials = null;
161 OSDArray matsArr = null;
162  
163 lock (part.DynAttrs)
164 {
165 if (part.DynAttrs.ContainsStore("OpenSim", "Materials"))
166 {
167 OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials");
168  
169 if (materialsStore == null)
170 return;
171  
172 materialsStore.TryGetValue("Materials", out OSMaterials);
173 }
174  
175 if (OSMaterials != null && OSMaterials is OSDArray)
176 matsArr = OSMaterials as OSDArray;
177 else
178 return;
179 }
180  
181 if (matsArr == null)
182 return;
183  
184 foreach (OSD elemOsd in matsArr)
185 {
186 if (elemOsd != null && elemOsd is OSDMap)
187 {
188 OSDMap matMap = elemOsd as OSDMap;
189 if (matMap.ContainsKey("ID") && matMap.ContainsKey("Material"))
190 {
191 try
192 {
193 lock (m_regionMaterials)
194 m_regionMaterials[matMap["ID"].AsUUID()] = (OSDMap)matMap["Material"];
195 }
196 catch (Exception e)
197 {
198 m_log.Warn("[Materials]: exception decoding persisted legacy material: " + e.ToString());
199 }
200 }
201 }
202 }
203 }
204  
205 /// <summary>
206 /// Find the materials used in the SOP, and add them to 'm_regionMaterials'.
207 /// </summary>
208 private void GetStoredMaterialsInPart(SceneObjectPart part)
209 {
210 if (part.Shape == null)
211 return;
212  
213 var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length);
214 if (te == null)
215 return;
216  
217 GetLegacyStoredMaterialsInPart(part);
218  
219 if (te.DefaultTexture != null)
220 GetStoredMaterialInFace(part, te.DefaultTexture);
221 else
222 m_log.WarnFormat(
223 "[Materials]: Default texture for part {0} (part of object {1)) in {2} unexpectedly null. Ignoring.",
224 part.Name, part.ParentGroup.Name, m_scene.Name);
225  
226 foreach (Primitive.TextureEntryFace face in te.FaceTextures)
227 {
228 if (face != null)
229 GetStoredMaterialInFace(part, face);
230 }
231 }
232  
233 /// <summary>
234 /// Find the materials used in one Face, and add them to 'm_regionMaterials'.
235 /// </summary>
236 private void GetStoredMaterialInFace(SceneObjectPart part, Primitive.TextureEntryFace face)
237 {
238 UUID id = face.MaterialID;
239 if (id == UUID.Zero)
240 return;
241  
242 lock (m_regionMaterials)
243 {
244 if (m_regionMaterials.ContainsKey(id))
245 return;
246  
247 byte[] data = m_scene.AssetService.GetData(id.ToString());
248 if (data == null)
249 {
250 m_log.WarnFormat("[Materials]: Prim \"{0}\" ({1}) contains unknown material ID {2}", part.Name, part.UUID, id);
251 return;
252 }
253  
254 OSDMap mat;
255 try
256 {
257 mat = (OSDMap)OSDParser.DeserializeLLSDXml(data);
258 }
259 catch (Exception e)
260 {
261 m_log.WarnFormat("[Materials]: cannot decode material asset {0}: {1}", id, e.Message);
262 return;
263 }
264  
265 m_regionMaterials[id] = mat;
266 }
267 }
268  
269 public string RenderMaterialsPostCap(string request, UUID agentID)
270 {
271 OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request);
272 OSDMap resp = new OSDMap();
273  
274 OSDMap materialsFromViewer = null;
275  
276 OSDArray respArr = new OSDArray();
277  
278 if (req.ContainsKey("Zipped"))
279 {
280 OSD osd = null;
281  
282 byte[] inBytes = req["Zipped"].AsBinary();
283  
284 try
285 {
286 osd = ZDecompressBytesToOsd(inBytes);
287  
288 if (osd != null)
289 {
290 if (osd is OSDArray) // assume array of MaterialIDs designating requested material entries
291 {
292 foreach (OSD elem in (OSDArray)osd)
293 {
294 try
295 {
296 UUID id = new UUID(elem.AsBinary(), 0);
297  
298 lock (m_regionMaterials)
299 {
300 if (m_regionMaterials.ContainsKey(id))
301 {
302 OSDMap matMap = new OSDMap();
303 matMap["ID"] = OSD.FromBinary(id.GetBytes());
304 matMap["Material"] = m_regionMaterials[id];
305 respArr.Add(matMap);
306 }
307 else
308 {
309 m_log.Warn("[Materials]: request for unknown material ID: " + id.ToString());
310  
311 // Theoretically we could try to load the material from the assets service,
312 // but that shouldn't be necessary because the viewer should only request
313 // materials that exist in a prim on the region, and all of these materials
314 // are already stored in m_regionMaterials.
315 }
316 }
317 }
318 catch (Exception e)
319 {
320 m_log.Error("Error getting materials in response to viewer request", e);
321 continue;
322 }
323 }
324 }
325 else if (osd is OSDMap) // request to assign a material
326 {
327 materialsFromViewer = osd as OSDMap;
328  
329 if (materialsFromViewer.ContainsKey("FullMaterialsPerFace"))
330 {
331 OSD matsOsd = materialsFromViewer["FullMaterialsPerFace"];
332 if (matsOsd is OSDArray)
333 {
334 OSDArray matsArr = matsOsd as OSDArray;
335  
336 try
337 {
338 foreach (OSDMap matsMap in matsArr)
339 {
340 uint primLocalID = 0;
341 try {
342 primLocalID = matsMap["ID"].AsUInteger();
343 }
344 catch (Exception e) {
345 m_log.Warn("[Materials]: cannot decode \"ID\" from matsMap: " + e.Message);
346 continue;
347 }
348  
349 OSDMap mat = null;
350 try
351 {
352 mat = matsMap["Material"] as OSDMap;
353 }
354 catch (Exception e)
355 {
356 m_log.Warn("[Materials]: cannot decode \"Material\" from matsMap: " + e.Message);
357 continue;
358 }
359  
360 SceneObjectPart sop = m_scene.GetSceneObjectPart(primLocalID);
361 if (sop == null)
362 {
363 m_log.WarnFormat("[Materials]: SOP not found for localId: {0}", primLocalID.ToString());
364 continue;
365 }
366  
367 if (!m_scene.Permissions.CanEditObject(sop.UUID, agentID))
368 {
369 m_log.WarnFormat("User {0} can't edit object {1} {2}", agentID, sop.Name, sop.UUID);
370 continue;
371 }
372  
373 Primitive.TextureEntry te = new Primitive.TextureEntry(sop.Shape.TextureEntry, 0, sop.Shape.TextureEntry.Length);
374 if (te == null)
375 {
376 m_log.WarnFormat("[Materials]: Error in TextureEntry for SOP {0} {1}", sop.Name, sop.UUID);
377 continue;
378 }
379  
380  
381 UUID id;
382 if (mat == null)
383 {
384 // This happens then the user removes a material from a prim
385 id = UUID.Zero;
386 }
387 else
388 {
389 id = StoreMaterialAsAsset(agentID, mat, sop);
390 }
391  
392  
393 int face = -1;
394  
395 if (matsMap.ContainsKey("Face"))
396 {
397 face = matsMap["Face"].AsInteger();
398 Primitive.TextureEntryFace faceEntry = te.CreateFace((uint)face);
399 faceEntry.MaterialID = id;
400 }
401 else
402 {
403 if (te.DefaultTexture == null)
404 m_log.WarnFormat("[Materials]: TextureEntry.DefaultTexture is null in {0} {1}", sop.Name, sop.UUID);
405 else
406 te.DefaultTexture.MaterialID = id;
407 }
408  
409 //m_log.DebugFormat("[Materials]: in \"{0}\" {1}, setting material ID for face {2} to {3}", sop.Name, sop.UUID, face, id);
410  
411 // We can't use sop.UpdateTextureEntry(te) because it filters, so do it manually
412 sop.Shape.TextureEntry = te.GetBytes();
413  
414 if (sop.ParentGroup != null)
415 {
416 sop.TriggerScriptChangedEvent(Changed.TEXTURE);
417 sop.UpdateFlag = UpdateRequired.FULL;
418 sop.ParentGroup.HasGroupChanged = true;
419 sop.ScheduleFullUpdate();
420 }
421 }
422 }
423 catch (Exception e)
424 {
425 m_log.Warn("[Materials]: exception processing received material ", e);
426 }
427 }
428 }
429 }
430 }
431  
432 }
433 catch (Exception e)
434 {
435 m_log.Warn("[Materials]: exception decoding zipped CAP payload ", e);
436 //return "";
437 }
438 }
439  
440  
441 resp["Zipped"] = ZCompressOSD(respArr, false);
442 string response = OSDParser.SerializeLLSDXmlString(resp);
443  
444 //m_log.Debug("[Materials]: cap request: " + request);
445 //m_log.Debug("[Materials]: cap request (zipped portion): " + ZippedOsdBytesToString(req["Zipped"].AsBinary()));
446 //m_log.Debug("[Materials]: cap response: " + response);
447 return response;
448 }
449  
450 private UUID StoreMaterialAsAsset(UUID agentID, OSDMap mat, SceneObjectPart sop)
451 {
452 UUID id;
453 // Material UUID = hash of the material's data.
454 // This makes materials deduplicate across the entire grid (but isn't otherwise required).
455 byte[] data = System.Text.Encoding.ASCII.GetBytes(OSDParser.SerializeLLSDXmlString(mat));
456 using (var md5 = MD5.Create())
457 id = new UUID(md5.ComputeHash(data), 0);
458  
459 lock (m_regionMaterials)
460 {
461 if (!m_regionMaterials.ContainsKey(id))
462 {
463 m_regionMaterials[id] = mat;
464  
465 // This asset might exist already, but it's ok to try to store it again
466 string name = "Material " + ChooseMaterialName(mat, sop);
467 name = name.Substring(0, Math.Min(64, name.Length)).Trim();
468 AssetBase asset = new AssetBase(id, name, (sbyte)OpenSimAssetType.Material, agentID.ToString());
469 asset.Data = data;
470 m_scene.AssetService.Store(asset);
471 }
472 }
473 return id;
474 }
475  
476 /// <summary>
477 /// Use heuristics to choose a good name for the material.
478 /// </summary>
479 private string ChooseMaterialName(OSDMap mat, SceneObjectPart sop)
480 {
481 UUID normMap = mat["NormMap"].AsUUID();
482 if (normMap != UUID.Zero)
483 {
484 AssetBase asset = m_scene.AssetService.GetCached(normMap.ToString());
485 if ((asset != null) && (asset.Name.Length > 0) && !asset.Name.Equals("From IAR"))
486 return asset.Name;
487 }
488  
489 UUID specMap = mat["SpecMap"].AsUUID();
490 if (specMap != UUID.Zero)
491 {
492 AssetBase asset = m_scene.AssetService.GetCached(specMap.ToString());
493 if ((asset != null) && (asset.Name.Length > 0) && !asset.Name.Equals("From IAR"))
494 return asset.Name;
495 }
496  
497 if (sop.Name != "Primitive")
498 return sop.Name;
499  
500 if ((sop.ParentGroup != null) && (sop.ParentGroup.Name != "Primitive"))
501 return sop.ParentGroup.Name;
502  
503 return "";
504 }
505  
506  
507 public string RenderMaterialsGetCap(string request)
508 {
509 OSDMap resp = new OSDMap();
510 int matsCount = 0;
511 OSDArray allOsd = new OSDArray();
512  
513 lock (m_regionMaterials)
514 {
515 foreach (KeyValuePair<UUID, OSDMap> kvp in m_regionMaterials)
516 {
517 OSDMap matMap = new OSDMap();
518  
519 matMap["ID"] = OSD.FromBinary(kvp.Key.GetBytes());
520 matMap["Material"] = kvp.Value;
521 allOsd.Add(matMap);
522 matsCount++;
523 }
524 }
525  
526 resp["Zipped"] = ZCompressOSD(allOsd, false);
527  
528 return OSDParser.SerializeLLSDXmlString(resp);
529 }
530  
531 private static string ZippedOsdBytesToString(byte[] bytes)
532 {
533 try
534 {
535 return OSDParser.SerializeJsonString(ZDecompressBytesToOsd(bytes));
536 }
537 catch (Exception e)
538 {
539 return "ZippedOsdBytesToString caught an exception: " + e.ToString();
540 }
541 }
542  
543 /// <summary>
544 /// computes a UUID by hashing a OSD object
545 /// </summary>
546 /// <param name="osd"></param>
547 /// <returns></returns>
548 private static UUID HashOsd(OSD osd)
549 {
550 byte[] data = OSDParser.SerializeLLSDBinary(osd, false);
551 using (var md5 = MD5.Create())
552 return new UUID(md5.ComputeHash(data), 0);
553 }
554  
555 public static OSD ZCompressOSD(OSD inOsd, bool useHeader)
556 {
557 OSD osd = null;
558  
559 byte[] data = OSDParser.SerializeLLSDBinary(inOsd, useHeader);
560  
561 using (MemoryStream msSinkCompressed = new MemoryStream())
562 {
563 using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkCompressed,
564 Ionic.Zlib.CompressionMode.Compress, CompressionLevel.BestCompression, true))
565 {
566 zOut.Write(data, 0, data.Length);
567 }
568  
569 msSinkCompressed.Seek(0L, SeekOrigin.Begin);
570 osd = OSD.FromBinary(msSinkCompressed.ToArray());
571 }
572  
573 return osd;
574 }
575  
576  
577 public static OSD ZDecompressBytesToOsd(byte[] input)
578 {
579 OSD osd = null;
580  
581 using (MemoryStream msSinkUnCompressed = new MemoryStream())
582 {
583 using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkUnCompressed, CompressionMode.Decompress, true))
584 {
585 zOut.Write(input, 0, input.Length);
586 }
587  
588 msSinkUnCompressed.Seek(0L, SeekOrigin.Begin);
589 osd = OSDParser.DeserializeLLSDBinary(msSinkUnCompressed.ToArray());
590 }
591  
592 return osd;
593 }
594 }
595 }