opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 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.Threading;
31 using OpenMetaverse;
32 using OpenMetaverse.Packets;
33 using OpenSim.Framework;
34 using OpenSim.Framework.Communications;
35 using OpenSim.Services.Interfaces;
36  
37 namespace OpenSim.Region.Framework.Scenes
38 {
39 public partial class Scene
40 {
41 /// <summary>
42 /// Send chat to listeners.
43 /// </summary>
44 /// <param name='message'></param>
45 /// <param name='type'>/param>
46 /// <param name='channel'></param>
47 /// <param name='fromPos'></param>
48 /// <param name='fromName'></param>
49 /// <param name='fromID'></param>
50 /// <param name='targetID'></param>
51 /// <param name='fromAgent'></param>
52 /// <param name='broadcast'></param>
53 protected void SimChat(byte[] message, ChatTypeEnum type, int channel, Vector3 fromPos, string fromName,
54 UUID fromID, UUID targetID, bool fromAgent, bool broadcast)
55 {
56 OSChatMessage args = new OSChatMessage();
57  
58 args.Message = Utils.BytesToString(message);
59 args.Channel = channel;
60 args.Type = type;
61 args.Position = fromPos;
62 args.SenderUUID = fromID;
63 args.Scene = this;
64  
65 if (fromAgent)
66 {
67 ScenePresence user = GetScenePresence(fromID);
68 if (user != null)
69 args.Sender = user.ControllingClient;
70 }
71 else
72 {
73 SceneObjectPart obj = GetSceneObjectPart(fromID);
74 args.SenderObject = obj;
75 }
76  
77 args.From = fromName;
78 args.TargetUUID = targetID;
79  
80 // m_log.DebugFormat(
81 // "[SCENE]: Sending message {0} on channel {1}, type {2} from {3}, broadcast {4}",
82 // args.Message.Replace("\n", "\\n"), args.Channel, args.Type, fromName, broadcast);
83  
84 if (broadcast)
85 EventManager.TriggerOnChatBroadcast(this, args);
86 else
87 EventManager.TriggerOnChatFromWorld(this, args);
88 }
89  
90 protected void SimChat(byte[] message, ChatTypeEnum type, int channel, Vector3 fromPos, string fromName,
91 UUID fromID, bool fromAgent, bool broadcast)
92 {
93 SimChat(message, type, channel, fromPos, fromName, fromID, UUID.Zero, fromAgent, broadcast);
94 }
95  
96 /// <summary>
97 ///
98 /// </summary>
99 /// <param name="message"></param>
100 /// <param name="type"></param>
101 /// <param name="fromPos"></param>
102 /// <param name="fromName"></param>
103 /// <param name="fromAgentID"></param>
104 public void SimChat(byte[] message, ChatTypeEnum type, int channel, Vector3 fromPos, string fromName,
105 UUID fromID, bool fromAgent)
106 {
107 SimChat(message, type, channel, fromPos, fromName, fromID, fromAgent, false);
108 }
109  
110 public void SimChat(string message, ChatTypeEnum type, Vector3 fromPos, string fromName, UUID fromID, bool fromAgent)
111 {
112 SimChat(Utils.StringToBytes(message), type, 0, fromPos, fromName, fromID, fromAgent);
113 }
114  
115 public void SimChat(string message, string fromName)
116 {
117 SimChat(message, ChatTypeEnum.Broadcast, Vector3.Zero, fromName, UUID.Zero, false);
118 }
119  
120 /// <summary>
121 ///
122 /// </summary>
123 /// <param name="message"></param>
124 /// <param name="type"></param>
125 /// <param name="fromPos"></param>
126 /// <param name="fromName"></param>
127 /// <param name="fromAgentID"></param>
128 public void SimChatBroadcast(byte[] message, ChatTypeEnum type, int channel, Vector3 fromPos, string fromName,
129 UUID fromID, bool fromAgent)
130 {
131 SimChat(message, type, channel, fromPos, fromName, fromID, fromAgent, true);
132 }
133 /// <summary>
134 ///
135 /// </summary>
136 /// <param name="message"></param>
137 /// <param name="type"></param>
138 /// <param name="fromPos"></param>
139 /// <param name="fromName"></param>
140 /// <param name="fromAgentID"></param>
141 /// <param name="targetID"></param>
142 public void SimChatToAgent(UUID targetID, byte[] message, Vector3 fromPos, string fromName, UUID fromID, bool fromAgent)
143 {
144 SimChat(message, ChatTypeEnum.Say, 0, fromPos, fromName, fromID, targetID, fromAgent, false);
145 }
146  
147 /// <summary>
148 /// Invoked when the client requests a prim.
149 /// </summary>
150 /// <param name="primLocalID"></param>
151 /// <param name="remoteClient"></param>
152 public void RequestPrim(uint primLocalID, IClientAPI remoteClient)
153 {
154 SceneObjectGroup sog = GetGroupByPrim(primLocalID);
155  
156 if (sog != null)
157 sog.SendFullUpdateToClient(remoteClient);
158 }
159  
160 /// <summary>
161 /// Invoked when the client selects a prim.
162 /// </summary>
163 /// <param name="primLocalID"></param>
164 /// <param name="remoteClient"></param>
165 public void SelectPrim(uint primLocalID, IClientAPI remoteClient)
166 {
167 SceneObjectPart part = GetSceneObjectPart(primLocalID);
168  
169 if (null == part)
170 return;
171  
172 if (part.IsRoot)
173 {
174 SceneObjectGroup sog = part.ParentGroup;
175 sog.SendPropertiesToClient(remoteClient);
176 sog.IsSelected = true;
177  
178 // A prim is only tainted if it's allowed to be edited by the person clicking it.
179 if (Permissions.CanEditObject(sog.UUID, remoteClient.AgentId)
180 || Permissions.CanMoveObject(sog.UUID, remoteClient.AgentId))
181 {
182 EventManager.TriggerParcelPrimCountTainted();
183 }
184 }
185 else
186 {
187 part.SendPropertiesToClient(remoteClient);
188 }
189 }
190  
191 /// <summary>
192 /// Handle the update of an object's user group.
193 /// </summary>
194 /// <param name="remoteClient"></param>
195 /// <param name="groupID"></param>
196 /// <param name="objectLocalID"></param>
197 /// <param name="Garbage"></param>
198 private void HandleObjectGroupUpdate(
199 IClientAPI remoteClient, UUID groupID, uint objectLocalID, UUID Garbage)
200 {
201 if (m_groupsModule == null)
202 return;
203  
204 // XXX: Might be better to get rid of this special casing and have GetMembershipData return something
205 // reasonable for a UUID.Zero group.
206 if (groupID != UUID.Zero)
207 {
208 GroupMembershipData gmd = m_groupsModule.GetMembershipData(groupID, remoteClient.AgentId);
209  
210 if (gmd == null)
211 {
212 // m_log.WarnFormat(
213 // "[GROUPS]: User {0} is not a member of group {1} so they can't update {2} to this group",
214 // remoteClient.Name, GroupID, objectLocalID);
215  
216 return;
217 }
218 }
219  
220 SceneObjectGroup so = ((Scene)remoteClient.Scene).GetGroupByPrim(objectLocalID);
221 if (so != null)
222 {
223 if (so.OwnerID == remoteClient.AgentId)
224 {
225 so.SetGroup(groupID, remoteClient);
226 }
227 }
228 }
229  
230 /// <summary>
231 /// Handle the deselection of a prim from the client.
232 /// </summary>
233 /// <param name="primLocalID"></param>
234 /// <param name="remoteClient"></param>
235 public void DeselectPrim(uint primLocalID, IClientAPI remoteClient)
236 {
237 SceneObjectPart part = GetSceneObjectPart(primLocalID);
238 if (part == null)
239 return;
240  
241 // A deselect packet contains all the local prims being deselected. However, since selection is still
242 // group based we only want the root prim to trigger a full update - otherwise on objects with many prims
243 // we end up sending many duplicate ObjectUpdates
244 if (part.ParentGroup.RootPart.LocalId != part.LocalId)
245 return;
246  
247 // This is wrong, wrong, wrong. Selection should not be
248 // handled by group, but by prim. Legacy cruft.
249 // TODO: Make selection flagging per prim!
250 //
251 part.ParentGroup.IsSelected = false;
252  
253 part.ParentGroup.ScheduleGroupForFullUpdate();
254  
255 // If it's not an attachment, and we are allowed to move it,
256 // then we might have done so. If we moved across a parcel
257 // boundary, we will need to recount prims on the parcels.
258 // For attachments, that makes no sense.
259 //
260 if (!part.ParentGroup.IsAttachment)
261 {
262 if (Permissions.CanEditObject(
263 part.UUID, remoteClient.AgentId)
264 || Permissions.CanMoveObject(
265 part.UUID, remoteClient.AgentId))
266 EventManager.TriggerParcelPrimCountTainted();
267 }
268 }
269  
270 public virtual void ProcessMoneyTransferRequest(UUID source, UUID destination, int amount,
271 int transactiontype, string description)
272 {
273 EventManager.MoneyTransferArgs args = new EventManager.MoneyTransferArgs(source, destination, amount,
274 transactiontype, description);
275  
276 EventManager.TriggerMoneyTransfer(this, args);
277 }
278  
279 public virtual void ProcessParcelBuy(UUID agentId, UUID groupId, bool final, bool groupOwned,
280 bool removeContribution, int parcelLocalID, int parcelArea, int parcelPrice, bool authenticated)
281 {
282 EventManager.LandBuyArgs args = new EventManager.LandBuyArgs(agentId, groupId, final, groupOwned,
283 removeContribution, parcelLocalID, parcelArea,
284 parcelPrice, authenticated);
285  
286 // First, allow all validators a stab at it
287 m_eventManager.TriggerValidateLandBuy(this, args);
288  
289 // Then, check validation and transfer
290 m_eventManager.TriggerLandBuy(this, args);
291 }
292  
293 public virtual void ProcessObjectGrab(uint localID, Vector3 offsetPos, IClientAPI remoteClient, List<SurfaceTouchEventArgs> surfaceArgs)
294 {
295 SceneObjectPart part = GetSceneObjectPart(localID);
296  
297 if (part == null)
298 return;
299  
300 SceneObjectGroup obj = part.ParentGroup;
301  
302 SurfaceTouchEventArgs surfaceArg = null;
303 if (surfaceArgs != null && surfaceArgs.Count > 0)
304 surfaceArg = surfaceArgs[0];
305  
306 // Currently only grab/touch for the single prim
307 // the client handles rez correctly
308 obj.ObjectGrabHandler(localID, offsetPos, remoteClient);
309  
310 // If the touched prim handles touches, deliver it
311 // If not, deliver to root prim
312 if ((part.ScriptEvents & scriptEvents.touch_start) != 0)
313 EventManager.TriggerObjectGrab(part.LocalId, 0, part.OffsetPosition, remoteClient, surfaceArg);
314  
315 // Deliver to the root prim if the touched prim doesn't handle touches
316 // or if we're meant to pass on touches anyway. Don't send to root prim
317 // if prim touched is the root prim as we just did it
318 if (((part.ScriptEvents & scriptEvents.touch_start) == 0) ||
319 (part.PassTouches && (part.LocalId != obj.RootPart.LocalId)))
320 {
321 EventManager.TriggerObjectGrab(obj.RootPart.LocalId, part.LocalId, part.OffsetPosition, remoteClient, surfaceArg);
322 }
323 }
324  
325 public virtual void ProcessObjectGrabUpdate(
326 UUID objectID, Vector3 offset, Vector3 pos, IClientAPI remoteClient, List<SurfaceTouchEventArgs> surfaceArgs)
327 {
328 SceneObjectPart part = GetSceneObjectPart(objectID);
329 if (part == null)
330 return;
331  
332 SceneObjectGroup obj = part.ParentGroup;
333  
334 SurfaceTouchEventArgs surfaceArg = null;
335 if (surfaceArgs != null && surfaceArgs.Count > 0)
336 surfaceArg = surfaceArgs[0];
337  
338 // If the touched prim handles touches, deliver it
339 // If not, deliver to root prim
340 if ((part.ScriptEvents & scriptEvents.touch) != 0)
341 EventManager.TriggerObjectGrabbing(part.LocalId, 0, part.OffsetPosition, remoteClient, surfaceArg);
342 // Deliver to the root prim if the touched prim doesn't handle touches
343 // or if we're meant to pass on touches anyway. Don't send to root prim
344 // if prim touched is the root prim as we just did it
345 if (((part.ScriptEvents & scriptEvents.touch) == 0) ||
346 (part.PassTouches && (part.LocalId != obj.RootPart.LocalId)))
347 {
348 EventManager.TriggerObjectGrabbing(obj.RootPart.LocalId, part.LocalId, part.OffsetPosition, remoteClient, surfaceArg);
349 }
350 }
351  
352 public virtual void ProcessObjectDeGrab(uint localID, IClientAPI remoteClient, List<SurfaceTouchEventArgs> surfaceArgs)
353 {
354 SceneObjectPart part = GetSceneObjectPart(localID);
355 if (part == null)
356 return;
357  
358 SceneObjectGroup obj = part.ParentGroup;
359  
360 SurfaceTouchEventArgs surfaceArg = null;
361 if (surfaceArgs != null && surfaceArgs.Count > 0)
362 surfaceArg = surfaceArgs[0];
363  
364 // If the touched prim handles touches, deliver it
365 // If not, deliver to root prim
366 if ((part.ScriptEvents & scriptEvents.touch_end) != 0)
367 EventManager.TriggerObjectDeGrab(part.LocalId, 0, remoteClient, surfaceArg);
368 else
369 EventManager.TriggerObjectDeGrab(obj.RootPart.LocalId, part.LocalId, remoteClient, surfaceArg);
370 }
371  
372 public void ProcessScriptReset(IClientAPI remoteClient, UUID objectID,
373 UUID itemID)
374 {
375 SceneObjectPart part=GetSceneObjectPart(objectID);
376 if (part == null)
377 return;
378  
379 if (Permissions.CanResetScript(objectID, itemID, remoteClient.AgentId))
380 {
381 EventManager.TriggerScriptReset(part.LocalId, itemID);
382 }
383 }
384  
385 void ProcessViewerEffect(IClientAPI remoteClient, List<ViewerEffectEventHandlerArg> args)
386 {
387 // TODO: don't create new blocks if recycling an old packet
388 bool discardableEffects = true;
389 ViewerEffectPacket.EffectBlock[] effectBlockArray = new ViewerEffectPacket.EffectBlock[args.Count];
390 for (int i = 0; i < args.Count; i++)
391 {
392 ViewerEffectPacket.EffectBlock effect = new ViewerEffectPacket.EffectBlock();
393 effect.AgentID = args[i].AgentID;
394 effect.Color = args[i].Color;
395 effect.Duration = args[i].Duration;
396 effect.ID = args[i].ID;
397 effect.Type = args[i].Type;
398 effect.TypeData = args[i].TypeData;
399 effectBlockArray[i] = effect;
400  
401 if ((EffectType)effect.Type != EffectType.LookAt && (EffectType)effect.Type != EffectType.Beam)
402 discardableEffects = false;
403  
404 //m_log.DebugFormat("[YYY]: VE {0} {1} {2}", effect.AgentID, effect.Duration, (EffectType)effect.Type);
405 }
406  
407 ForEachScenePresence(sp =>
408 {
409 if (sp.ControllingClient.AgentId != remoteClient.AgentId)
410 {
411 if (!discardableEffects ||
412 (discardableEffects && ShouldSendDiscardableEffect(remoteClient, sp)))
413 {
414 //m_log.DebugFormat("[YYY]: Sending to {0}", sp.UUID);
415 sp.ControllingClient.SendViewerEffect(effectBlockArray);
416 }
417 //else
418 // m_log.DebugFormat("[YYY]: Not sending to {0}", sp.UUID);
419 }
420 });
421 }
422  
423 private bool ShouldSendDiscardableEffect(IClientAPI thisClient, ScenePresence other)
424 {
425 return Vector3.Distance(other.CameraPosition, thisClient.SceneAgent.AbsolutePosition) < 10;
426 }
427  
428 /// <summary>
429 /// Tell the client about the various child items and folders contained in the requested folder.
430 /// </summary>
431 /// <param name="remoteClient"></param>
432 /// <param name="folderID"></param>
433 /// <param name="ownerID"></param>
434 /// <param name="fetchFolders"></param>
435 /// <param name="fetchItems"></param>
436 /// <param name="sortOrder"></param>
437 public void HandleFetchInventoryDescendents(IClientAPI remoteClient, UUID folderID, UUID ownerID,
438 bool fetchFolders, bool fetchItems, int sortOrder)
439 {
440 // m_log.DebugFormat(
441 // "[USER INVENTORY]: HandleFetchInventoryDescendents() for {0}, folder={1}, fetchFolders={2}, fetchItems={3}, sortOrder={4}",
442 // remoteClient.Name, folderID, fetchFolders, fetchItems, sortOrder);
443  
444 if (folderID == UUID.Zero)
445 return;
446  
447 // FIXME MAYBE: We're not handling sortOrder!
448  
449 // TODO: This code for looking in the folder for the library should be folded somewhere else
450 // so that this class doesn't have to know the details (and so that multiple libraries, etc.
451 // can be handled transparently).
452 InventoryFolderImpl fold = null;
453 if (LibraryService != null && LibraryService.LibraryRootFolder != null)
454 {
455 if ((fold = LibraryService.LibraryRootFolder.FindFolder(folderID)) != null)
456 {
457 remoteClient.SendInventoryFolderDetails(
458 fold.Owner, folderID, fold.RequestListOfItems(),
459 fold.RequestListOfFolders(), fold.Version, fetchFolders, fetchItems);
460 return;
461 }
462 }
463  
464 // We're going to send the reply async, because there may be
465 // an enormous quantity of packets -- basically the entire inventory!
466 // We don't want to block the client thread while all that is happening.
467 SendInventoryDelegate d = SendInventoryAsync;
468 d.BeginInvoke(remoteClient, folderID, ownerID, fetchFolders, fetchItems, sortOrder, SendInventoryComplete, d);
469 }
470  
471 delegate void SendInventoryDelegate(IClientAPI remoteClient, UUID folderID, UUID ownerID, bool fetchFolders, bool fetchItems, int sortOrder);
472  
473 void SendInventoryAsync(IClientAPI remoteClient, UUID folderID, UUID ownerID, bool fetchFolders, bool fetchItems, int sortOrder)
474 {
475 SendInventoryUpdate(remoteClient, new InventoryFolderBase(folderID), fetchFolders, fetchItems);
476 }
477  
478 void SendInventoryComplete(IAsyncResult iar)
479 {
480 SendInventoryDelegate d = (SendInventoryDelegate)iar.AsyncState;
481 d.EndInvoke(iar);
482 }
483  
484 /// <summary>
485 /// Handle an inventory folder creation request from the client.
486 /// </summary>
487 /// <param name="remoteClient"></param>
488 /// <param name="folderID"></param>
489 /// <param name="folderType"></param>
490 /// <param name="folderName"></param>
491 /// <param name="parentID"></param>
492 public void HandleCreateInventoryFolder(IClientAPI remoteClient, UUID folderID, ushort folderType,
493 string folderName, UUID parentID)
494 {
495 InventoryFolderBase folder = new InventoryFolderBase(folderID, folderName, remoteClient.AgentId, (short)folderType, parentID, 1);
496 if (!InventoryService.AddFolder(folder))
497 {
498 m_log.WarnFormat(
499 "[AGENT INVENTORY]: Failed to create folder for user {0} {1}",
500 remoteClient.Name, remoteClient.AgentId);
501 }
502 }
503  
504 /// <summary>
505 /// Handle a client request to update the inventory folder
506 /// </summary>
507 ///
508 /// FIXME: We call add new inventory folder because in the data layer, we happen to use an SQL REPLACE
509 /// so this will work to rename an existing folder. Needless to say, to rely on this is very confusing,
510 /// and needs to be changed.
511 ///
512 /// <param name="remoteClient"></param>
513 /// <param name="folderID"></param>
514 /// <param name="type"></param>
515 /// <param name="name"></param>
516 /// <param name="parentID"></param>
517 public void HandleUpdateInventoryFolder(IClientAPI remoteClient, UUID folderID, ushort type, string name,
518 UUID parentID)
519 {
520 // m_log.DebugFormat(
521 // "[AGENT INVENTORY]: Updating inventory folder {0} {1} for {2} {3}", folderID, name, remoteClient.Name, remoteClient.AgentId);
522  
523 InventoryFolderBase folder = new InventoryFolderBase(folderID, remoteClient.AgentId);
524 folder = InventoryService.GetFolder(folder);
525 if (folder != null)
526 {
527 folder.Name = name;
528 folder.Type = (short)type;
529 folder.ParentID = parentID;
530 if (!InventoryService.UpdateFolder(folder))
531 {
532 m_log.ErrorFormat(
533 "[AGENT INVENTORY]: Failed to update folder for user {0} {1}",
534 remoteClient.Name, remoteClient.AgentId);
535 }
536 }
537 }
538  
539 public void HandleMoveInventoryFolder(IClientAPI remoteClient, UUID folderID, UUID parentID)
540 {
541 InventoryFolderBase folder = new InventoryFolderBase(folderID, remoteClient.AgentId);
542 folder = InventoryService.GetFolder(folder);
543 if (folder != null)
544 {
545 folder.ParentID = parentID;
546 if (!InventoryService.MoveFolder(folder))
547 m_log.WarnFormat("[AGENT INVENTORY]: could not move folder {0}", folderID);
548 else
549 m_log.DebugFormat("[AGENT INVENTORY]: folder {0} moved to parent {1}", folderID, parentID);
550 }
551 else
552 {
553 m_log.WarnFormat("[AGENT INVENTORY]: request to move folder {0} but folder not found", folderID);
554 }
555 }
556  
557 delegate void PurgeFolderDelegate(UUID userID, UUID folder);
558  
559 /// <summary>
560 /// This should delete all the items and folders in the given directory.
561 /// </summary>
562 /// <param name="remoteClient"></param>
563 /// <param name="folderID"></param>
564 public void HandlePurgeInventoryDescendents(IClientAPI remoteClient, UUID folderID)
565 {
566 PurgeFolderDelegate d = PurgeFolderAsync;
567 try
568 {
569 d.BeginInvoke(remoteClient.AgentId, folderID, PurgeFolderCompleted, d);
570 }
571 catch (Exception e)
572 {
573 m_log.WarnFormat("[AGENT INVENTORY]: Exception on purge folder for user {0}: {1}", remoteClient.AgentId, e.Message);
574 }
575 }
576  
577 private void PurgeFolderAsync(UUID userID, UUID folderID)
578 {
579 InventoryFolderBase folder = new InventoryFolderBase(folderID, userID);
580  
581 if (InventoryService.PurgeFolder(folder))
582 m_log.DebugFormat("[AGENT INVENTORY]: folder {0} purged successfully", folderID);
583 else
584 m_log.WarnFormat("[AGENT INVENTORY]: could not purge folder {0}", folderID);
585 }
586  
587 private void PurgeFolderCompleted(IAsyncResult iar)
588 {
589 PurgeFolderDelegate d = (PurgeFolderDelegate)iar.AsyncState;
590 d.EndInvoke(iar);
591 }
592 }
593 }