diff options
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/CoreModules/World/Land/PrimCountModule.cs | 260 |
1 files changed, 225 insertions, 35 deletions
diff --git a/OpenSim/Region/CoreModules/World/Land/PrimCountModule.cs b/OpenSim/Region/CoreModules/World/Land/PrimCountModule.cs index 34ef67f..dca842a 100644 --- a/OpenSim/Region/CoreModules/World/Land/PrimCountModule.cs +++ b/OpenSim/Region/CoreModules/World/Land/PrimCountModule.cs | |||
@@ -45,14 +45,13 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
45 | public int Owner = 0; | 45 | public int Owner = 0; |
46 | public int Group = 0; | 46 | public int Group = 0; |
47 | public int Others = 0; | 47 | public int Others = 0; |
48 | public Dictionary <UUID, int> Users = | 48 | public int Selected = 0; |
49 | new Dictionary <UUID, int>(); | 49 | public Dictionary <UUID, int> Users = new Dictionary <UUID, int>(); |
50 | } | 50 | } |
51 | 51 | ||
52 | public class PrimCountModule : IPrimCountModule, INonSharedRegionModule | 52 | public class PrimCountModule : IPrimCountModule, INonSharedRegionModule |
53 | { | 53 | { |
54 | private static readonly ILog m_log = | 54 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
55 | LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
56 | 55 | ||
57 | private Scene m_Scene; | 56 | private Scene m_Scene; |
58 | private Dictionary<UUID, PrimCounts> m_PrimCounts = | 57 | private Dictionary<UUID, PrimCounts> m_PrimCounts = |
@@ -64,10 +63,15 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
64 | private Dictionary<UUID, ParcelCounts> m_ParcelCounts = | 63 | private Dictionary<UUID, ParcelCounts> m_ParcelCounts = |
65 | new Dictionary<UUID, ParcelCounts>(); | 64 | new Dictionary<UUID, ParcelCounts>(); |
66 | 65 | ||
67 | // For now, a simple simwide taint to get this up. Later parcel based | 66 | /// <value> |
68 | // taint to allow recounting a parcel if only ownership has changed | 67 | /// For now, a simple simwide taint to get this up. Later parcel based |
69 | // without recounting the whole sim. | 68 | /// taint to allow recounting a parcel if only ownership has changed |
69 | /// without recounting the whole sim. | ||
70 | /// | ||
71 | /// We start out tainted so that the first get call resets the various prim counts. | ||
72 | /// <value> | ||
70 | private bool m_Tainted = true; | 73 | private bool m_Tainted = true; |
74 | |||
71 | private Object m_TaintLock = new Object(); | 75 | private Object m_TaintLock = new Object(); |
72 | 76 | ||
73 | public Type ReplaceableInterface | 77 | public Type ReplaceableInterface |
@@ -82,13 +86,15 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
82 | public void AddRegion(Scene scene) | 86 | public void AddRegion(Scene scene) |
83 | { | 87 | { |
84 | m_Scene = scene; | 88 | m_Scene = scene; |
89 | |||
90 | m_Scene.RegisterModuleInterface<IPrimCountModule>(this); | ||
85 | 91 | ||
86 | m_Scene.EventManager.OnParcelPrimCountAdd += | 92 | m_Scene.EventManager.OnObjectAddedToScene += OnParcelPrimCountAdd; |
87 | OnParcelPrimCountAdd; | ||
88 | m_Scene.EventManager.OnObjectBeingRemovedFromScene += | 93 | m_Scene.EventManager.OnObjectBeingRemovedFromScene += |
89 | OnObjectBeingRemovedFromScene; | 94 | OnObjectBeingRemovedFromScene; |
90 | m_Scene.EventManager.OnParcelPrimCountTainted += | 95 | m_Scene.EventManager.OnParcelPrimCountTainted += |
91 | OnParcelPrimCountTainted; | 96 | OnParcelPrimCountTainted; |
97 | m_Scene.EventManager.OnLandObjectAdded += delegate(ILandObject lo) { OnParcelPrimCountTainted(); }; | ||
92 | } | 98 | } |
93 | 99 | ||
94 | public void RegionLoaded(Scene scene) | 100 | public void RegionLoaded(Scene scene) |
@@ -116,6 +122,11 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
116 | { | 122 | { |
117 | if (!m_Tainted) | 123 | if (!m_Tainted) |
118 | AddObject(obj); | 124 | AddObject(obj); |
125 | // else | ||
126 | // m_log.DebugFormat( | ||
127 | // "[PRIM COUNT MODULE]: Ignoring OnParcelPrimCountAdd() for {0} on {1} since count is tainted", | ||
128 | // obj.Name, m_Scene.RegionInfo.RegionName); | ||
129 | |||
119 | } | 130 | } |
120 | } | 131 | } |
121 | 132 | ||
@@ -126,11 +137,18 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
126 | { | 137 | { |
127 | if (!m_Tainted) | 138 | if (!m_Tainted) |
128 | RemoveObject(obj); | 139 | RemoveObject(obj); |
140 | // else | ||
141 | // m_log.DebugFormat( | ||
142 | // "[PRIM COUNT MODULE]: Ignoring OnObjectBeingRemovedFromScene() for {0} on {1} since count is tainted", | ||
143 | // obj.Name, m_Scene.RegionInfo.RegionName); | ||
129 | } | 144 | } |
130 | } | 145 | } |
131 | 146 | ||
132 | private void OnParcelPrimCountTainted() | 147 | private void OnParcelPrimCountTainted() |
133 | { | 148 | { |
149 | // m_log.DebugFormat( | ||
150 | // "[PRIM COUNT MODULE]: OnParcelPrimCountTainted() called on {0}", m_Scene.RegionInfo.RegionName); | ||
151 | |||
134 | lock (m_TaintLock) | 152 | lock (m_TaintLock) |
135 | m_Tainted = true; | 153 | m_Tainted = true; |
136 | } | 154 | } |
@@ -155,15 +173,34 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
155 | 173 | ||
156 | // NOTE: Call under Taint Lock | 174 | // NOTE: Call under Taint Lock |
157 | private void AddObject(SceneObjectGroup obj) | 175 | private void AddObject(SceneObjectGroup obj) |
158 | { | 176 | { |
159 | if (obj.IsAttachment) | 177 | if (obj.IsAttachment) |
160 | return; | 178 | return; |
161 | if (((obj.RootPart.Flags & PrimFlags.TemporaryOnRez) != 0)) | 179 | if (((obj.RootPart.Flags & PrimFlags.TemporaryOnRez) != 0)) |
162 | return; | 180 | return; |
163 | 181 | ||
164 | Vector3 pos = obj.AbsolutePosition; | 182 | Vector3 pos = obj.AbsolutePosition; |
165 | ILandObject landObject = m_Scene.LandChannel.GetLandObject(pos.X, pos.Y); | 183 | ILandObject landObject = m_Scene.LandChannel.GetLandObject(pos.X, pos.Y); |
184 | |||
185 | // If for some reason there is no land object (perhaps the object is out of bounds) then we can't count it | ||
186 | if (landObject == null) | ||
187 | { | ||
188 | // m_log.WarnFormat( | ||
189 | // "[PRIM COUNT MODULE]: Found no land object for {0} at position ({1}, {2}) on {3}", | ||
190 | // obj.Name, pos.X, pos.Y, m_Scene.RegionInfo.RegionName); | ||
191 | |||
192 | return; | ||
193 | } | ||
194 | |||
166 | LandData landData = landObject.LandData; | 195 | LandData landData = landObject.LandData; |
196 | |||
197 | // m_log.DebugFormat( | ||
198 | // "[PRIM COUNT MODULE]: Adding object {0} with {1} parts to prim count for parcel {2} on {3}", | ||
199 | // obj.Name, obj.Parts.Length, landData.Name, m_Scene.RegionInfo.RegionName); | ||
200 | |||
201 | // m_log.DebugFormat( | ||
202 | // "[PRIM COUNT MODULE]: Object {0} is owned by {1} over land owned by {2}", | ||
203 | // obj.Name, obj.OwnerID, landData.OwnerID); | ||
167 | 204 | ||
168 | ParcelCounts parcelCounts; | 205 | ParcelCounts parcelCounts; |
169 | if (m_ParcelCounts.TryGetValue(landData.GlobalID, out parcelCounts)) | 206 | if (m_ParcelCounts.TryGetValue(landData.GlobalID, out parcelCounts)) |
@@ -177,23 +214,28 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
177 | else | 214 | else |
178 | parcelCounts.Users[obj.OwnerID] = partCount; | 215 | parcelCounts.Users[obj.OwnerID] = partCount; |
179 | 216 | ||
180 | if (landData.IsGroupOwned) | 217 | if (obj.IsSelected) |
181 | { | 218 | { |
182 | if (obj.OwnerID == landData.GroupID) | 219 | parcelCounts.Selected += partCount; |
183 | parcelCounts.Owner += partCount; | ||
184 | else if (obj.GroupID == landData.GroupID) | ||
185 | parcelCounts.Group += partCount; | ||
186 | else | ||
187 | parcelCounts.Others += partCount; | ||
188 | } | 220 | } |
189 | else | 221 | else |
190 | { | 222 | { |
191 | if (obj.OwnerID == landData.OwnerID) | 223 | if (landData.IsGroupOwned) |
192 | parcelCounts.Owner += partCount; | 224 | { |
193 | else if (obj.GroupID == landData.GroupID) | 225 | if (obj.OwnerID == landData.GroupID) |
194 | parcelCounts.Group += partCount; | 226 | parcelCounts.Owner += partCount; |
227 | else if (landData.GroupID != UUID.Zero && obj.GroupID == landData.GroupID) | ||
228 | parcelCounts.Group += partCount; | ||
229 | else | ||
230 | parcelCounts.Others += partCount; | ||
231 | } | ||
195 | else | 232 | else |
196 | parcelCounts.Others += partCount; | 233 | { |
234 | if (obj.OwnerID == landData.OwnerID) | ||
235 | parcelCounts.Owner += partCount; | ||
236 | else | ||
237 | parcelCounts.Others += partCount; | ||
238 | } | ||
197 | } | 239 | } |
198 | } | 240 | } |
199 | } | 241 | } |
@@ -201,10 +243,16 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
201 | // NOTE: Call under Taint Lock | 243 | // NOTE: Call under Taint Lock |
202 | private void RemoveObject(SceneObjectGroup obj) | 244 | private void RemoveObject(SceneObjectGroup obj) |
203 | { | 245 | { |
246 | // m_log.DebugFormat("[PRIM COUNT MODULE]: Removing object {0} {1} from prim count", obj.Name, obj.UUID); | ||
247 | |||
248 | // Currently this is being done by tainting the count instead. | ||
204 | } | 249 | } |
205 | 250 | ||
206 | public IPrimCounts GetPrimCounts(UUID parcelID) | 251 | public IPrimCounts GetPrimCounts(UUID parcelID) |
207 | { | 252 | { |
253 | // m_log.DebugFormat( | ||
254 | // "[PRIM COUNT MODULE]: GetPrimCounts for parcel {0} in {1}", parcelID, m_Scene.RegionInfo.RegionName); | ||
255 | |||
208 | PrimCounts primCounts; | 256 | PrimCounts primCounts; |
209 | 257 | ||
210 | lock (m_PrimCounts) | 258 | lock (m_PrimCounts) |
@@ -218,8 +266,16 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
218 | return primCounts; | 266 | return primCounts; |
219 | } | 267 | } |
220 | 268 | ||
269 | |||
270 | /// <summary> | ||
271 | /// Get the number of prims on the parcel that are owned by the parcel owner. | ||
272 | /// </summary> | ||
273 | /// <param name="parcelID"></param> | ||
274 | /// <returns></returns> | ||
221 | public int GetOwnerCount(UUID parcelID) | 275 | public int GetOwnerCount(UUID parcelID) |
222 | { | 276 | { |
277 | int count = 0; | ||
278 | |||
223 | lock (m_TaintLock) | 279 | lock (m_TaintLock) |
224 | { | 280 | { |
225 | if (m_Tainted) | 281 | if (m_Tainted) |
@@ -227,13 +283,25 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
227 | 283 | ||
228 | ParcelCounts counts; | 284 | ParcelCounts counts; |
229 | if (m_ParcelCounts.TryGetValue(parcelID, out counts)) | 285 | if (m_ParcelCounts.TryGetValue(parcelID, out counts)) |
230 | return counts.Owner; | 286 | count = counts.Owner; |
231 | } | 287 | } |
232 | return 0; | 288 | |
289 | // m_log.DebugFormat( | ||
290 | // "[PRIM COUNT MODULE]: GetOwnerCount for parcel {0} in {1} returning {2}", | ||
291 | // parcelID, m_Scene.RegionInfo.RegionName, count); | ||
292 | |||
293 | return count; | ||
233 | } | 294 | } |
234 | 295 | ||
296 | /// <summary> | ||
297 | /// Get the number of prims on the parcel that have been set to the group that owns the parcel. | ||
298 | /// </summary> | ||
299 | /// <param name="parcelID"></param> | ||
300 | /// <returns></returns> | ||
235 | public int GetGroupCount(UUID parcelID) | 301 | public int GetGroupCount(UUID parcelID) |
236 | { | 302 | { |
303 | int count = 0; | ||
304 | |||
237 | lock (m_TaintLock) | 305 | lock (m_TaintLock) |
238 | { | 306 | { |
239 | if (m_Tainted) | 307 | if (m_Tainted) |
@@ -241,13 +309,25 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
241 | 309 | ||
242 | ParcelCounts counts; | 310 | ParcelCounts counts; |
243 | if (m_ParcelCounts.TryGetValue(parcelID, out counts)) | 311 | if (m_ParcelCounts.TryGetValue(parcelID, out counts)) |
244 | return counts.Group; | 312 | count = counts.Group; |
245 | } | 313 | } |
246 | return 0; | 314 | |
315 | // m_log.DebugFormat( | ||
316 | // "[PRIM COUNT MODULE]: GetGroupCount for parcel {0} in {1} returning {2}", | ||
317 | // parcelID, m_Scene.RegionInfo.RegionName, count); | ||
318 | |||
319 | return count; | ||
247 | } | 320 | } |
248 | 321 | ||
322 | /// <summary> | ||
323 | /// Get the number of prims on the parcel that are not owned by the parcel owner or set to the parcel group. | ||
324 | /// </summary> | ||
325 | /// <param name="parcelID"></param> | ||
326 | /// <returns></returns> | ||
249 | public int GetOthersCount(UUID parcelID) | 327 | public int GetOthersCount(UUID parcelID) |
250 | { | 328 | { |
329 | int count = 0; | ||
330 | |||
251 | lock (m_TaintLock) | 331 | lock (m_TaintLock) |
252 | { | 332 | { |
253 | if (m_Tainted) | 333 | if (m_Tainted) |
@@ -255,13 +335,83 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
255 | 335 | ||
256 | ParcelCounts counts; | 336 | ParcelCounts counts; |
257 | if (m_ParcelCounts.TryGetValue(parcelID, out counts)) | 337 | if (m_ParcelCounts.TryGetValue(parcelID, out counts)) |
258 | return counts.Others; | 338 | count = counts.Others; |
259 | } | 339 | } |
260 | return 0; | 340 | |
341 | // m_log.DebugFormat( | ||
342 | // "[PRIM COUNT MODULE]: GetOthersCount for parcel {0} in {1} returning {2}", | ||
343 | // parcelID, m_Scene.RegionInfo.RegionName, count); | ||
344 | |||
345 | return count; | ||
261 | } | 346 | } |
347 | |||
348 | /// <summary> | ||
349 | /// Get the number of selected prims. | ||
350 | /// </summary> | ||
351 | /// <param name="parcelID"></param> | ||
352 | /// <returns></returns> | ||
353 | public int GetSelectedCount(UUID parcelID) | ||
354 | { | ||
355 | int count = 0; | ||
356 | |||
357 | lock (m_TaintLock) | ||
358 | { | ||
359 | if (m_Tainted) | ||
360 | Recount(); | ||
262 | 361 | ||
362 | ParcelCounts counts; | ||
363 | if (m_ParcelCounts.TryGetValue(parcelID, out counts)) | ||
364 | count = counts.Selected; | ||
365 | } | ||
366 | |||
367 | // m_log.DebugFormat( | ||
368 | // "[PRIM COUNT MODULE]: GetSelectedCount for parcel {0} in {1} returning {2}", | ||
369 | // parcelID, m_Scene.RegionInfo.RegionName, count); | ||
370 | |||
371 | return count; | ||
372 | } | ||
373 | |||
374 | /// <summary> | ||
375 | /// Get the total count of owner, group and others prims on the parcel. | ||
376 | /// FIXME: Need to do selected prims once this is reimplemented. | ||
377 | /// </summary> | ||
378 | /// <param name="parcelID"></param> | ||
379 | /// <returns></returns> | ||
380 | public int GetTotalCount(UUID parcelID) | ||
381 | { | ||
382 | int count = 0; | ||
383 | |||
384 | lock (m_TaintLock) | ||
385 | { | ||
386 | if (m_Tainted) | ||
387 | Recount(); | ||
388 | |||
389 | ParcelCounts counts; | ||
390 | if (m_ParcelCounts.TryGetValue(parcelID, out counts)) | ||
391 | { | ||
392 | count = counts.Owner; | ||
393 | count += counts.Group; | ||
394 | count += counts.Others; | ||
395 | count += counts.Selected; | ||
396 | } | ||
397 | } | ||
398 | |||
399 | // m_log.DebugFormat( | ||
400 | // "[PRIM COUNT MODULE]: GetTotalCount for parcel {0} in {1} returning {2}", | ||
401 | // parcelID, m_Scene.RegionInfo.RegionName, count); | ||
402 | |||
403 | return count; | ||
404 | } | ||
405 | |||
406 | /// <summary> | ||
407 | /// Get the number of prims that are in the entire simulator for the owner of this parcel. | ||
408 | /// </summary> | ||
409 | /// <param name="parcelID"></param> | ||
410 | /// <returns></returns> | ||
263 | public int GetSimulatorCount(UUID parcelID) | 411 | public int GetSimulatorCount(UUID parcelID) |
264 | { | 412 | { |
413 | int count = 0; | ||
414 | |||
265 | lock (m_TaintLock) | 415 | lock (m_TaintLock) |
266 | { | 416 | { |
267 | if (m_Tainted) | 417 | if (m_Tainted) |
@@ -272,14 +422,27 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
272 | { | 422 | { |
273 | int val; | 423 | int val; |
274 | if (m_SimwideCounts.TryGetValue(owner, out val)) | 424 | if (m_SimwideCounts.TryGetValue(owner, out val)) |
275 | return val; | 425 | count = val; |
276 | } | 426 | } |
277 | } | 427 | } |
278 | return 0; | 428 | |
429 | // m_log.DebugFormat( | ||
430 | // "[PRIM COUNT MODULE]: GetOthersCount for parcel {0} in {1} returning {2}", | ||
431 | // parcelID, m_Scene.RegionInfo.RegionName, count); | ||
432 | |||
433 | return count; | ||
279 | } | 434 | } |
280 | 435 | ||
436 | /// <summary> | ||
437 | /// Get the number of prims that a particular user owns on this parcel. | ||
438 | /// </summary> | ||
439 | /// <param name="parcelID"></param> | ||
440 | /// <param name="userID"></param> | ||
441 | /// <returns></returns> | ||
281 | public int GetUserCount(UUID parcelID, UUID userID) | 442 | public int GetUserCount(UUID parcelID, UUID userID) |
282 | { | 443 | { |
444 | int count = 0; | ||
445 | |||
283 | lock (m_TaintLock) | 446 | lock (m_TaintLock) |
284 | { | 447 | { |
285 | if (m_Tainted) | 448 | if (m_Tainted) |
@@ -290,27 +453,37 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
290 | { | 453 | { |
291 | int val; | 454 | int val; |
292 | if (counts.Users.TryGetValue(userID, out val)) | 455 | if (counts.Users.TryGetValue(userID, out val)) |
293 | return val; | 456 | count = val; |
294 | } | 457 | } |
295 | } | 458 | } |
296 | return 0; | 459 | |
460 | // m_log.DebugFormat( | ||
461 | // "[PRIM COUNT MODULE]: GetUserCount for user {0} in parcel {1} in region {2} returning {3}", | ||
462 | // userID, parcelID, m_Scene.RegionInfo.RegionName, count); | ||
463 | |||
464 | return count; | ||
297 | } | 465 | } |
298 | 466 | ||
299 | // NOTE: This method MUST be called while holding the taint lock! | 467 | // NOTE: This method MUST be called while holding the taint lock! |
300 | private void Recount() | 468 | private void Recount() |
301 | { | 469 | { |
470 | // m_log.DebugFormat("[PRIM COUNT MODULE]: Recounting prims on {0}", m_Scene.RegionInfo.RegionName); | ||
471 | |||
302 | m_OwnerMap.Clear(); | 472 | m_OwnerMap.Clear(); |
303 | m_SimwideCounts.Clear(); | 473 | m_SimwideCounts.Clear(); |
304 | m_ParcelCounts.Clear(); | 474 | m_ParcelCounts.Clear(); |
305 | 475 | ||
306 | List<ILandObject> land = m_Scene.LandChannel.AllParcels(); | 476 | List<ILandObject> land = m_Scene.LandChannel.AllParcels(); |
307 | 477 | ||
308 | foreach (ILandObject l in land) | 478 | foreach (ILandObject l in land) |
309 | { | 479 | { |
310 | LandData landData = l.LandData; | 480 | LandData landData = l.LandData; |
311 | 481 | ||
312 | m_OwnerMap[landData.GlobalID] = landData.OwnerID; | 482 | m_OwnerMap[landData.GlobalID] = landData.OwnerID; |
313 | m_SimwideCounts[landData.OwnerID] = 0; | 483 | m_SimwideCounts[landData.OwnerID] = 0; |
484 | // m_log.DebugFormat( | ||
485 | // "[PRIM COUNT MODULE]: Initializing parcel count for {0} on {1}", | ||
486 | // landData.Name, m_Scene.RegionInfo.RegionName); | ||
314 | m_ParcelCounts[landData.GlobalID] = new ParcelCounts(); | 487 | m_ParcelCounts[landData.GlobalID] = new ParcelCounts(); |
315 | } | 488 | } |
316 | 489 | ||
@@ -322,6 +495,7 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
322 | if (!m_OwnerMap.ContainsKey(k)) | 495 | if (!m_OwnerMap.ContainsKey(k)) |
323 | m_PrimCounts.Remove(k); | 496 | m_PrimCounts.Remove(k); |
324 | } | 497 | } |
498 | |||
325 | m_Tainted = false; | 499 | m_Tainted = false; |
326 | } | 500 | } |
327 | } | 501 | } |
@@ -363,6 +537,22 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
363 | return m_Parent.GetOthersCount(m_ParcelID); | 537 | return m_Parent.GetOthersCount(m_ParcelID); |
364 | } | 538 | } |
365 | } | 539 | } |
540 | |||
541 | public int Selected | ||
542 | { | ||
543 | get | ||
544 | { | ||
545 | return m_Parent.GetSelectedCount(m_ParcelID); | ||
546 | } | ||
547 | } | ||
548 | |||
549 | public int Total | ||
550 | { | ||
551 | get | ||
552 | { | ||
553 | return m_Parent.GetTotalCount(m_ParcelID); | ||
554 | } | ||
555 | } | ||
366 | 556 | ||
367 | public int Simulator | 557 | public int Simulator |
368 | { | 558 | { |
@@ -403,4 +593,4 @@ namespace OpenSim.Region.CoreModules.World.Land | |||
403 | } | 593 | } |
404 | } | 594 | } |
405 | } | 595 | } |
406 | } | 596 | } \ No newline at end of file |