clockwerk-opensim-stable – 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  
46 using Ionic.Zlib;
47  
48 // You will need to uncomment these lines if you are adding a region module to some other assembly which does not already
49 // specify its assembly. Otherwise, the region modules in the assembly will not be picked up when OpenSimulator scans
50 // the available DLLs
51 //[assembly: Addin("MaterialsDemoModule", "1.0")]
52 //[assembly: AddinDependency("OpenSim", "0.5")]
53  
54 namespace OpenSim.Region.OptionalModules.MaterialsDemoModule
55 {
56 /// <summary>
57 ///
58 // # # ## ##### # # # # # ####
59 // # # # # # # ## # # ## # # #
60 // # # # # # # # # # # # # # #
61 // # ## # ###### ##### # # # # # # # # ###
62 // ## ## # # # # # ## # # ## # #
63 // # # # # # # # # # # # ####
64 //
65 // THIS MODULE IS FOR EXPERIMENTAL USE ONLY AND MAY CAUSE REGION OR ASSET CORRUPTION!
66 //
67 ////////////// WARNING //////////////////////////////////////////////////////////////////
68 /// This is an *Experimental* module for developing support for materials-capable viewers
69 /// This module should NOT be used in a production environment! It may cause data corruption and
70 /// viewer crashes. It should be only used to evaluate implementations of materials.
71 ///
72 /// Materials are persisted via SceneObjectPart.dynattrs. This is a relatively new feature
73 /// of OpenSimulator and is not field proven at the time this module was written. Persistence
74 /// may fail or become corrupt and this could cause viewer crashes due to erroneous materials
75 /// data being sent to viewers. Materials descriptions might survive IAR, OAR, or other means
76 /// of archiving however the texture resources used by these materials probably will not as they
77 /// may not be adequately referenced to ensure proper archiving.
78 ///
79 ///
80 ///
81 /// To enable this module, add this string at the bottom of OpenSim.ini:
82 /// [MaterialsDemoModule]
83 ///
84 /// </summary>
85 ///
86  
87 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MaterialsDemoModule")]
88 public class MaterialsDemoModule : INonSharedRegionModule
89 {
90 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
91  
92 public string Name { get { return "MaterialsDemoModule"; } }
93  
94 public Type ReplaceableInterface { get { return null; } }
95  
96 private Scene m_scene = null;
97 private bool m_enabled = false;
98  
99 public Dictionary<UUID, OSDMap> m_knownMaterials = new Dictionary<UUID, OSDMap>();
100  
101 public void Initialise(IConfigSource source)
102 {
103 m_enabled = (source.Configs["MaterialsDemoModule"] != null);
104 if (!m_enabled)
105 return;
106  
107 m_log.DebugFormat("[MaterialsDemoModule]: INITIALIZED MODULE");
108 }
109  
110 public void Close()
111 {
112 if (!m_enabled)
113 return;
114  
115 m_log.DebugFormat("[MaterialsDemoModule]: CLOSED MODULE");
116 }
117  
118 public void AddRegion(Scene scene)
119 {
120 if (!m_enabled)
121 return;
122  
123 m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} ADDED", scene.RegionInfo.RegionName);
124  
125 m_scene = scene;
126 m_scene.EventManager.OnRegisterCaps += OnRegisterCaps;
127 m_scene.EventManager.OnObjectAddedToScene += EventManager_OnObjectAddedToScene;
128 // m_scene.EventManager.OnGatherUuids += GatherMaterialsUuids;
129 }
130  
131 void EventManager_OnObjectAddedToScene(SceneObjectGroup obj)
132 {
133 foreach (var part in obj.Parts)
134 if (part != null)
135 GetStoredMaterialsForPart(part);
136 }
137  
138 void OnRegisterCaps(OpenMetaverse.UUID agentID, OpenSim.Framework.Capabilities.Caps caps)
139 {
140 string capsBase = "/CAPS/" + caps.CapsObjectPath;
141  
142 IRequestHandler renderMaterialsPostHandler
143 = new RestStreamHandler("POST", capsBase + "/", RenderMaterialsPostCap, "RenderMaterials", null);
144 caps.RegisterHandler("RenderMaterials", renderMaterialsPostHandler);
145  
146 // OpenSimulator CAPs infrastructure seems to be somewhat hostile towards any CAP that requires both GET
147 // and POST handlers, (at least at the time this was originally written), so we first set up a POST
148 // handler normally and then add a GET handler via MainServer
149  
150 IRequestHandler renderMaterialsGetHandler
151 = new RestStreamHandler("GET", capsBase + "/", RenderMaterialsGetCap, "RenderMaterials", null);
152 MainServer.Instance.AddStreamHandler(renderMaterialsGetHandler);
153  
154 // materials viewer seems to use either POST or PUT, so assign POST handler for PUT as well
155 IRequestHandler renderMaterialsPutHandler
156 = new RestStreamHandler("PUT", capsBase + "/", RenderMaterialsPostCap, "RenderMaterials", null);
157 MainServer.Instance.AddStreamHandler(renderMaterialsPutHandler);
158 }
159  
160 public void RemoveRegion(Scene scene)
161 {
162 if (!m_enabled)
163 return;
164  
165 m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps;
166 m_scene.EventManager.OnObjectAddedToScene -= EventManager_OnObjectAddedToScene;
167 // m_scene.EventManager.OnGatherUuids -= GatherMaterialsUuids;
168  
169 m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} REMOVED", scene.RegionInfo.RegionName);
170 }
171  
172 public void RegionLoaded(Scene scene)
173 {
174 }
175  
176 OSDMap GetMaterial(UUID id)
177 {
178 OSDMap map = null;
179 lock (m_knownMaterials)
180 {
181 if (m_knownMaterials.ContainsKey(id))
182 {
183 map = new OSDMap();
184 map["ID"] = OSD.FromBinary(id.GetBytes());
185 map["Material"] = m_knownMaterials[id];
186 }
187 }
188 return map;
189 }
190  
191 void GetStoredMaterialsForPart(SceneObjectPart part)
192 {
193 OSD OSMaterials = null;
194 OSDArray matsArr = null;
195  
196 if (part.DynAttrs == null)
197 {
198 m_log.Warn("[MaterialsDemoModule]: NULL DYNATTRS :( ");
199 }
200  
201 lock (part.DynAttrs)
202 {
203 if (part.DynAttrs.ContainsStore("OpenSim", "Materials"))
204 {
205 OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials");
206  
207 if (materialsStore == null)
208 return;
209  
210 materialsStore.TryGetValue("Materials", out OSMaterials);
211 }
212  
213 if (OSMaterials != null && OSMaterials is OSDArray)
214 matsArr = OSMaterials as OSDArray;
215 else
216 return;
217 }
218  
219 m_log.Info("[MaterialsDemoModule]: OSMaterials: " + OSDParser.SerializeJsonString(OSMaterials));
220  
221 if (matsArr == null)
222 {
223 m_log.Info("[MaterialsDemoModule]: matsArr is null :( ");
224 return;
225 }
226  
227 foreach (OSD elemOsd in matsArr)
228 {
229 if (elemOsd != null && elemOsd is OSDMap)
230 {
231 OSDMap matMap = elemOsd as OSDMap;
232 if (matMap.ContainsKey("ID") && matMap.ContainsKey("Material"))
233 {
234 try
235 {
236 lock (m_knownMaterials)
237 m_knownMaterials[matMap["ID"].AsUUID()] = (OSDMap)matMap["Material"];
238 }
239 catch (Exception e)
240 {
241 m_log.Warn("[MaterialsDemoModule]: exception decoding persisted material: " + e.ToString());
242 }
243 }
244 }
245 }
246 }
247  
248 void StoreMaterialsForPart(SceneObjectPart part)
249 {
250 try
251 {
252 if (part == null || part.Shape == null)
253 return;
254  
255 Dictionary<UUID, OSDMap> mats = new Dictionary<UUID, OSDMap>();
256  
257 Primitive.TextureEntry te = part.Shape.Textures;
258  
259 if (te.DefaultTexture != null)
260 {
261 lock (m_knownMaterials)
262 {
263 if (m_knownMaterials.ContainsKey(te.DefaultTexture.MaterialID))
264 mats[te.DefaultTexture.MaterialID] = m_knownMaterials[te.DefaultTexture.MaterialID];
265 }
266 }
267  
268 if (te.FaceTextures != null)
269 {
270 foreach (var face in te.FaceTextures)
271 {
272 if (face != null)
273 {
274 lock (m_knownMaterials)
275 {
276 if (m_knownMaterials.ContainsKey(face.MaterialID))
277 mats[face.MaterialID] = m_knownMaterials[face.MaterialID];
278 }
279 }
280 }
281 }
282 if (mats.Count == 0)
283 return;
284  
285 OSDArray matsArr = new OSDArray();
286 foreach (KeyValuePair<UUID, OSDMap> kvp in mats)
287 {
288 OSDMap matOsd = new OSDMap();
289 matOsd["ID"] = OSD.FromUUID(kvp.Key);
290 matOsd["Material"] = kvp.Value;
291 matsArr.Add(matOsd);
292 }
293  
294 OSDMap OSMaterials = new OSDMap();
295 OSMaterials["Materials"] = matsArr;
296  
297 lock (part.DynAttrs)
298 part.DynAttrs.SetStore("OpenSim", "Materials", OSMaterials);
299 }
300 catch (Exception e)
301 {
302 m_log.Warn("[MaterialsDemoModule]: exception in StoreMaterialsForPart(): " + e.ToString());
303 }
304 }
305  
306 public string RenderMaterialsPostCap(string request, string path,
307 string param, IOSHttpRequest httpRequest,
308 IOSHttpResponse httpResponse)
309 {
310 m_log.Debug("[MaterialsDemoModule]: POST cap handler");
311  
312 OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request);
313 OSDMap resp = new OSDMap();
314  
315 OSDMap materialsFromViewer = null;
316  
317 OSDArray respArr = new OSDArray();
318  
319 if (req.ContainsKey("Zipped"))
320 {
321 OSD osd = null;
322  
323 byte[] inBytes = req["Zipped"].AsBinary();
324  
325 try
326 {
327 osd = ZDecompressBytesToOsd(inBytes);
328  
329 if (osd != null)
330 {
331 if (osd is OSDArray) // assume array of MaterialIDs designating requested material entries
332 {
333 foreach (OSD elem in (OSDArray)osd)
334 {
335  
336 try
337 {
338 UUID id = new UUID(elem.AsBinary(), 0);
339  
340 lock (m_knownMaterials)
341 {
342 if (m_knownMaterials.ContainsKey(id))
343 {
344 m_log.Info("[MaterialsDemoModule]: request for known material ID: " + id.ToString());
345 OSDMap matMap = new OSDMap();
346 matMap["ID"] = OSD.FromBinary(id.GetBytes());
347  
348 matMap["Material"] = m_knownMaterials[id];
349 respArr.Add(matMap);
350 }
351 else
352 m_log.Info("[MaterialsDemoModule]: request for UNKNOWN material ID: " + id.ToString());
353 }
354 }
355 catch (Exception e)
356 {
357 // report something here?
358 continue;
359 }
360 }
361 }
362 else if (osd is OSDMap) // reqest to assign a material
363 {
364 materialsFromViewer = osd as OSDMap;
365  
366 if (materialsFromViewer.ContainsKey("FullMaterialsPerFace"))
367 {
368 OSD matsOsd = materialsFromViewer["FullMaterialsPerFace"];
369 if (matsOsd is OSDArray)
370 {
371 OSDArray matsArr = matsOsd as OSDArray;
372  
373 try
374 {
375 foreach (OSDMap matsMap in matsArr)
376 {
377 m_log.Debug("[MaterialsDemoModule]: processing matsMap: " + OSDParser.SerializeJsonString(matsMap));
378  
379 uint matLocalID = 0;
380 try { matLocalID = matsMap["ID"].AsUInteger(); }
381 catch (Exception e) { m_log.Warn("[MaterialsDemoModule]: cannot decode \"ID\" from matsMap: " + e.Message); }
382 m_log.Debug("[MaterialsDemoModule]: matLocalId: " + matLocalID.ToString());
383  
384  
385 OSDMap mat = null;
386 try { mat = matsMap["Material"] as OSDMap; }
387 catch (Exception e) { m_log.Warn("[MaterialsDemoModule]: cannot decode \"Material\" from matsMap: " + e.Message); }
388 m_log.Debug("[MaterialsDemoModule]: mat: " + OSDParser.SerializeJsonString(mat));
389  
390 UUID id = HashOsd(mat);
391 lock (m_knownMaterials)
392 m_knownMaterials[id] = mat;
393  
394  
395 var sop = m_scene.GetSceneObjectPart(matLocalID);
396 if (sop == null)
397 m_log.Debug("[MaterialsDemoModule]: null SOP for localId: " + matLocalID.ToString());
398 else
399 {
400 var te = new Primitive.TextureEntry(sop.Shape.TextureEntry, 0, sop.Shape.TextureEntry.Length);
401  
402 if (te == null)
403 {
404 m_log.Debug("[MaterialsDemoModule]: null TextureEntry for localId: " + matLocalID.ToString());
405 }
406 else
407 {
408 int face = -1;
409  
410 if (matsMap.ContainsKey("Face"))
411 {
412 face = matsMap["Face"].AsInteger();
413 if (te.FaceTextures == null) // && face == 0)
414 {
415 if (te.DefaultTexture == null)
416 m_log.Debug("[MaterialsDemoModule]: te.DefaultTexture is null");
417 else
418 te.DefaultTexture.MaterialID = id;
419 }
420 else
421 {
422 if (te.FaceTextures.Length >= face - 1)
423 {
424 if (te.FaceTextures[face] == null)
425 te.DefaultTexture.MaterialID = id;
426 else
427 te.FaceTextures[face].MaterialID = id;
428 }
429 }
430 }
431 else
432 {
433 if (te.DefaultTexture != null)
434 te.DefaultTexture.MaterialID = id;
435 }
436  
437 m_log.Debug("[MaterialsDemoModule]: setting material ID for face " + face.ToString() + " to " + id.ToString());
438  
439 //we cant use sop.UpdateTextureEntry(te); because it filters so do it manually
440  
441 if (sop.ParentGroup != null)
442 {
443 sop.Shape.TextureEntry = te.GetBytes();
444 sop.TriggerScriptChangedEvent(Changed.TEXTURE);
445 sop.UpdateFlag = UpdateRequired.FULL;
446 sop.ParentGroup.HasGroupChanged = true;
447  
448 sop.ScheduleFullUpdate();
449  
450 StoreMaterialsForPart(sop);
451 }
452 }
453 }
454 }
455 }
456 catch (Exception e)
457 {
458 m_log.Warn("[MaterialsDemoModule]: exception processing received material: " + e.Message);
459 }
460 }
461 }
462 }
463 }
464  
465 }
466 catch (Exception e)
467 {
468 m_log.Warn("[MaterialsDemoModule]: exception decoding zipped CAP payload: " + e.Message);
469 //return "";
470 }
471 m_log.Debug("[MaterialsDemoModule]: knownMaterials.Count: " + m_knownMaterials.Count.ToString());
472 }
473  
474  
475 resp["Zipped"] = ZCompressOSD(respArr, false);
476 string response = OSDParser.SerializeLLSDXmlString(resp);
477  
478 //m_log.Debug("[MaterialsDemoModule]: cap request: " + request);
479 m_log.Debug("[MaterialsDemoModule]: cap request (zipped portion): " + ZippedOsdBytesToString(req["Zipped"].AsBinary()));
480 m_log.Debug("[MaterialsDemoModule]: cap response: " + response);
481 return response;
482 }
483  
484  
485 public string RenderMaterialsGetCap(string request, string path,
486 string param, IOSHttpRequest httpRequest,
487 IOSHttpResponse httpResponse)
488 {
489 m_log.Debug("[MaterialsDemoModule]: GET cap handler");
490  
491 OSDMap resp = new OSDMap();
492 int matsCount = 0;
493 OSDArray allOsd = new OSDArray();
494  
495 lock (m_knownMaterials)
496 {
497 foreach (KeyValuePair<UUID, OSDMap> kvp in m_knownMaterials)
498 {
499 OSDMap matMap = new OSDMap();
500  
501 matMap["ID"] = OSD.FromBinary(kvp.Key.GetBytes());
502 matMap["Material"] = kvp.Value;
503 allOsd.Add(matMap);
504 matsCount++;
505 }
506 }
507  
508 resp["Zipped"] = ZCompressOSD(allOsd, false);
509 m_log.Debug("[MaterialsDemoModule]: matsCount: " + matsCount.ToString());
510  
511 return OSDParser.SerializeLLSDXmlString(resp);
512 }
513  
514 static string ZippedOsdBytesToString(byte[] bytes)
515 {
516 try
517 {
518 return OSDParser.SerializeJsonString(ZDecompressBytesToOsd(bytes));
519 }
520 catch (Exception e)
521 {
522 return "ZippedOsdBytesToString caught an exception: " + e.ToString();
523 }
524 }
525  
526 /// <summary>
527 /// computes a UUID by hashing a OSD object
528 /// </summary>
529 /// <param name="osd"></param>
530 /// <returns></returns>
531 private static UUID HashOsd(OSD osd)
532 {
533 using (var md5 = MD5.Create())
534 using (MemoryStream ms = new MemoryStream(OSDParser.SerializeLLSDBinary(osd, false)))
535 return new UUID(md5.ComputeHash(ms), 0);
536 }
537  
538 public static OSD ZCompressOSD(OSD inOsd, bool useHeader)
539 {
540 OSD osd = null;
541  
542 using (MemoryStream msSinkCompressed = new MemoryStream())
543 {
544 using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkCompressed,
545 Ionic.Zlib.CompressionMode.Compress, CompressionLevel.BestCompression, true))
546 {
547 CopyStream(new MemoryStream(OSDParser.SerializeLLSDBinary(inOsd, useHeader)), zOut);
548 zOut.Close();
549 }
550  
551 msSinkCompressed.Seek(0L, SeekOrigin.Begin);
552 osd = OSD.FromBinary( msSinkCompressed.ToArray());
553 }
554  
555 return osd;
556 }
557  
558  
559 public static OSD ZDecompressBytesToOsd(byte[] input)
560 {
561 OSD osd = null;
562  
563 using (MemoryStream msSinkUnCompressed = new MemoryStream())
564 {
565 using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkUnCompressed, CompressionMode.Decompress, true))
566 {
567 CopyStream(new MemoryStream(input), zOut);
568 zOut.Close();
569 }
570 msSinkUnCompressed.Seek(0L, SeekOrigin.Begin);
571 osd = OSDParser.DeserializeLLSDBinary(msSinkUnCompressed.ToArray());
572 }
573  
574 return osd;
575 }
576  
577 static void CopyStream(System.IO.Stream input, System.IO.Stream output)
578 {
579 byte[] buffer = new byte[2048];
580 int len;
581 while ((len = input.Read(buffer, 0, 2048)) > 0)
582 {
583 output.Write(buffer, 0, len);
584 }
585  
586 output.Flush();
587 }
588  
589 // FIXME: This code is currently still in UuidGatherer since we cannot use Scene.EventManager as some
590 // calls to the gatherer are done for objects with no scene.
591 // /// <summary>
592 // /// Gather all of the texture asset UUIDs used to reference "Materials" such as normal and specular maps
593 // /// </summary>
594 // /// <param name="part"></param>
595 // /// <param name="assetUuids"></param>
596 // private void GatherMaterialsUuids(SceneObjectPart part, IDictionary<UUID, AssetType> assetUuids)
597 // {
598 // // scan thru the dynAttrs map of this part for any textures used as materials
599 // OSD osdMaterials = null;
600 //
601 // lock (part.DynAttrs)
602 // {
603 // if (part.DynAttrs.ContainsStore("OpenSim", "Materials"))
604 // {
605 // OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials");
606 // if (materialsStore == null)
607 // return;
608 //
609 // materialsStore.TryGetValue("Materials", out osdMaterials);
610 // }
611 //
612 // if (osdMaterials != null)
613 // {
614 // //m_log.Info("[UUID Gatherer]: found Materials: " + OSDParser.SerializeJsonString(osd));
615 //
616 // if (osdMaterials is OSDArray)
617 // {
618 // OSDArray matsArr = osdMaterials as OSDArray;
619 // foreach (OSDMap matMap in matsArr)
620 // {
621 // try
622 // {
623 // if (matMap.ContainsKey("Material"))
624 // {
625 // OSDMap mat = matMap["Material"] as OSDMap;
626 // if (mat.ContainsKey("NormMap"))
627 // {
628 // UUID normalMapId = mat["NormMap"].AsUUID();
629 // if (normalMapId != UUID.Zero)
630 // {
631 // assetUuids[normalMapId] = AssetType.Texture;
632 // //m_log.Info("[UUID Gatherer]: found normal map ID: " + normalMapId.ToString());
633 // }
634 // }
635 // if (mat.ContainsKey("SpecMap"))
636 // {
637 // UUID specularMapId = mat["SpecMap"].AsUUID();
638 // if (specularMapId != UUID.Zero)
639 // {
640 // assetUuids[specularMapId] = AssetType.Texture;
641 // //m_log.Info("[UUID Gatherer]: found specular map ID: " + specularMapId.ToString());
642 // }
643 // }
644 // }
645 //
646 // }
647 // catch (Exception e)
648 // {
649 // m_log.Warn("[MaterialsDemoModule]: exception getting materials: " + e.Message);
650 // }
651 // }
652 // }
653 // }
654 // }
655 // }
656 }
657 }