From 82250be4f1deba5fe02f806977748e6ce3b82bdb Mon Sep 17 00:00:00 2001 From: omegaworks Date: Tue, 12 Aug 2008 05:55:42 +0000 Subject: [PATCH] Added more sophisticated path handling to Inventory. cd command path handling is now available to all TestClient commands. Resolved [TC-12]. Helpers.Implode replaced by String.Join. git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@2077 52acb1d6-8a22-11de-b505-999d5b087335 --- OpenMetaverse/AppearanceManager.cs | 72 ++++++---- OpenMetaverse/Helpers.cs | 24 ---- OpenMetaverse/Inventory.cs | 128 +++++++++++++++--- OpenMetaverse/InventoryManager.cs | 45 +++++- .../Commands/Appearance/WearCommand.cs | 11 +- .../Inventory/ChangeDirectoryCommand.cs | 76 +++-------- .../Commands/Inventory/DeleteFolderCommand.cs | 4 +- .../Commands/Inventory/GiveItemCommand.cs | 24 ++-- .../Commands/Inventory/ListContentsCommand.cs | 45 ++++-- 9 files changed, 264 insertions(+), 165 deletions(-) diff --git a/OpenMetaverse/AppearanceManager.cs b/OpenMetaverse/AppearanceManager.cs index e6e30584..749504ed 100644 --- a/OpenMetaverse/AppearanceManager.cs +++ b/OpenMetaverse/AppearanceManager.cs @@ -364,6 +364,13 @@ namespace OpenMetaverse appearanceThread.Start(); } + public void WearOutfit(InventoryFolder folder, bool bake) + { + _wearOutfitParams = new WearParams(folder, bake); + Thread appearanceThread = new Thread(new ThreadStart(StartWearOutfitFolder)); + appearanceThread.Start(); + } + private void StartWearOutfitFolder() { SendAgentWearablesRequest(); // request current wearables async @@ -385,18 +392,22 @@ namespace OpenMetaverse wearables = null; attachments = null; - if (_folder.GetType() == typeof(string[])) + if (_folder is string[]) { string[] path = (string[])_folder; - folder = Client.Inventory.FindObjectByPath( - Client.Inventory.InventorySkeleton.RootUUID, Client.Self.AgentID, String.Join("/", path), TimeSpan.FromMilliseconds(1000 * 20)); + List results = Client.InventoryStore.InventoryFromPath(path, true); - if (folder == UUID.Zero) + if (results.Count == 0) { Logger.Log("Outfit path " + path + " not found", Helpers.LogLevel.Error, Client); return false; } + folder = results[0].UUID; + } + else if (_folder is InventoryFolder) + { + folder = (_folder as InventoryFolder).UUID; } else folder = (UUID)_folder; @@ -404,39 +415,42 @@ namespace OpenMetaverse wearables = new List(); attachments = new List(); - List folders; - List items; - Client.Inventory.FolderContents(folder, Client.Self.AgentID, - false, true, InventorySortOrder.ByName, TimeSpan.FromMilliseconds(1000 * 20), out items, out folders); - - if (items != null) + InventoryFolder invFolder = Client.InventoryStore[folder] as InventoryFolder; + if (invFolder != null) { - foreach (ItemData ib in items) + if (invFolder.IsStale) + invFolder.DownloadContents(TimeSpan.FromSeconds(20)); + + foreach (InventoryBase ibase in invFolder) { - if (ib.InventoryType == InventoryType.Wearable) + if (ibase is InventoryItem) { - Logger.DebugLog("Adding wearable " + ib.Name, Client); - wearables.Add(ib); - } - else if (ib.InventoryType == InventoryType.Attachment) - { - Logger.DebugLog("Adding attachment (attachment) " + ib.Name, Client); - attachments.Add(ib); - } - else if (ib.InventoryType == InventoryType.Object) - { - Logger.DebugLog("Adding attachment (object) " + ib.Name, Client); - attachments.Add(ib); - } - else - { - Logger.DebugLog("Ignoring inventory item " + ib.Name, Client); + ItemData ib = (ibase as InventoryItem).Data; + if (ib.InventoryType == InventoryType.Wearable) + { + Logger.DebugLog("Adding wearable " + ib.Name, Client); + wearables.Add(ib); + } + else if (ib.InventoryType == InventoryType.Attachment) + { + Logger.DebugLog("Adding attachment (attachment) " + ib.Name, Client); + attachments.Add(ib); + } + else if (ib.InventoryType == InventoryType.Object) + { + Logger.DebugLog("Adding attachment (object) " + ib.Name, Client); + attachments.Add(ib); + } + else + { + Logger.DebugLog("Ignoring inventory item " + ib.Name, Client); + } } } } else { - Logger.Log("Failed to download folder contents of + " + folder.ToString(), + Logger.Log("Failed to download folder contents of + " + folder.ToString(), Helpers.LogLevel.Error, Client); return false; } diff --git a/OpenMetaverse/Helpers.cs b/OpenMetaverse/Helpers.cs index 74294126..ef35a073 100644 --- a/OpenMetaverse/Helpers.cs +++ b/OpenMetaverse/Helpers.cs @@ -1276,30 +1276,6 @@ namespace OpenMetaverse } } - /// - /// Collapses a string[] into a single string, with between - /// between each element. - /// - /// - /// - /// - public static string Implode(string[] array, string between) - { - StringBuilder b = new StringBuilder(array.Length * (array[array.Length / 2].Length + between.Length)); - if (array.Length == 0) - return String.Empty; - b.Append(array[0]); - if (between != null) - b.Append(between); - for (int i = 1; i < array.Length; ++i) - { - if (between != null) - b.Append(between); - b.Append(array[i]); - } - return b.ToString(); - } - #endregion Platform Helper Functions } } diff --git a/OpenMetaverse/Inventory.cs b/OpenMetaverse/Inventory.cs index fdc44e2c..d80ca371 100644 --- a/OpenMetaverse/Inventory.cs +++ b/OpenMetaverse/Inventory.cs @@ -76,8 +76,8 @@ namespace OpenMetaverse protected Dictionary Items; private InventoryManager _Manager; - private UUID _Owner; + private UUID _Owner; /// /// The owner of this inventory. Inventorys can only manage items /// owned by the same agent. @@ -86,14 +86,21 @@ namespace OpenMetaverse get { return _Owner; } private set { _Owner = value; } } - + private UUID _RootUUID; + /// + /// The UUID of the root folder. + /// public UUID RootUUID { get { return _RootUUID; } private set { _RootUUID = value; } } + /// + /// Reference to the InventoryFolder representing the + /// inventory root folder, if it has been managed. + /// public InventoryFolder RootFolder { get { return this[RootUUID] as InventoryFolder; } @@ -426,13 +433,60 @@ namespace OpenMetaverse } } - public List InventoryFromPath(string[] path) + /// + /// Fetches an inventory item or folder by path. + /// If the path starts with /, is ignored + /// and the path is located from the root. + /// + /// If is true, this method may take a while to return. + /// A "/"-seperated path, UNIX style. Accepts UUIDs or folder names. + /// The directory to begin in if the path does not start at the root. + /// Whether to fetch folder contents when they're needed. + /// Multiple items if the path is ambiguous. + public List InventoryFromPath(string path, InventoryFolder currentDirectory, bool fetchStale) { - return InventoryFromPath(path, RootFolder); + path = path.Trim(); + if (path.StartsWith("/")) + { + currentDirectory = RootFolder; + path = path.Remove(0, 1); + } + return InventoryFromPath(path.Split(new char[] { '/' }), currentDirectory, fetchStale); } + /// + /// Fetches an inventory item or folder by path, without fetching anything + /// from the remote inventory. + /// + /// If is true, this method may take a while to return. + /// Array whose elements are names or UUIDs of folders, representing the path to the desired item or folder. + /// Multiple items if the path is ambiguous. + public List InventoryFromPath(string[] path) + { + return InventoryFromPath(path, false); + } - public List InventoryFromPath(string[] path, InventoryFolder baseFolder) + /// + /// Fetches an inventory item or folder by path, starting at the inventory's root folder. + /// + /// If is true, this method may take a while to return. + /// Array whose elements are names or UUIDs of folders, representing the path to the desired item or folder. + /// If a folder is stale, fetch its contents. + /// Multiple items if the path is ambiguous. + public List InventoryFromPath(string[] path, bool fetchStale) + { + return InventoryFromPath(path, RootFolder, fetchStale); + } + + /// + /// Fetches an inventory item or folder by path, starting at . + /// + /// If is true, this method may take a while to return. + /// Array whose elements are names or UUIDs of folders, representing the path to the desired item or folder. + /// Folder to start the path from. + /// Whether to fetch folder contents when they're needed. + /// Multiple items if the path is ambiguous. + public List InventoryFromPath(string[] path, InventoryFolder baseFolder, bool fetchStale) { if (path == null || path.Length == 0) { @@ -441,29 +495,61 @@ namespace OpenMetaverse return one; } - Dictionary goal = new Dictionary(); + // Agenda stores an object[] which contains an InventoryFolder and an int + // the int represents the level in the path that the children of the InventoryFolder + // should satasfy. List results = new List(); - Stack agenda = new Stack(); - goal.Add(baseFolder, 0); - agenda.Push(baseFolder); + Stack agenda = new Stack(); + agenda.Push(new object[] { baseFolder, 0 }); while (agenda.Count > 0) { - InventoryFolder currentFolder = agenda.Pop() as InventoryFolder; - int currentLevel = goal[currentFolder]; - foreach (InventoryBase child in currentFolder) + object[] currentData = agenda.Pop(); + InventoryFolder currentFolder = currentData[0] as InventoryFolder; + int goalLevel = (int)currentData[1]; + + if (path[goalLevel] == "..") { - if (child.Name == path[currentLevel]) + // The unix behavior at root is that it's its own parent. + InventoryFolder parent = currentFolder != RootFolder ? currentFolder.Parent : RootFolder; + if (goalLevel == path.Length - 1) // End of the path. { - if (currentLevel == path.Length - 1) + results.Add(parent); + } + else + { + agenda.Push(new object[] { parent, goalLevel + 1 }); + } + } + else if (path[goalLevel] == ".") + { + if (goalLevel == path.Length - 1) // End of the path. + { + results.Add(currentFolder); + } + else + { + agenda.Push(new object[] {currentFolder, goalLevel + 1}); + } + } + else // We need to look at the children + { + if (fetchStale && currentFolder.IsStale) + currentFolder.DownloadContents(TimeSpan.FromSeconds(10)); + + foreach (InventoryBase child in currentFolder) + { + if (child.Name == path[goalLevel] || child.UUID.ToString() == path[goalLevel]) { - results.Add(child); - } - else - { - if (child is InventoryFolder) + if (goalLevel == path.Length - 1) // End of the path. { - agenda.Push(child); - goal.Add(child, currentLevel + 1); + results.Add(child); + } + else + { + if (child is InventoryFolder) + { + agenda.Push(new object[] { child, goalLevel + 1 }); + } } } } diff --git a/OpenMetaverse/InventoryManager.cs b/OpenMetaverse/InventoryManager.cs index 3f1c7f6d..c9276dfe 100644 --- a/OpenMetaverse/InventoryManager.cs +++ b/OpenMetaverse/InventoryManager.cs @@ -638,6 +638,7 @@ namespace OpenMetaverse public UUID Folder; public bool ReceivedResponse; public FolderContentsCallback Callback; + public PartialContentsCallback PartialCallback; public int Descendents; public List FolderContents; public List ItemContents; @@ -649,6 +650,7 @@ namespace OpenMetaverse Descendents = 0; FolderContents = new List(); ItemContents = new List(); + PartialCallback = null; } } @@ -717,6 +719,15 @@ namespace OpenMetaverse /// public delegate void FolderContentsCallback(UUID folder, List Items, List Folders); + /// + /// Use this delegate to create a callback for RequestFolderContents + /// + /// The folder whose contents were received. + /// The items in that were just received. + /// The folders in that were just received. + /// Number of item or folders that remain to be downloaded. + public delegate void PartialContentsCallback(UUID folder, ItemData[] items, FolderData[] folders, int remaining); + /// /// Use this delegate to create a callback for RequestFetchItems. /// @@ -1115,16 +1126,32 @@ namespace OpenMetaverse } /// - /// Request the contents of an inventory folder + /// + /// + /// + /// + /// + /// + /// + /// + public void RequestFolderContents(UUID folder, UUID owner, bool folders, bool items, InventorySortOrder order, FolderContentsCallback callback) + { + RequestFolderContents(folder, owner, folders, items, order, callback, null); + } + + /// + /// Request the contents of an inventory folder, is fired when all of the contents are retrieved. + /// is fired as the contents trickle in from the server. /// /// The folder to search /// The folder owners /// true to return s contained in folder /// true to return s containd in folder /// the sort order to return items in - /// The callback to fire when the contents are received. + /// The callback to fire when all the contents are received. + /// The callback to fire as the contents are received. /// - public void RequestFolderContents(UUID folder, UUID owner, bool folders, bool items, InventorySortOrder order, FolderContentsCallback callback) + public void RequestFolderContents(UUID folder, UUID owner, bool folders, bool items, InventorySortOrder order, FolderContentsCallback callback, PartialContentsCallback partialCallback) { DescendentsRequest request = new DescendentsRequest(folder, callback); lock (_DescendentsRequests) @@ -1237,7 +1264,7 @@ namespace OpenMetaverse public void RequestFindObjectByPath(UUID baseFolder, UUID inventoryOwner, string[] pathArray, FindObjectByPathCallback callback) { // Create the RequestFolderContents callback: - FolderContentsCallback contentsCallback = ConstructFindContentsHandler(Helpers.Implode(pathArray, "/"), pathArray, 0, callback); + FolderContentsCallback contentsCallback = ConstructFindContentsHandler(String.Join("/", pathArray), pathArray, 0, callback); // Start the search RequestFolderContents(baseFolder, inventoryOwner, true, true, InventorySortOrder.ByName, contentsCallback); } @@ -3027,8 +3054,16 @@ namespace OpenMetaverse _DescendentsRequests[i] = request; + int contentsReceived = request.FolderContents.Count + request.ItemContents.Count; + + // Fire the partial callback, if we have one: + if (request.PartialCallback != null) + { + request.PartialCallback(reply.AgentData.FolderID, items, folders, request.Descendents - contentsReceived); + } + // Check if we're done: - if (request.FolderContents.Count + request.ItemContents.Count >= request.Descendents) + if (contentsReceived >= request.Descendents) { // Fire the callback: request.Callback(reply.AgentData.FolderID, request.ItemContents, request.FolderContents); diff --git a/Programs/examples/TestClient/Commands/Appearance/WearCommand.cs b/Programs/examples/TestClient/Commands/Appearance/WearCommand.cs index e8c9cb83..cdf5e76e 100644 --- a/Programs/examples/TestClient/Commands/Appearance/WearCommand.cs +++ b/Programs/examples/TestClient/Commands/Appearance/WearCommand.cs @@ -1,5 +1,6 @@ using System; using OpenMetaverse; +using System.Collections.Generic; namespace OpenMetaverse.TestClient { @@ -16,7 +17,7 @@ namespace OpenMetaverse.TestClient public override string Execute(string[] args, UUID fromAgentID) { if (args.Length < 1) - return "Usage: wear [outfit name] eg: 'wear /My Outfit/Dance Party"; + return "Usage: wear [outfit name] [nobake] eg: 'wear \"/Clothing/My Outfit\" [nobake]'"; string target = String.Empty; bool bake = true; @@ -28,12 +29,14 @@ namespace OpenMetaverse.TestClient else target = target + args[ct] + " "; } - - target = target.TrimEnd(); + + List results = Client.InventoryStore.InventoryFromPath(target, Client.CurrentDirectory, true); + if (results.Count == 0 || !(results[0] is InventoryFolder)) + return "Unable to find folder at " + target; try { - Client.Appearance.WearOutfit(target.Split('/'), bake); + Client.Appearance.WearOutfit(results[0] as InventoryFolder, bake); } catch (InvalidOutfitException ex) { diff --git a/Programs/examples/TestClient/Commands/Inventory/ChangeDirectoryCommand.cs b/Programs/examples/TestClient/Commands/Inventory/ChangeDirectoryCommand.cs index 7be6bd4b..1a9dd785 100644 --- a/Programs/examples/TestClient/Commands/Inventory/ChangeDirectoryCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/ChangeDirectoryCommand.cs @@ -21,68 +21,32 @@ namespace OpenMetaverse.TestClient.Commands.Inventory.Shell Manager = Client.Inventory; Inventory = Client.InventoryStore; - if (args.Length > 1) - return "Usage: cd [path-to-folder]"; - string pathStr = ""; - string[] path = null; if (args.Length == 0) - { - path = new string[] { "" }; - // cd without any arguments doesn't do anything. - } - else if (args.Length == 1) - { - pathStr = args[0]; - path = pathStr.Split(new char[] { '/' }); - // Use '/' as a path seperator. - } - InventoryFolder currentFolder = Client.CurrentDirectory; - if (pathStr.StartsWith("/")) - currentFolder = Inventory.RootFolder; + return "Current folder: " + Client.CurrentDirectory.Name; - if (currentFolder == null) // We need this to be set to something. - return "Error: Client not logged in."; - - // Traverse the path, looking for the - for (int i = 0; i < path.Length; ++i) + string path = args[0]; + for(int i = 1; i < args.Length; ++i) { - string nextName = path[i]; - if (string.IsNullOrEmpty(nextName) || nextName == ".") - continue; // Ignore '.' and blanks, stay in the current directory. - if (nextName == ".." && currentFolder != Inventory.RootFolder) + path += " " + args[i]; + } + + List results = Inventory.InventoryFromPath(path, Client.CurrentDirectory, true); + if (results.Count == 0) + return "Can not find inventory at: " + path; + InventoryFolder destFolder = null; + foreach (InventoryBase ib in results) + { + if (ib is InventoryFolder) { - // If we encounter .., move to the parent folder. - currentFolder = currentFolder.Parent; - } - else - { - if (currentFolder.IsStale) - currentFolder.DownloadContents(TimeSpan.FromSeconds(30)); - // Try and find an InventoryBase with the corresponding name. - bool found = false; - foreach (InventoryBase item in currentFolder) - { - string name = item.Name; - // Allow lookup by UUID as well as name: - if (name == nextName || item.UUID.ToString() == nextName) - { - found = true; - if (item is InventoryFolder) - { - currentFolder = item as InventoryFolder; - } - else - { - return name + " is not a folder."; - } - } - } - if (!found) - return nextName + " not found in " + currentFolder.Data.Name; + destFolder = ib as InventoryFolder; + break; } } - Client.CurrentDirectory = currentFolder; - return "Current folder: " + currentFolder.Data.Name; + if (destFolder == null) + return path + " is not a folder."; + + Client.CurrentDirectory = destFolder; + return "Current folder: " + Client.CurrentDirectory.Name; } } } diff --git a/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs b/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs index fea88f4b..4df06234 100644 --- a/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs @@ -27,14 +27,14 @@ namespace OpenMetaverse.TestClient string target = String.Empty; for (int ct = 0; ct < args.Length; ct++) target = target + args[ct] + " "; - target = target.TrimEnd(); // initialize results list List found = new List(); try { // find the folder - found = Client.InventoryStore.InventoryFromPath(target.Split('/'), Client.InventoryStore.RootFolder); + + found = Client.InventoryStore.InventoryFromPath(target, Client.CurrentDirectory, true); if (found.Count > 0) { InventoryBase item = found[0]; diff --git a/Programs/examples/TestClient/Commands/Inventory/GiveItemCommand.cs b/Programs/examples/TestClient/Commands/Inventory/GiveItemCommand.cs index 548637ed..1ed5f6bc 100644 --- a/Programs/examples/TestClient/Commands/Inventory/GiveItemCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/GiveItemCommand.cs @@ -31,25 +31,19 @@ namespace OpenMetaverse.TestClient.Commands.Inventory.Shell string nl = "\n"; for (int i = 1; i < args.Length; ++i) { - string inventoryName = args[i]; + string itemPath = args[i]; - if (Client.CurrentDirectory.IsStale) + List results = Inventory.InventoryFromPath(itemPath, Client.CurrentDirectory, true); + + if (results.Count == 0) { - Client.CurrentDirectory.DownloadContents(TimeSpan.FromSeconds(30)); + ret += "No inventory item at " + itemPath + " found." + nl; } - - bool found = false; - foreach (InventoryBase b in Client.CurrentDirectory) { - string name = b.Name; - if (inventoryName == name || inventoryName == b.UUID.ToString()) - { - found = true; - b.Give(dest, true); - ret += "Gave " + name + nl; - } + else + { + results[0].Give(dest, true); + ret += "Gave " + results[0].Name + nl; } - if (!found) - ret += "No inventory item named " + inventoryName + " found." + nl; } return ret; } diff --git a/Programs/examples/TestClient/Commands/Inventory/ListContentsCommand.cs b/Programs/examples/TestClient/Commands/Inventory/ListContentsCommand.cs index 8a403b58..be90ba3b 100644 --- a/Programs/examples/TestClient/Commands/Inventory/ListContentsCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/ListContentsCommand.cs @@ -17,22 +17,49 @@ namespace OpenMetaverse.TestClient.Commands.Inventory.Shell } public override string Execute(string[] args, UUID fromAgentID) { - if (args.Length > 1) - return "Usage: ls [-l]"; - bool longDisplay = false; - if (args.Length > 0 && args[0] == "-l") - longDisplay = true; - Manager = Client.Inventory; Inventory = Client.InventoryStore; + if (args.Length > 1) + return "Usage: ls [-l] [directory path]"; + bool longDisplay = false; + InventoryFolder directory = Client.CurrentDirectory; + if (args.Length > 0) + { + int start = 0; + if (args[0] == "-l") + { + longDisplay = true; + start = 1; + } + if (start < args.Length) + { + string path = args[start]; + for (int i = start + 1; i < args.Length; ++i) + { + path += " " + args[i]; + } + bool found = false; + List results = Inventory.InventoryFromPath(path, Client.CurrentDirectory, true); + foreach (InventoryBase ib in results) + { + if (ib is InventoryFolder) + { + directory = ib as InventoryFolder; + found = true; + } + } + if (!found) + return "Unable to find directory at path: " + path; + } + } - if (Client.CurrentDirectory.IsStale) - Client.CurrentDirectory.DownloadContents(TimeSpan.FromSeconds(30)); + if (directory.IsStale) + directory.DownloadContents(TimeSpan.FromSeconds(30)); string displayString = ""; string nl = "\n"; // New line character // Pretty simple, just print out the contents. - foreach (InventoryBase b in Client.CurrentDirectory) + foreach (InventoryBase b in directory) { if (longDisplay) {