corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) 2006-2014, openmetaverse.org
3 * All rights reserved.
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 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26  
27 using System;
28 using System.Collections.Generic;
29 using System.IO;
30 using System.Runtime.Serialization.Formatters.Binary;
31 using System.Runtime.Serialization;
32  
33 namespace OpenMetaverse
34 {
35 /// <summary>
36 /// Exception class to identify inventory exceptions
37 /// </summary>
38 public class InventoryException : Exception
39 {
40 public InventoryException(string message)
41 : base(message) { }
42 }
43  
44 /// <summary>
45 /// Responsible for maintaining inventory structure. Inventory constructs nodes
46 /// and manages node children as is necessary to maintain a coherant hirarchy.
47 /// Other classes should not manipulate or create InventoryNodes explicitly. When
48 /// A node's parent changes (when a folder is moved, for example) simply pass
49 /// Inventory the updated InventoryFolder and it will make the appropriate changes
50 /// to its internal representation.
51 /// </summary>
52 public class Inventory
53 {
54  
55 /// <summary>The event subscribers, null of no subscribers</summary>
56 private EventHandler<InventoryObjectUpdatedEventArgs> m_InventoryObjectUpdated;
57  
58 ///<summary>Raises the InventoryObjectUpdated Event</summary>
59 /// <param name="e">A InventoryObjectUpdatedEventArgs object containing
60 /// the data sent from the simulator</param>
61 protected virtual void OnInventoryObjectUpdated(InventoryObjectUpdatedEventArgs e)
62 {
63 EventHandler<InventoryObjectUpdatedEventArgs> handler = m_InventoryObjectUpdated;
64 if (handler != null)
65 handler(this, e);
66 }
67  
68 /// <summary>Thread sync lock object</summary>
69 private readonly object m_InventoryObjectUpdatedLock = new object();
70  
71 /// <summary>Raised when the simulator sends us data containing
72 /// ...</summary>
73 public event EventHandler<InventoryObjectUpdatedEventArgs> InventoryObjectUpdated
74 {
75 add { lock (m_InventoryObjectUpdatedLock) { m_InventoryObjectUpdated += value; } }
76 remove { lock (m_InventoryObjectUpdatedLock) { m_InventoryObjectUpdated -= value; } }
77 }
78  
79 /// <summary>The event subscribers, null of no subscribers</summary>
80 private EventHandler<InventoryObjectRemovedEventArgs> m_InventoryObjectRemoved;
81  
82 ///<summary>Raises the InventoryObjectRemoved Event</summary>
83 /// <param name="e">A InventoryObjectRemovedEventArgs object containing
84 /// the data sent from the simulator</param>
85 protected virtual void OnInventoryObjectRemoved(InventoryObjectRemovedEventArgs e)
86 {
87 EventHandler<InventoryObjectRemovedEventArgs> handler = m_InventoryObjectRemoved;
88 if (handler != null)
89 handler(this, e);
90 }
91  
92 /// <summary>Thread sync lock object</summary>
93 private readonly object m_InventoryObjectRemovedLock = new object();
94  
95 /// <summary>Raised when the simulator sends us data containing
96 /// ...</summary>
97 public event EventHandler<InventoryObjectRemovedEventArgs> InventoryObjectRemoved
98 {
99 add { lock (m_InventoryObjectRemovedLock) { m_InventoryObjectRemoved += value; } }
100 remove { lock (m_InventoryObjectRemovedLock) { m_InventoryObjectRemoved -= value; } }
101 }
102  
103 /// <summary>The event subscribers, null of no subscribers</summary>
104 private EventHandler<InventoryObjectAddedEventArgs> m_InventoryObjectAdded;
105  
106 ///<summary>Raises the InventoryObjectAdded Event</summary>
107 /// <param name="e">A InventoryObjectAddedEventArgs object containing
108 /// the data sent from the simulator</param>
109 protected virtual void OnInventoryObjectAdded(InventoryObjectAddedEventArgs e)
110 {
111 EventHandler<InventoryObjectAddedEventArgs> handler = m_InventoryObjectAdded;
112 if (handler != null)
113 handler(this, e);
114 }
115  
116 /// <summary>Thread sync lock object</summary>
117 private readonly object m_InventoryObjectAddedLock = new object();
118  
119 /// <summary>Raised when the simulator sends us data containing
120 /// ...</summary>
121 public event EventHandler<InventoryObjectAddedEventArgs> InventoryObjectAdded
122 {
123 add { lock (m_InventoryObjectAddedLock) { m_InventoryObjectAdded += value; } }
124 remove { lock (m_InventoryObjectAddedLock) { m_InventoryObjectAdded -= value; } }
125 }
126  
127 /// <summary>
128 /// The root folder of this avatars inventory
129 /// </summary>
130 public InventoryFolder RootFolder
131 {
132 get { return RootNode.Data as InventoryFolder; }
133 set
134 {
135 UpdateNodeFor(value);
136 _RootNode = Items[value.UUID];
137 }
138 }
139  
140 /// <summary>
141 /// The default shared library folder
142 /// </summary>
143 public InventoryFolder LibraryFolder
144 {
145 get { return LibraryRootNode.Data as InventoryFolder; }
146 set
147 {
148 UpdateNodeFor(value);
149 _LibraryRootNode = Items[value.UUID];
150 }
151 }
152  
153 private InventoryNode _LibraryRootNode;
154 private InventoryNode _RootNode;
155  
156 /// <summary>
157 /// The root node of the avatars inventory
158 /// </summary>
159 public InventoryNode RootNode
160 {
161 get { return _RootNode; }
162 }
163  
164 /// <summary>
165 /// The root node of the default shared library
166 /// </summary>
167 public InventoryNode LibraryRootNode
168 {
169 get { return _LibraryRootNode; }
170 }
171  
172 public UUID Owner {
173 get { return _Owner; }
174 }
175  
176 private UUID _Owner;
177  
178 private GridClient Client;
179 //private InventoryManager Manager;
180 public Dictionary<UUID, InventoryNode> Items = new Dictionary<UUID, InventoryNode>();
181  
182 public Inventory(GridClient client, InventoryManager manager)
183 : this(client, manager, client.Self.AgentID) { }
184  
185 public Inventory(GridClient client, InventoryManager manager, UUID owner)
186 {
187 Client = client;
188 //Manager = manager;
189 _Owner = owner;
190 if (owner == UUID.Zero)
191 Logger.Log("Inventory owned by nobody!", Helpers.LogLevel.Warning, Client);
192 Items = new Dictionary<UUID, InventoryNode>();
193 }
194  
195 public List<InventoryBase> GetContents(InventoryFolder folder)
196 {
197 return GetContents(folder.UUID);
198 }
199  
200 /// <summary>
201 /// Returns the contents of the specified folder
202 /// </summary>
203 /// <param name="folder">A folder's UUID</param>
204 /// <returns>The contents of the folder corresponding to <code>folder</code></returns>
205 /// <exception cref="InventoryException">When <code>folder</code> does not exist in the inventory</exception>
206 public List<InventoryBase> GetContents(UUID folder)
207 {
208 InventoryNode folderNode;
209 if (!Items.TryGetValue(folder, out folderNode))
210 throw new InventoryException("Unknown folder: " + folder);
211 lock (folderNode.Nodes.SyncRoot)
212 {
213 List<InventoryBase> contents = new List<InventoryBase>(folderNode.Nodes.Count);
214 foreach (InventoryNode node in folderNode.Nodes.Values)
215 {
216 contents.Add(node.Data);
217 }
218 return contents;
219 }
220 }
221  
222 /// <summary>
223 /// Updates the state of the InventoryNode and inventory data structure that
224 /// is responsible for the InventoryObject. If the item was previously not added to inventory,
225 /// it adds the item, and updates structure accordingly. If it was, it updates the
226 /// InventoryNode, changing the parent node if <code>item.parentUUID</code> does
227 /// not match <code>node.Parent.Data.UUID</code>.
228 ///
229 /// You can not set the inventory root folder using this method
230 /// </summary>
231 /// <param name="item">The InventoryObject to store</param>
232 public void UpdateNodeFor(InventoryBase item)
233 {
234 lock (Items)
235 {
236 InventoryNode itemParent = null;
237 if (item.ParentUUID != UUID.Zero && !Items.TryGetValue(item.ParentUUID, out itemParent))
238 {
239 // OK, we have no data on the parent, let's create a fake one.
240 InventoryFolder fakeParent = new InventoryFolder(item.ParentUUID);
241 fakeParent.DescendentCount = 1; // Dear god, please forgive me.
242 itemParent = new InventoryNode(fakeParent);
243 Items[item.ParentUUID] = itemParent;
244 // Unfortunately, this breaks the nice unified tree
245 // while we're waiting for the parent's data to come in.
246 // As soon as we get the parent, the tree repairs itself.
247 //Logger.DebugLog("Attempting to update inventory child of " +
248 // item.ParentUUID.ToString() + " when we have no local reference to that folder", Client);
249  
250 if (Client.Settings.FETCH_MISSING_INVENTORY)
251 {
252 // Fetch the parent
253 List<UUID> fetchreq = new List<UUID>(1);
254 fetchreq.Add(item.ParentUUID);
255 }
256 }
257  
258 InventoryNode itemNode;
259 if (Items.TryGetValue(item.UUID, out itemNode)) // We're updating.
260 {
261 InventoryNode oldParent = itemNode.Parent;
262 // Handle parent change
263 if (oldParent == null || itemParent == null || itemParent.Data.UUID != oldParent.Data.UUID)
264 {
265 if (oldParent != null)
266 {
267 lock (oldParent.Nodes.SyncRoot)
268 oldParent.Nodes.Remove(item.UUID);
269 }
270 if (itemParent != null)
271 {
272 lock (itemParent.Nodes.SyncRoot)
273 itemParent.Nodes[item.UUID] = itemNode;
274 }
275 }
276  
277 itemNode.Parent = itemParent;
278  
279 if (m_InventoryObjectUpdated != null)
280 {
281 OnInventoryObjectUpdated(new InventoryObjectUpdatedEventArgs(itemNode.Data, item));
282 }
283  
284 itemNode.Data = item;
285 }
286 else // We're adding.
287 {
288 itemNode = new InventoryNode(item, itemParent);
289 Items.Add(item.UUID, itemNode);
290 if (m_InventoryObjectAdded != null)
291 {
292 OnInventoryObjectAdded(new InventoryObjectAddedEventArgs(item));
293 }
294 }
295 }
296 }
297  
298 public InventoryNode GetNodeFor(UUID uuid)
299 {
300 return Items[uuid];
301 }
302  
303 /// <summary>
304 /// Removes the InventoryObject and all related node data from Inventory.
305 /// </summary>
306 /// <param name="item">The InventoryObject to remove.</param>
307 public void RemoveNodeFor(InventoryBase item)
308 {
309 lock (Items)
310 {
311 InventoryNode node;
312 if (Items.TryGetValue(item.UUID, out node))
313 {
314 if (node.Parent != null)
315 lock (node.Parent.Nodes.SyncRoot)
316 node.Parent.Nodes.Remove(item.UUID);
317 Items.Remove(item.UUID);
318 if (m_InventoryObjectRemoved != null)
319 {
320 OnInventoryObjectRemoved(new InventoryObjectRemovedEventArgs(item));
321 }
322 }
323  
324 // In case there's a new parent:
325 InventoryNode newParent;
326 if (Items.TryGetValue(item.ParentUUID, out newParent))
327 {
328 lock (newParent.Nodes.SyncRoot)
329 newParent.Nodes.Remove(item.UUID);
330 }
331 }
332 }
333  
334 /// <summary>
335 /// Used to find out if Inventory contains the InventoryObject
336 /// specified by <code>uuid</code>.
337 /// </summary>
338 /// <param name="uuid">The UUID to check.</param>
339 /// <returns>true if inventory contains uuid, false otherwise</returns>
340 public bool Contains(UUID uuid)
341 {
342 return Items.ContainsKey(uuid);
343 }
344  
345 public bool Contains(InventoryBase obj)
346 {
347 return Contains(obj.UUID);
348 }
349  
350 /// <summary>
351 /// Saves the current inventory structure to a cache file
352 /// </summary>
353 /// <param name="filename">Name of the cache file to save to</param>
354 public void SaveToDisk(string filename)
355 {
356 try
357 {
358 using (Stream stream = File.Open(filename, FileMode.Create))
359 {
360 BinaryFormatter bformatter = new BinaryFormatter();
361 lock (Items)
362 {
363 Logger.Log("Caching " + Items.Count.ToString() + " inventory items to " + filename, Helpers.LogLevel.Info);
364 foreach (KeyValuePair<UUID, InventoryNode> kvp in Items)
365 {
366 bformatter.Serialize(stream, kvp.Value);
367 }
368 }
369 }
370 }
371 catch (Exception e)
372 {
373 Logger.Log("Error saving inventory cache to disk :"+e.Message,Helpers.LogLevel.Error);
374 }
375 }
376  
377 /// <summary>
378 /// Loads in inventory cache file into the inventory structure. Note only valid to call after login has been successful.
379 /// </summary>
380 /// <param name="filename">Name of the cache file to load</param>
381 /// <returns>The number of inventory items sucessfully reconstructed into the inventory node tree</returns>
382 public int RestoreFromDisk(string filename)
383 {
384 List<InventoryNode> nodes = new List<InventoryNode>();
385 int item_count = 0;
386  
387 try
388 {
389 if (!File.Exists(filename))
390 return -1;
391  
392 using (Stream stream = File.Open(filename, FileMode.Open))
393 {
394 BinaryFormatter bformatter = new BinaryFormatter();
395  
396 while (stream.Position < stream.Length)
397 {
398 OpenMetaverse.InventoryNode node = (InventoryNode)bformatter.Deserialize(stream);
399 nodes.Add(node);
400 item_count++;
401 }
402 }
403 }
404 catch (Exception e)
405 {
406 Logger.Log("Error accessing inventory cache file :" + e.Message, Helpers.LogLevel.Error);
407 return -1;
408 }
409  
410 Logger.Log("Read " + item_count.ToString() + " items from inventory cache file", Helpers.LogLevel.Info);
411  
412 item_count = 0;
413 List<InventoryNode> del_nodes = new List<InventoryNode>(); //nodes that we have processed and will delete
414 List<UUID> dirty_folders = new List<UUID>(); // Tainted folders that we will not restore items into
415  
416 // Because we could get child nodes before parents we must itterate around and only add nodes who have
417 // a parent already in the list because we must update both child and parent to link together
418 // But sometimes we have seen orphin nodes due to bad/incomplete data when caching so we have an emergency abort route
419 int stuck = 0;
420  
421 while (nodes.Count != 0 && stuck<5)
422 {
423 foreach (InventoryNode node in nodes)
424 {
425 InventoryNode pnode;
426 if (node.ParentID == UUID.Zero)
427 {
428 //We don't need the root nodes "My Inventory" etc as they will already exist for the correct
429 // user of this cache.
430 del_nodes.Add(node);
431 item_count--;
432 }
433 else if(Items.TryGetValue(node.Data.UUID,out pnode))
434 {
435 //We already have this it must be a folder
436 if (node.Data is InventoryFolder)
437 {
438 InventoryFolder cache_folder = (InventoryFolder)node.Data;
439 InventoryFolder server_folder = (InventoryFolder)pnode.Data;
440  
441 if (cache_folder.Version != server_folder.Version)
442 {
443 Logger.DebugLog("Inventory Cache/Server version mismatch on " + node.Data.Name + " " + cache_folder.Version.ToString() + " vs " + server_folder.Version.ToString());
444 pnode.NeedsUpdate = true;
445 dirty_folders.Add(node.Data.UUID);
446 }
447 else
448 {
449 pnode.NeedsUpdate = false;
450 }
451  
452 del_nodes.Add(node);
453 }
454 }
455 else if (Items.TryGetValue(node.ParentID, out pnode))
456 {
457 if (node.Data != null)
458 {
459 // If node is folder, and it does not exist in skeleton, mark it as
460 // dirty and don't process nodes that belong to it
461 if (node.Data is InventoryFolder && !(Items.ContainsKey(node.Data.UUID)))
462 {
463 dirty_folders.Add(node.Data.UUID);
464 }
465  
466 //Only add new items, this is most likely to be run at login time before any inventory
467 //nodes other than the root are populated. Don't add non existing folders.
468 if (!Items.ContainsKey(node.Data.UUID) && !dirty_folders.Contains(pnode.Data.UUID) && !(node.Data is InventoryFolder))
469 {
470 Items.Add(node.Data.UUID, node);
471 node.Parent = pnode; //Update this node with its parent
472 pnode.Nodes.Add(node.Data.UUID, node); // Add to the parents child list
473 item_count++;
474 }
475 }
476  
477 del_nodes.Add(node);
478 }
479 }
480  
481 if (del_nodes.Count == 0)
482 stuck++;
483 else
484 stuck = 0;
485  
486 //Clean up processed nodes this loop around.
487 foreach (InventoryNode node in del_nodes)
488 nodes.Remove(node);
489  
490 del_nodes.Clear();
491 }
492  
493 Logger.Log("Reassembled " + item_count.ToString() + " items from inventory cache file", Helpers.LogLevel.Info);
494 return item_count;
495 }
496  
497 #region Operators
498  
499 /// <summary>
500 /// By using the bracket operator on this class, the program can get the
501 /// InventoryObject designated by the specified uuid. If the value for the corresponding
502 /// UUID is null, the call is equivelant to a call to <code>RemoveNodeFor(this[uuid])</code>.
503 /// If the value is non-null, it is equivelant to a call to <code>UpdateNodeFor(value)</code>,
504 /// the uuid parameter is ignored.
505 /// </summary>
506 /// <param name="uuid">The UUID of the InventoryObject to get or set, ignored if set to non-null value.</param>
507 /// <returns>The InventoryObject corresponding to <code>uuid</code>.</returns>
508 public InventoryBase this[UUID uuid]
509 {
510 get
511 {
512 InventoryNode node = Items[uuid];
513 return node.Data;
514 }
515 set
516 {
517 if (value != null)
518 {
519 // Log a warning if there is a UUID mismatch, this will cause problems
520 if (value.UUID != uuid)
521 Logger.Log("Inventory[uuid]: uuid " + uuid.ToString() + " is not equal to value.UUID " +
522 value.UUID.ToString(), Helpers.LogLevel.Warning, Client);
523  
524 UpdateNodeFor(value);
525 }
526 else
527 {
528 InventoryNode node;
529 if (Items.TryGetValue(uuid, out node))
530 {
531 RemoveNodeFor(node.Data);
532 }
533 }
534 }
535 }
536  
537 #endregion Operators
538  
539 }
540 #region EventArgs classes
541  
542 public class InventoryObjectUpdatedEventArgs : EventArgs
543 {
544 private readonly InventoryBase m_OldObject;
545 private readonly InventoryBase m_NewObject;
546  
547 public InventoryBase OldObject { get { return m_OldObject; } }
548 public InventoryBase NewObject { get { return m_NewObject; } }
549  
550 public InventoryObjectUpdatedEventArgs(InventoryBase oldObject, InventoryBase newObject)
551 {
552 this.m_OldObject = oldObject;
553 this.m_NewObject = newObject;
554 }
555 }
556  
557 public class InventoryObjectRemovedEventArgs : EventArgs
558 {
559 private readonly InventoryBase m_Obj;
560  
561 public InventoryBase Obj { get { return m_Obj; } }
562 public InventoryObjectRemovedEventArgs(InventoryBase obj)
563 {
564 this.m_Obj = obj;
565 }
566 }
567  
568 public class InventoryObjectAddedEventArgs : EventArgs
569 {
570 private readonly InventoryBase m_Obj;
571  
572 public InventoryBase Obj { get { return m_Obj; } }
573  
574 public InventoryObjectAddedEventArgs(InventoryBase obj)
575 {
576 this.m_Obj = obj;
577 }
578 }
579 #endregion EventArgs
580 }