diff options
Diffstat (limited to 'OpenSim/Region/CoreModules/World/Media')
-rw-r--r-- | OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | 255 | ||||
-rw-r--r-- | OpenSim/Region/CoreModules/World/Media/Moap/Tests/MoapTests.cs | 36 |
2 files changed, 147 insertions, 144 deletions
diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 46b0470..f5aa40a 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs | |||
@@ -56,7 +56,7 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
56 | public class MoapModule : INonSharedRegionModule, IMoapModule | 56 | public class MoapModule : INonSharedRegionModule, IMoapModule |
57 | { | 57 | { |
58 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 58 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
59 | 59 | ||
60 | public string Name { get { return "MoapModule"; } } | 60 | public string Name { get { return "MoapModule"; } } |
61 | public Type ReplaceableInterface { get { return null; } } | 61 | public Type ReplaceableInterface { get { return null; } } |
62 | 62 | ||
@@ -64,33 +64,33 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
64 | /// Is this module enabled? | 64 | /// Is this module enabled? |
65 | /// </summary> | 65 | /// </summary> |
66 | protected bool m_isEnabled = true; | 66 | protected bool m_isEnabled = true; |
67 | 67 | ||
68 | /// <summary> | 68 | /// <summary> |
69 | /// The scene to which this module is attached | 69 | /// The scene to which this module is attached |
70 | /// </summary> | 70 | /// </summary> |
71 | protected Scene m_scene; | 71 | protected Scene m_scene; |
72 | 72 | ||
73 | /// <summary> | 73 | /// <summary> |
74 | /// Track the ObjectMedia capabilities given to users keyed by path | 74 | /// Track the ObjectMedia capabilities given to users keyed by path |
75 | /// </summary> | 75 | /// </summary> |
76 | protected Dictionary<string, UUID> m_omCapUsers = new Dictionary<string, UUID>(); | 76 | protected Dictionary<string, UUID> m_omCapUsers = new Dictionary<string, UUID>(); |
77 | 77 | ||
78 | /// <summary> | 78 | /// <summary> |
79 | /// Track the ObjectMedia capabilities given to users keyed by agent. Lock m_omCapUsers to manipulate. | 79 | /// Track the ObjectMedia capabilities given to users keyed by agent. Lock m_omCapUsers to manipulate. |
80 | /// </summary> | 80 | /// </summary> |
81 | protected Dictionary<UUID, string> m_omCapUrls = new Dictionary<UUID, string>(); | 81 | protected Dictionary<UUID, string> m_omCapUrls = new Dictionary<UUID, string>(); |
82 | 82 | ||
83 | /// <summary> | 83 | /// <summary> |
84 | /// Track the ObjectMediaUpdate capabilities given to users keyed by path | 84 | /// Track the ObjectMediaUpdate capabilities given to users keyed by path |
85 | /// </summary> | 85 | /// </summary> |
86 | protected Dictionary<string, UUID> m_omuCapUsers = new Dictionary<string, UUID>(); | 86 | protected Dictionary<string, UUID> m_omuCapUsers = new Dictionary<string, UUID>(); |
87 | 87 | ||
88 | /// <summary> | 88 | /// <summary> |
89 | /// Track the ObjectMediaUpdate capabilities given to users keyed by agent. Lock m_omuCapUsers to manipulate | 89 | /// Track the ObjectMediaUpdate capabilities given to users keyed by agent. Lock m_omuCapUsers to manipulate |
90 | /// </summary> | 90 | /// </summary> |
91 | protected Dictionary<UUID, string> m_omuCapUrls = new Dictionary<UUID, string>(); | 91 | protected Dictionary<UUID, string> m_omuCapUrls = new Dictionary<UUID, string>(); |
92 | 92 | ||
93 | public void Initialise(IConfigSource configSource) | 93 | public void Initialise(IConfigSource configSource) |
94 | { | 94 | { |
95 | IConfig config = configSource.Configs["MediaOnAPrim"]; | 95 | IConfig config = configSource.Configs["MediaOnAPrim"]; |
96 | 96 | ||
@@ -100,63 +100,63 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
100 | // m_log.Debug("[MOAP]: Initialised module.")l | 100 | // m_log.Debug("[MOAP]: Initialised module.")l |
101 | } | 101 | } |
102 | 102 | ||
103 | public void AddRegion(Scene scene) | 103 | public void AddRegion(Scene scene) |
104 | { | 104 | { |
105 | if (!m_isEnabled) | 105 | if (!m_isEnabled) |
106 | return; | 106 | return; |
107 | 107 | ||
108 | m_scene = scene; | 108 | m_scene = scene; |
109 | m_scene.RegisterModuleInterface<IMoapModule>(this); | 109 | m_scene.RegisterModuleInterface<IMoapModule>(this); |
110 | } | 110 | } |
111 | 111 | ||
112 | public void RemoveRegion(Scene scene) {} | 112 | public void RemoveRegion(Scene scene) {} |
113 | 113 | ||
114 | public void RegionLoaded(Scene scene) | 114 | public void RegionLoaded(Scene scene) |
115 | { | 115 | { |
116 | if (!m_isEnabled) | 116 | if (!m_isEnabled) |
117 | return; | 117 | return; |
118 | 118 | ||
119 | m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; | 119 | m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; |
120 | m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps; | 120 | m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps; |
121 | m_scene.EventManager.OnSceneObjectPartCopy += OnSceneObjectPartCopy; | 121 | m_scene.EventManager.OnSceneObjectPartCopy += OnSceneObjectPartCopy; |
122 | } | 122 | } |
123 | 123 | ||
124 | public void Close() | 124 | public void Close() |
125 | { | 125 | { |
126 | if (!m_isEnabled) | 126 | if (!m_isEnabled) |
127 | return; | 127 | return; |
128 | 128 | ||
129 | m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; | 129 | m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; |
130 | m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps; | 130 | m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps; |
131 | m_scene.EventManager.OnSceneObjectPartCopy -= OnSceneObjectPartCopy; | 131 | m_scene.EventManager.OnSceneObjectPartCopy -= OnSceneObjectPartCopy; |
132 | } | 132 | } |
133 | 133 | ||
134 | public void OnRegisterCaps(UUID agentID, Caps caps) | 134 | public void OnRegisterCaps(UUID agentID, Caps caps) |
135 | { | 135 | { |
136 | // m_log.DebugFormat( | 136 | // m_log.DebugFormat( |
137 | // "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); | 137 | // "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID); |
138 | 138 | ||
139 | string omCapUrl = "/CAPS/" + UUID.Random(); | 139 | string omCapUrl = "/CAPS/" + UUID.Random(); |
140 | 140 | ||
141 | lock (m_omCapUsers) | 141 | lock (m_omCapUsers) |
142 | { | 142 | { |
143 | m_omCapUsers[omCapUrl] = agentID; | 143 | m_omCapUsers[omCapUrl] = agentID; |
144 | m_omCapUrls[agentID] = omCapUrl; | 144 | m_omCapUrls[agentID] = omCapUrl; |
145 | 145 | ||
146 | // Even though we're registering for POST we're going to get GETS and UPDATES too | 146 | // Even though we're registering for POST we're going to get GETS and UPDATES too |
147 | caps.RegisterHandler( | 147 | caps.RegisterHandler( |
148 | "ObjectMedia", | 148 | "ObjectMedia", |
149 | new RestStreamHandler( | 149 | new RestStreamHandler( |
150 | "POST", omCapUrl, HandleObjectMediaMessage, "ObjectMedia", agentID.ToString())); | 150 | "POST", omCapUrl, HandleObjectMediaMessage, "ObjectMedia", agentID.ToString())); |
151 | } | 151 | } |
152 | 152 | ||
153 | string omuCapUrl = "/CAPS/" + UUID.Random(); | 153 | string omuCapUrl = "/CAPS/" + UUID.Random(); |
154 | 154 | ||
155 | lock (m_omuCapUsers) | 155 | lock (m_omuCapUsers) |
156 | { | 156 | { |
157 | m_omuCapUsers[omuCapUrl] = agentID; | 157 | m_omuCapUsers[omuCapUrl] = agentID; |
158 | m_omuCapUrls[agentID] = omuCapUrl; | 158 | m_omuCapUrls[agentID] = omuCapUrl; |
159 | 159 | ||
160 | // Even though we're registering for POST we're going to get GETS and UPDATES too | 160 | // Even though we're registering for POST we're going to get GETS and UPDATES too |
161 | caps.RegisterHandler( | 161 | caps.RegisterHandler( |
162 | "ObjectMediaNavigate", | 162 | "ObjectMediaNavigate", |
@@ -164,7 +164,7 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
164 | "POST", omuCapUrl, HandleObjectMediaNavigateMessage, "ObjectMediaNavigate", agentID.ToString())); | 164 | "POST", omuCapUrl, HandleObjectMediaNavigateMessage, "ObjectMediaNavigate", agentID.ToString())); |
165 | } | 165 | } |
166 | } | 166 | } |
167 | 167 | ||
168 | public void OnDeregisterCaps(UUID agentID, Caps caps) | 168 | public void OnDeregisterCaps(UUID agentID, Caps caps) |
169 | { | 169 | { |
170 | lock (m_omCapUsers) | 170 | lock (m_omCapUsers) |
@@ -173,7 +173,7 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
173 | m_omCapUrls.Remove(agentID); | 173 | m_omCapUrls.Remove(agentID); |
174 | m_omCapUsers.Remove(path); | 174 | m_omCapUsers.Remove(path); |
175 | } | 175 | } |
176 | 176 | ||
177 | lock (m_omuCapUsers) | 177 | lock (m_omuCapUsers) |
178 | { | 178 | { |
179 | string path = m_omuCapUrls[agentID]; | 179 | string path = m_omuCapUrls[agentID]; |
@@ -181,7 +181,7 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
181 | m_omuCapUsers.Remove(path); | 181 | m_omuCapUsers.Remove(path); |
182 | } | 182 | } |
183 | } | 183 | } |
184 | 184 | ||
185 | protected void OnSceneObjectPartCopy(SceneObjectPart copy, SceneObjectPart original, bool userExposed) | 185 | protected void OnSceneObjectPartCopy(SceneObjectPart copy, SceneObjectPart original, bool userExposed) |
186 | { | 186 | { |
187 | if (original.Shape.Media != null) | 187 | if (original.Shape.Media != null) |
@@ -197,19 +197,19 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
197 | dupeMedia.Add(null); | 197 | dupeMedia.Add(null); |
198 | } | 198 | } |
199 | } | 199 | } |
200 | 200 | ||
201 | copy.Shape.Media = dupeMedia; | 201 | copy.Shape.Media = dupeMedia; |
202 | } | 202 | } |
203 | } | 203 | } |
204 | 204 | ||
205 | public MediaEntry GetMediaEntry(SceneObjectPart part, int face) | 205 | public MediaEntry GetMediaEntry(SceneObjectPart part, int face) |
206 | { | 206 | { |
207 | MediaEntry me = null; | 207 | MediaEntry me = null; |
208 | 208 | ||
209 | CheckFaceParam(part, face); | 209 | CheckFaceParam(part, face); |
210 | 210 | ||
211 | List<MediaEntry> media = part.Shape.Media; | 211 | List<MediaEntry> media = part.Shape.Media; |
212 | 212 | ||
213 | if (null == media) | 213 | if (null == media) |
214 | { | 214 | { |
215 | me = null; | 215 | me = null; |
@@ -218,17 +218,17 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
218 | { | 218 | { |
219 | lock (media) | 219 | lock (media) |
220 | me = media[face]; | 220 | me = media[face]; |
221 | 221 | ||
222 | // TODO: Really need a proper copy constructor down in libopenmetaverse | 222 | // TODO: Really need a proper copy constructor down in libopenmetaverse |
223 | if (me != null) | 223 | if (me != null) |
224 | me = MediaEntry.FromOSD(me.GetOSD()); | 224 | me = MediaEntry.FromOSD(me.GetOSD()); |
225 | } | 225 | } |
226 | 226 | ||
227 | // m_log.DebugFormat("[MOAP]: GetMediaEntry for {0} face {1} found {2}", part.Name, face, me); | 227 | // m_log.DebugFormat("[MOAP]: GetMediaEntry for {0} face {1} found {2}", part.Name, face, me); |
228 | 228 | ||
229 | return me; | 229 | return me; |
230 | } | 230 | } |
231 | 231 | ||
232 | /// <summary> | 232 | /// <summary> |
233 | /// Set the media entry on the face of the given part. | 233 | /// Set the media entry on the face of the given part. |
234 | /// </summary> | 234 | /// </summary> |
@@ -238,28 +238,29 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
238 | public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me) | 238 | public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me) |
239 | { | 239 | { |
240 | // m_log.DebugFormat("[MOAP]: SetMediaEntry for {0}, face {1}", part.Name, face); | 240 | // m_log.DebugFormat("[MOAP]: SetMediaEntry for {0}, face {1}", part.Name, face); |
241 | 241 | ||
242 | CheckFaceParam(part, face); | 242 | CheckFaceParam(part, face); |
243 | 243 | ||
244 | if (null == part.Shape.Media) | 244 | if (null == part.Shape.Media) |
245 | { | 245 | { |
246 | if (me == null) | 246 | if (me == null) |
247 | return; | 247 | return; |
248 | else | 248 | else |
249 | part.Shape.Media = new PrimitiveBaseShape.MediaList(new MediaEntry[part.GetNumberOfSides()]); | 249 | part.Shape.Media = new PrimitiveBaseShape.MediaList(new MediaEntry[part.GetNumberOfSides()]); |
250 | } | 250 | } |
251 | 251 | ||
252 | lock (part.Shape.Media) | 252 | lock (part.Shape.Media) |
253 | part.Shape.Media[face] = me; | 253 | part.Shape.Media[face] = me; |
254 | 254 | ||
255 | UpdateMediaUrl(part, UUID.Zero); | 255 | UpdateMediaUrl(part, UUID.Zero); |
256 | 256 | ||
257 | SetPartMediaFlags(part, face, me != null); | 257 | SetPartMediaFlags(part, face, me != null); |
258 | 258 | ||
259 | part.ParentGroup.HasGroupChanged = true; | ||
259 | part.ScheduleFullUpdate(); | 260 | part.ScheduleFullUpdate(); |
260 | part.TriggerScriptChangedEvent(Changed.MEDIA); | 261 | part.TriggerScriptChangedEvent(Changed.MEDIA); |
261 | } | 262 | } |
262 | 263 | ||
263 | /// <summary> | 264 | /// <summary> |
264 | /// Clear the media entry from the face of the given part. | 265 | /// Clear the media entry from the face of the given part. |
265 | /// </summary> | 266 | /// </summary> |
@@ -267,9 +268,9 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
267 | /// <param name="face"></param> | 268 | /// <param name="face"></param> |
268 | public void ClearMediaEntry(SceneObjectPart part, int face) | 269 | public void ClearMediaEntry(SceneObjectPart part, int face) |
269 | { | 270 | { |
270 | SetMediaEntry(part, face, null); | 271 | SetMediaEntry(part, face, null); |
271 | } | 272 | } |
272 | 273 | ||
273 | /// <summary> | 274 | /// <summary> |
274 | /// Set the media flags on the texture face of the given part. | 275 | /// Set the media flags on the texture face of the given part. |
275 | /// </summary> | 276 | /// </summary> |
@@ -284,9 +285,9 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
284 | Primitive.TextureEntry te = part.Shape.Textures; | 285 | Primitive.TextureEntry te = part.Shape.Textures; |
285 | Primitive.TextureEntryFace teFace = te.CreateFace((uint)face); | 286 | Primitive.TextureEntryFace teFace = te.CreateFace((uint)face); |
286 | teFace.MediaFlags = flag; | 287 | teFace.MediaFlags = flag; |
287 | part.Shape.Textures = te; | 288 | part.Shape.Textures = te; |
288 | } | 289 | } |
289 | 290 | ||
290 | /// <summary> | 291 | /// <summary> |
291 | /// Sets or gets per face media textures. | 292 | /// Sets or gets per face media textures. |
292 | /// </summary> | 293 | /// </summary> |
@@ -300,11 +301,11 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
300 | string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | 301 | string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) |
301 | { | 302 | { |
302 | // m_log.DebugFormat("[MOAP]: Got ObjectMedia path [{0}], raw request [{1}]", path, request); | 303 | // m_log.DebugFormat("[MOAP]: Got ObjectMedia path [{0}], raw request [{1}]", path, request); |
303 | 304 | ||
304 | OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); | 305 | OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); |
305 | ObjectMediaMessage omm = new ObjectMediaMessage(); | 306 | ObjectMediaMessage omm = new ObjectMediaMessage(); |
306 | omm.Deserialize(osd); | 307 | omm.Deserialize(osd); |
307 | 308 | ||
308 | if (omm.Request is ObjectMediaRequest) | 309 | if (omm.Request is ObjectMediaRequest) |
309 | return HandleObjectMediaRequest(omm.Request as ObjectMediaRequest); | 310 | return HandleObjectMediaRequest(omm.Request as ObjectMediaRequest); |
310 | else if (omm.Request is ObjectMediaUpdate) | 311 | else if (omm.Request is ObjectMediaUpdate) |
@@ -312,10 +313,10 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
312 | 313 | ||
313 | throw new Exception( | 314 | throw new Exception( |
314 | string.Format( | 315 | string.Format( |
315 | "[MOAP]: ObjectMediaMessage has unrecognized ObjectMediaBlock of {0}", | 316 | "[MOAP]: ObjectMediaMessage has unrecognized ObjectMediaBlock of {0}", |
316 | omm.Request.GetType())); | 317 | omm.Request.GetType())); |
317 | } | 318 | } |
318 | 319 | ||
319 | /// <summary> | 320 | /// <summary> |
320 | /// Handle a fetch request for media textures | 321 | /// Handle a fetch request for media textures |
321 | /// </summary> | 322 | /// </summary> |
@@ -324,36 +325,36 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
324 | protected string HandleObjectMediaRequest(ObjectMediaRequest omr) | 325 | protected string HandleObjectMediaRequest(ObjectMediaRequest omr) |
325 | { | 326 | { |
326 | UUID primId = omr.PrimID; | 327 | UUID primId = omr.PrimID; |
327 | 328 | ||
328 | SceneObjectPart part = m_scene.GetSceneObjectPart(primId); | 329 | SceneObjectPart part = m_scene.GetSceneObjectPart(primId); |
329 | 330 | ||
330 | if (null == part) | 331 | if (null == part) |
331 | { | 332 | { |
332 | m_log.WarnFormat( | 333 | m_log.WarnFormat( |
333 | "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", | 334 | "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", |
334 | primId, m_scene.RegionInfo.RegionName); | 335 | primId, m_scene.RegionInfo.RegionName); |
335 | return string.Empty; | 336 | return string.Empty; |
336 | } | 337 | } |
337 | 338 | ||
338 | if (null == part.Shape.Media) | 339 | if (null == part.Shape.Media) |
339 | return string.Empty; | 340 | return string.Empty; |
340 | 341 | ||
341 | ObjectMediaResponse resp = new ObjectMediaResponse(); | 342 | ObjectMediaResponse resp = new ObjectMediaResponse(); |
342 | 343 | ||
343 | resp.PrimID = primId; | 344 | resp.PrimID = primId; |
344 | 345 | ||
345 | lock (part.Shape.Media) | 346 | lock (part.Shape.Media) |
346 | resp.FaceMedia = part.Shape.Media.ToArray(); | 347 | resp.FaceMedia = part.Shape.Media.ToArray(); |
347 | 348 | ||
348 | resp.Version = part.MediaUrl; | 349 | resp.Version = part.MediaUrl; |
349 | 350 | ||
350 | string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize()); | 351 | string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize()); |
351 | 352 | ||
352 | // m_log.DebugFormat("[MOAP]: Got HandleObjectMediaRequestGet raw response is [{0}]", rawResp); | 353 | // m_log.DebugFormat("[MOAP]: Got HandleObjectMediaRequestGet raw response is [{0}]", rawResp); |
353 | 354 | ||
354 | return rawResp; | 355 | return rawResp; |
355 | } | 356 | } |
356 | 357 | ||
357 | /// <summary> | 358 | /// <summary> |
358 | /// Handle an update of media textures. | 359 | /// Handle an update of media textures. |
359 | /// </summary> | 360 | /// </summary> |
@@ -363,46 +364,46 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
363 | protected string HandleObjectMediaUpdate(string path, ObjectMediaUpdate omu) | 364 | protected string HandleObjectMediaUpdate(string path, ObjectMediaUpdate omu) |
364 | { | 365 | { |
365 | UUID primId = omu.PrimID; | 366 | UUID primId = omu.PrimID; |
366 | 367 | ||
367 | SceneObjectPart part = m_scene.GetSceneObjectPart(primId); | 368 | SceneObjectPart part = m_scene.GetSceneObjectPart(primId); |
368 | 369 | ||
369 | if (null == part) | 370 | if (null == part) |
370 | { | 371 | { |
371 | m_log.WarnFormat( | 372 | m_log.WarnFormat( |
372 | "[MOAP]: Received an UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", | 373 | "[MOAP]: Received an UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in region {1}", |
373 | primId, m_scene.RegionInfo.RegionName); | 374 | primId, m_scene.RegionInfo.RegionName); |
374 | return string.Empty; | 375 | return string.Empty; |
375 | } | 376 | } |
376 | 377 | ||
377 | // m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); | 378 | // m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId); |
378 | // | 379 | // |
379 | // for (int i = 0; i < omu.FaceMedia.Length; i++) | 380 | // for (int i = 0; i < omu.FaceMedia.Length; i++) |
380 | // { | 381 | // { |
381 | // MediaEntry me = omu.FaceMedia[i]; | 382 | // MediaEntry me = omu.FaceMedia[i]; |
382 | // string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); | 383 | // string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD())); |
383 | // m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); | 384 | // m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v); |
384 | // } | 385 | // } |
385 | 386 | ||
386 | if (omu.FaceMedia.Length > part.GetNumberOfSides()) | 387 | if (omu.FaceMedia.Length > part.GetNumberOfSides()) |
387 | { | 388 | { |
388 | m_log.WarnFormat( | 389 | m_log.WarnFormat( |
389 | "[MOAP]: Received {0} media entries from client for prim {1} {2} but this prim has only {3} faces. Dropping request.", | 390 | "[MOAP]: Received {0} media entries from client for prim {1} {2} but this prim has only {3} faces. Dropping request.", |
390 | omu.FaceMedia.Length, part.Name, part.UUID, part.GetNumberOfSides()); | 391 | omu.FaceMedia.Length, part.Name, part.UUID, part.GetNumberOfSides()); |
391 | return string.Empty; | 392 | return string.Empty; |
392 | } | 393 | } |
393 | 394 | ||
394 | UUID agentId = default(UUID); | 395 | UUID agentId = default(UUID); |
395 | 396 | ||
396 | lock (m_omCapUsers) | 397 | lock (m_omCapUsers) |
397 | agentId = m_omCapUsers[path]; | 398 | agentId = m_omCapUsers[path]; |
398 | 399 | ||
399 | List<MediaEntry> media = part.Shape.Media; | 400 | List<MediaEntry> media = part.Shape.Media; |
400 | 401 | ||
401 | if (null == media) | 402 | if (null == media) |
402 | { | 403 | { |
403 | // m_log.DebugFormat("[MOAP]: Setting all new media list for {0}", part.Name); | 404 | // m_log.DebugFormat("[MOAP]: Setting all new media list for {0}", part.Name); |
404 | part.Shape.Media = new PrimitiveBaseShape.MediaList(omu.FaceMedia); | 405 | part.Shape.Media = new PrimitiveBaseShape.MediaList(omu.FaceMedia); |
405 | 406 | ||
406 | for (int i = 0; i < omu.FaceMedia.Length; i++) | 407 | for (int i = 0; i < omu.FaceMedia.Length; i++) |
407 | { | 408 | { |
408 | if (omu.FaceMedia[i] != null) | 409 | if (omu.FaceMedia[i] != null) |
@@ -412,7 +413,7 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
412 | // directly. | 413 | // directly. |
413 | SetPartMediaFlags(part, i, true); | 414 | SetPartMediaFlags(part, i, true); |
414 | // m_log.DebugFormat( | 415 | // m_log.DebugFormat( |
415 | // "[MOAP]: Media flags for face {0} is {1}", | 416 | // "[MOAP]: Media flags for face {0} is {1}", |
416 | // i, part.Shape.Textures.FaceTextures[i].MediaFlags); | 417 | // i, part.Shape.Textures.FaceTextures[i].MediaFlags); |
417 | } | 418 | } |
418 | } | 419 | } |
@@ -420,15 +421,15 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
420 | else | 421 | else |
421 | { | 422 | { |
422 | // m_log.DebugFormat("[MOAP]: Setting existing media list for {0}", part.Name); | 423 | // m_log.DebugFormat("[MOAP]: Setting existing media list for {0}", part.Name); |
423 | 424 | ||
424 | // We need to go through the media textures one at a time to make sure that we have permission | 425 | // We need to go through the media textures one at a time to make sure that we have permission |
425 | // to change them | 426 | // to change them |
426 | 427 | ||
427 | // FIXME: Race condition here since some other texture entry manipulator may overwrite/get | 428 | // FIXME: Race condition here since some other texture entry manipulator may overwrite/get |
428 | // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry | 429 | // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry |
429 | // directly. | 430 | // directly. |
430 | Primitive.TextureEntry te = part.Shape.Textures; | 431 | Primitive.TextureEntry te = part.Shape.Textures; |
431 | 432 | ||
432 | lock (media) | 433 | lock (media) |
433 | { | 434 | { |
434 | for (int i = 0; i < media.Count; i++) | 435 | for (int i = 0; i < media.Count; i++) |
@@ -436,38 +437,39 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
436 | if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) | 437 | if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i)) |
437 | { | 438 | { |
438 | media[i] = omu.FaceMedia[i]; | 439 | media[i] = omu.FaceMedia[i]; |
439 | 440 | ||
440 | // When a face is cleared this is done by setting the MediaFlags in the TextureEntry via a normal | 441 | // When a face is cleared this is done by setting the MediaFlags in the TextureEntry via a normal |
441 | // texture update, so we don't need to worry about clearing MediaFlags here. | 442 | // texture update, so we don't need to worry about clearing MediaFlags here. |
442 | if (null == media[i]) | 443 | if (null == media[i]) |
443 | continue; | 444 | continue; |
444 | 445 | ||
445 | SetPartMediaFlags(part, i, true); | 446 | SetPartMediaFlags(part, i, true); |
446 | 447 | ||
447 | // m_log.DebugFormat( | 448 | // m_log.DebugFormat( |
448 | // "[MOAP]: Media flags for face {0} is {1}", | 449 | // "[MOAP]: Media flags for face {0} is {1}", |
449 | // i, face.MediaFlags); | 450 | // i, face.MediaFlags); |
450 | // m_log.DebugFormat("[MOAP]: Set media entry for face {0} on {1}", i, part.Name); | 451 | // m_log.DebugFormat("[MOAP]: Set media entry for face {0} on {1}", i, part.Name); |
451 | } | 452 | } |
452 | } | 453 | } |
453 | } | 454 | } |
454 | 455 | ||
455 | part.Shape.Textures = te; | 456 | part.Shape.Textures = te; |
456 | 457 | ||
457 | // for (int i2 = 0; i2 < part.Shape.Textures.FaceTextures.Length; i2++) | 458 | // for (int i2 = 0; i2 < part.Shape.Textures.FaceTextures.Length; i2++) |
458 | // m_log.DebugFormat("[MOAP]: FaceTexture[{0}] is {1}", i2, part.Shape.Textures.FaceTextures[i2]); | 459 | // m_log.DebugFormat("[MOAP]: FaceTexture[{0}] is {1}", i2, part.Shape.Textures.FaceTextures[i2]); |
459 | } | 460 | } |
460 | 461 | ||
461 | UpdateMediaUrl(part, agentId); | 462 | UpdateMediaUrl(part, agentId); |
462 | 463 | ||
463 | // Arguably, we could avoid sending a full update to the avatar that just changed the texture. | 464 | // Arguably, we could avoid sending a full update to the avatar that just changed the texture. |
465 | part.ParentGroup.HasGroupChanged = true; | ||
464 | part.ScheduleFullUpdate(); | 466 | part.ScheduleFullUpdate(); |
465 | 467 | ||
466 | part.TriggerScriptChangedEvent(Changed.MEDIA); | 468 | part.TriggerScriptChangedEvent(Changed.MEDIA); |
467 | 469 | ||
468 | return string.Empty; | 470 | return string.Empty; |
469 | } | 471 | } |
470 | 472 | ||
471 | /// <summary> | 473 | /// <summary> |
472 | /// Received from the viewer if a user has changed the url of a media texture. | 474 | /// Received from the viewer if a user has changed the url of a media texture. |
473 | /// </summary> | 475 | /// </summary> |
@@ -481,71 +483,72 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
481 | string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | 483 | string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) |
482 | { | 484 | { |
483 | // m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request [{0}]", request); | 485 | // m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request [{0}]", request); |
484 | 486 | ||
485 | OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); | 487 | OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); |
486 | ObjectMediaNavigateMessage omn = new ObjectMediaNavigateMessage(); | 488 | ObjectMediaNavigateMessage omn = new ObjectMediaNavigateMessage(); |
487 | omn.Deserialize(osd); | 489 | omn.Deserialize(osd); |
488 | 490 | ||
489 | UUID primId = omn.PrimID; | 491 | UUID primId = omn.PrimID; |
490 | 492 | ||
491 | SceneObjectPart part = m_scene.GetSceneObjectPart(primId); | 493 | SceneObjectPart part = m_scene.GetSceneObjectPart(primId); |
492 | 494 | ||
493 | if (null == part) | 495 | if (null == part) |
494 | { | 496 | { |
495 | m_log.WarnFormat( | 497 | m_log.WarnFormat( |
496 | "[MOAP]: Received an ObjectMediaNavigateMessage for prim {0} but this doesn't exist in region {1}", | 498 | "[MOAP]: Received an ObjectMediaNavigateMessage for prim {0} but this doesn't exist in region {1}", |
497 | primId, m_scene.RegionInfo.RegionName); | 499 | primId, m_scene.RegionInfo.RegionName); |
498 | return string.Empty; | 500 | return string.Empty; |
499 | } | 501 | } |
500 | 502 | ||
501 | UUID agentId = default(UUID); | 503 | UUID agentId = default(UUID); |
502 | 504 | ||
503 | lock (m_omuCapUsers) | 505 | lock (m_omuCapUsers) |
504 | agentId = m_omuCapUsers[path]; | 506 | agentId = m_omuCapUsers[path]; |
505 | 507 | ||
506 | if (!m_scene.Permissions.CanInteractWithPrimMedia(agentId, part.UUID, omn.Face)) | 508 | if (!m_scene.Permissions.CanInteractWithPrimMedia(agentId, part.UUID, omn.Face)) |
507 | return string.Empty; | 509 | return string.Empty; |
508 | 510 | ||
509 | // m_log.DebugFormat( | 511 | // m_log.DebugFormat( |
510 | // "[MOAP]: Received request to update media entry for face {0} on prim {1} {2} to {3}", | 512 | // "[MOAP]: Received request to update media entry for face {0} on prim {1} {2} to {3}", |
511 | // omn.Face, part.Name, part.UUID, omn.URL); | 513 | // omn.Face, part.Name, part.UUID, omn.URL); |
512 | 514 | ||
513 | // If media has never been set for this prim, then just return. | 515 | // If media has never been set for this prim, then just return. |
514 | if (null == part.Shape.Media) | 516 | if (null == part.Shape.Media) |
515 | return string.Empty; | 517 | return string.Empty; |
516 | 518 | ||
517 | MediaEntry me = null; | 519 | MediaEntry me = null; |
518 | 520 | ||
519 | lock (part.Shape.Media) | 521 | lock (part.Shape.Media) |
520 | me = part.Shape.Media[omn.Face]; | 522 | me = part.Shape.Media[omn.Face]; |
521 | 523 | ||
522 | // Do the same if media has not been set up for a specific face | 524 | // Do the same if media has not been set up for a specific face |
523 | if (null == me) | 525 | if (null == me) |
524 | return string.Empty; | 526 | return string.Empty; |
525 | 527 | ||
526 | if (me.EnableWhiteList) | 528 | if (me.EnableWhiteList) |
527 | { | 529 | { |
528 | if (!CheckUrlAgainstWhitelist(omn.URL, me.WhiteList)) | 530 | if (!CheckUrlAgainstWhitelist(omn.URL, me.WhiteList)) |
529 | { | 531 | { |
530 | // m_log.DebugFormat( | 532 | // m_log.DebugFormat( |
531 | // "[MOAP]: Blocking change of face {0} on prim {1} {2} to {3} since it's not on the enabled whitelist", | 533 | // "[MOAP]: Blocking change of face {0} on prim {1} {2} to {3} since it's not on the enabled whitelist", |
532 | // omn.Face, part.Name, part.UUID, omn.URL); | 534 | // omn.Face, part.Name, part.UUID, omn.URL); |
533 | 535 | ||
534 | return string.Empty; | 536 | return string.Empty; |
535 | } | 537 | } |
536 | } | 538 | } |
537 | 539 | ||
538 | me.CurrentURL = omn.URL; | 540 | me.CurrentURL = omn.URL; |
539 | 541 | ||
540 | UpdateMediaUrl(part, agentId); | 542 | UpdateMediaUrl(part, agentId); |
541 | 543 | ||
544 | part.ParentGroup.HasGroupChanged = true; | ||
542 | part.ScheduleFullUpdate(); | 545 | part.ScheduleFullUpdate(); |
543 | 546 | ||
544 | part.TriggerScriptChangedEvent(Changed.MEDIA); | 547 | part.TriggerScriptChangedEvent(Changed.MEDIA); |
545 | 548 | ||
546 | return OSDParser.SerializeLLSDXmlString(new OSD()); | 549 | return OSDParser.SerializeLLSDXmlString(new OSD()); |
547 | } | 550 | } |
548 | 551 | ||
549 | /// <summary> | 552 | /// <summary> |
550 | /// Check that the face number is valid for the given prim. | 553 | /// Check that the face number is valid for the given prim. |
551 | /// </summary> | 554 | /// </summary> |
@@ -555,13 +558,13 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
555 | { | 558 | { |
556 | if (face < 0) | 559 | if (face < 0) |
557 | throw new ArgumentException("Face cannot be less than zero"); | 560 | throw new ArgumentException("Face cannot be less than zero"); |
558 | 561 | ||
559 | int maxFaces = part.GetNumberOfSides() - 1; | 562 | int maxFaces = part.GetNumberOfSides() - 1; |
560 | if (face > maxFaces) | 563 | if (face > maxFaces) |
561 | throw new ArgumentException( | 564 | throw new ArgumentException( |
562 | string.Format("Face argument was {0} but max is {1}", face, maxFaces)); | 565 | string.Format("Face argument was {0} but max is {1}", face, maxFaces)); |
563 | } | 566 | } |
564 | 567 | ||
565 | /// <summary> | 568 | /// <summary> |
566 | /// Update the media url of the given part | 569 | /// Update the media url of the given part |
567 | /// </summary> | 570 | /// </summary> |
@@ -583,10 +586,10 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
583 | int version = int.Parse(rawVersion); | 586 | int version = int.Parse(rawVersion); |
584 | part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, updateId); | 587 | part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, updateId); |
585 | } | 588 | } |
586 | 589 | ||
587 | // m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); | 590 | // m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID); |
588 | } | 591 | } |
589 | 592 | ||
590 | /// <summary> | 593 | /// <summary> |
591 | /// Check the given url against the given whitelist. | 594 | /// Check the given url against the given whitelist. |
592 | /// </summary> | 595 | /// </summary> |
@@ -599,22 +602,22 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
599 | return false; | 602 | return false; |
600 | 603 | ||
601 | Uri url = new Uri(rawUrl); | 604 | Uri url = new Uri(rawUrl); |
602 | 605 | ||
603 | foreach (string origWlUrl in whitelist) | 606 | foreach (string origWlUrl in whitelist) |
604 | { | 607 | { |
605 | string wlUrl = origWlUrl; | 608 | string wlUrl = origWlUrl; |
606 | 609 | ||
607 | // Deal with a line-ending wildcard | 610 | // Deal with a line-ending wildcard |
608 | if (wlUrl.EndsWith("*")) | 611 | if (wlUrl.EndsWith("*")) |
609 | wlUrl = wlUrl.Remove(wlUrl.Length - 1); | 612 | wlUrl = wlUrl.Remove(wlUrl.Length - 1); |
610 | 613 | ||
611 | // m_log.DebugFormat("[MOAP]: Checking whitelist URL pattern {0}", origWlUrl); | 614 | // m_log.DebugFormat("[MOAP]: Checking whitelist URL pattern {0}", origWlUrl); |
612 | 615 | ||
613 | // Handle a line starting wildcard slightly differently since this can only match the domain, not the path | 616 | // Handle a line starting wildcard slightly differently since this can only match the domain, not the path |
614 | if (wlUrl.StartsWith("*")) | 617 | if (wlUrl.StartsWith("*")) |
615 | { | 618 | { |
616 | wlUrl = wlUrl.Substring(1); | 619 | wlUrl = wlUrl.Substring(1); |
617 | 620 | ||
618 | if (url.Host.Contains(wlUrl)) | 621 | if (url.Host.Contains(wlUrl)) |
619 | { | 622 | { |
620 | // m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl); | 623 | // m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl); |
@@ -624,7 +627,7 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
624 | else | 627 | else |
625 | { | 628 | { |
626 | string urlToMatch = url.Authority + url.AbsolutePath; | 629 | string urlToMatch = url.Authority + url.AbsolutePath; |
627 | 630 | ||
628 | if (urlToMatch.StartsWith(wlUrl)) | 631 | if (urlToMatch.StartsWith(wlUrl)) |
629 | { | 632 | { |
630 | // m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl); | 633 | // m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl); |
@@ -632,7 +635,7 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap | |||
632 | } | 635 | } |
633 | } | 636 | } |
634 | } | 637 | } |
635 | 638 | ||
636 | return false; | 639 | return false; |
637 | } | 640 | } |
638 | } | 641 | } |
diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/Tests/MoapTests.cs b/OpenSim/Region/CoreModules/World/Media/Moap/Tests/MoapTests.cs index ee57aed..7080705 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/Tests/MoapTests.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/Tests/MoapTests.cs | |||
@@ -47,7 +47,7 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap.Tests | |||
47 | { | 47 | { |
48 | protected TestScene m_scene; | 48 | protected TestScene m_scene; |
49 | protected MoapModule m_module; | 49 | protected MoapModule m_module; |
50 | 50 | ||
51 | [SetUp] | 51 | [SetUp] |
52 | public override void SetUp() | 52 | public override void SetUp() |
53 | { | 53 | { |
@@ -55,45 +55,45 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap.Tests | |||
55 | 55 | ||
56 | m_module = new MoapModule(); | 56 | m_module = new MoapModule(); |
57 | m_scene = new SceneHelpers().SetupScene(); | 57 | m_scene = new SceneHelpers().SetupScene(); |
58 | SceneHelpers.SetupSceneModules(m_scene, m_module); | 58 | SceneHelpers.SetupSceneModules(m_scene, m_module); |
59 | } | 59 | } |
60 | 60 | ||
61 | [Test] | 61 | [Test] |
62 | public void TestClearMediaUrl() | 62 | public void TestClearMediaUrl() |
63 | { | 63 | { |
64 | TestHelpers.InMethod(); | 64 | TestHelpers.InMethod(); |
65 | // log4net.Config.XmlConfigurator.Configure(); | 65 | // log4net.Config.XmlConfigurator.Configure(); |
66 | 66 | ||
67 | SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; | 67 | SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; |
68 | MediaEntry me = new MediaEntry(); | 68 | MediaEntry me = new MediaEntry(); |
69 | 69 | ||
70 | m_module.SetMediaEntry(part, 1, me); | 70 | m_module.SetMediaEntry(part, 1, me); |
71 | m_module.ClearMediaEntry(part, 1); | 71 | m_module.ClearMediaEntry(part, 1); |
72 | 72 | ||
73 | Assert.That(part.Shape.Media[1], Is.EqualTo(null)); | 73 | Assert.That(part.Shape.Media[1], Is.EqualTo(null)); |
74 | 74 | ||
75 | // Although we've cleared one face, other faces may still be present. So we need to check for an | 75 | // Although we've cleared one face, other faces may still be present. So we need to check for an |
76 | // update media url version | 76 | // update media url version |
77 | Assert.That(part.MediaUrl, Is.EqualTo("x-mv:0000000001/" + UUID.Zero)); | 77 | Assert.That(part.MediaUrl, Is.EqualTo("x-mv:0000000001/" + UUID.Zero)); |
78 | 78 | ||
79 | // By changing media flag to false, the face texture once again becomes identical to the DefaultTexture. | 79 | // By changing media flag to false, the face texture once again becomes identical to the DefaultTexture. |
80 | // Therefore, when libOMV reserializes it, it disappears and we are left with no face texture in this slot. | 80 | // Therefore, when libOMV reserializes it, it disappears and we are left with no face texture in this slot. |
81 | // Not at all confusing, eh? | 81 | // Not at all confusing, eh? |
82 | Assert.That(part.Shape.Textures.FaceTextures[1], Is.Null); | 82 | Assert.That(part.Shape.Textures.FaceTextures[1], Is.Null); |
83 | } | 83 | } |
84 | 84 | ||
85 | [Test] | 85 | [Test] |
86 | public void TestSetMediaUrl() | 86 | public void TestSetMediaUrl() |
87 | { | 87 | { |
88 | TestHelpers.InMethod(); | 88 | TestHelpers.InMethod(); |
89 | 89 | ||
90 | string homeUrl = "opensimulator.org"; | 90 | string homeUrl = "opensimulator.org"; |
91 | 91 | ||
92 | SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; | 92 | SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; |
93 | MediaEntry me = new MediaEntry() { HomeURL = homeUrl }; | 93 | MediaEntry me = new MediaEntry() { HomeURL = homeUrl }; |
94 | 94 | ||
95 | m_module.SetMediaEntry(part, 1, me); | 95 | m_module.SetMediaEntry(part, 1, me); |
96 | 96 | ||
97 | Assert.That(part.Shape.Media[1].HomeURL, Is.EqualTo(homeUrl)); | 97 | Assert.That(part.Shape.Media[1].HomeURL, Is.EqualTo(homeUrl)); |
98 | Assert.That(part.MediaUrl, Is.EqualTo("x-mv:0000000000/" + UUID.Zero)); | 98 | Assert.That(part.MediaUrl, Is.EqualTo("x-mv:0000000000/" + UUID.Zero)); |
99 | Assert.That(part.Shape.Textures.FaceTextures[1].MediaFlags, Is.True); | 99 | Assert.That(part.Shape.Textures.FaceTextures[1].MediaFlags, Is.True); |