diff --git a/OpenSim/Capabilities/Handlers/FetchInventory/FetchInvDescHandler.cs b/OpenSim/Capabilities/Handlers/FetchInventory/FetchInvDescHandler.cs new file mode 100644 index 0000000000..7197049f16 --- /dev/null +++ b/OpenSim/Capabilities/Handlers/FetchInventory/FetchInvDescHandler.cs @@ -0,0 +1,848 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Framework.Capabilities; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Services.Interfaces; +using Caps = OpenSim.Framework.Capabilities.Caps; + +namespace OpenSim.Capabilities.Handlers +{ + public class FetchInvDescHandler + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IInventoryService m_InventoryService; + private ILibraryService m_LibraryService; + private IScene m_Scene; +// private object m_fetchLock = new Object(); + + public FetchInvDescHandler(IInventoryService invService, ILibraryService libService, IScene s) + { + m_InventoryService = invService; + m_LibraryService = libService; + m_Scene = s; + } + + + public string FetchInventoryDescendentsRequest(string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + //m_log.DebugFormat("[XXX]: FetchInventoryDescendentsRequest in {0}, {1}", (m_Scene == null) ? "none" : m_Scene.Name, request); + + // nasty temporary hack here, the linden client falsely + // identifies the uuid 00000000-0000-0000-0000-000000000000 + // as a string which breaks us + // + // correctly mark it as a uuid + // + request = request.Replace("00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000000"); + + // another hack 1 results in a + // System.ArgumentException: Object type System.Int32 cannot + // be converted to target type: System.Boolean + // + request = request.Replace("fetch_folders0", "fetch_folders0"); + request = request.Replace("fetch_folders1", "fetch_folders1"); + + Hashtable hash = new Hashtable(); + try + { + hash = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request)); + } + catch (LLSD.LLSDParseException e) + { + m_log.ErrorFormat("[WEB FETCH INV DESC HANDLER]: Fetch error: {0}{1}" + e.Message, e.StackTrace); + m_log.Error("Request: " + request); + } + + ArrayList foldersrequested = (ArrayList)hash["folders"]; + + string response = ""; + string bad_folders_response = ""; + + List folders = new List(); + for (int i = 0; i < foldersrequested.Count; i++) + { + Hashtable inventoryhash = (Hashtable)foldersrequested[i]; + + LLSDFetchInventoryDescendents llsdRequest = new LLSDFetchInventoryDescendents(); + + try + { + LLSDHelpers.DeserialiseOSDMap(inventoryhash, llsdRequest); + } + catch (Exception e) + { + m_log.Debug("[WEB FETCH INV DESC HANDLER]: caught exception doing OSD deserialize" + e); + continue; + } + + // Filter duplicate folder ids that bad viewers may send + if (folders.Find(f => f.folder_id == llsdRequest.folder_id) == null) + folders.Add(llsdRequest); + + } + + if (folders.Count > 0) + { + List bad_folders = new List(); + List invcollSet = Fetch(folders, bad_folders); + //m_log.DebugFormat("[XXX]: Got {0} folders from a request of {1}", invcollSet.Count, folders.Count); + + if (invcollSet == null) + { + m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Multiple folder fetch failed. Trying old protocol."); +#pragma warning disable 0612 + return FetchInventoryDescendentsRequest(foldersrequested, httpRequest, httpResponse); +#pragma warning restore 0612 + } + + string inventoryitemstr = string.Empty; + foreach (InventoryCollectionWithDescendents icoll in invcollSet) + { + LLSDInventoryDescendents reply = ToLLSD(icoll.Collection, icoll.Descendents); + + inventoryitemstr = LLSDHelpers.SerialiseLLSDReply(reply); + inventoryitemstr = inventoryitemstr.Replace("folders", ""); + inventoryitemstr = inventoryitemstr.Replace("", ""); + + response += inventoryitemstr; + } + + //m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Bad folders {0}", string.Join(", ", bad_folders)); + foreach (UUID bad in bad_folders) + bad_folders_response += "" + bad + ""; + } + + if (response.Length == 0) + { + /* Viewers expect a bad_folders array when not available */ + if (bad_folders_response.Length != 0) + { + response = "bad_folders" + bad_folders_response + ""; + } + else + { + response = "folders"; + } + } + else + { + if (bad_folders_response.Length != 0) + { + response = "folders" + response + "bad_folders" + bad_folders_response + ""; + } + else + { + response = "folders" + response + ""; + } + } + + //m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Replying to CAPS fetch inventory request for {0} folders. Item count {1}", folders.Count, item_count); + //m_log.Debug("[WEB FETCH INV DESC HANDLER] " + response); + + return response; + + } + + /// + /// Construct an LLSD reply packet to a CAPS inventory request + /// + /// + /// + private LLSDInventoryDescendents FetchInventoryReply(LLSDFetchInventoryDescendents invFetch) + { + LLSDInventoryDescendents reply = new LLSDInventoryDescendents(); + LLSDInventoryFolderContents contents = new LLSDInventoryFolderContents(); + contents.agent_id = invFetch.owner_id; + contents.owner_id = invFetch.owner_id; + contents.folder_id = invFetch.folder_id; + + reply.folders.Array.Add(contents); + InventoryCollection inv = new InventoryCollection(); + inv.Folders = new List(); + inv.Items = new List(); + int version = 0; + int descendents = 0; + +#pragma warning disable 0612 + inv = Fetch( + invFetch.owner_id, invFetch.folder_id, invFetch.owner_id, + invFetch.fetch_folders, invFetch.fetch_items, invFetch.sort_order, out version, out descendents); +#pragma warning restore 0612 + + if (inv != null && inv.Folders != null) + { + foreach (InventoryFolderBase invFolder in inv.Folders) + { + contents.categories.Array.Add(ConvertInventoryFolder(invFolder)); + } + + descendents += inv.Folders.Count; + } + + if (inv != null && inv.Items != null) + { + foreach (InventoryItemBase invItem in inv.Items) + { + contents.items.Array.Add(ConvertInventoryItem(invItem)); + } + } + + contents.descendents = descendents; + contents.version = version; + + //m_log.DebugFormat( + // "[WEB FETCH INV DESC HANDLER]: Replying to request for folder {0} (fetch items {1}, fetch folders {2}) with {3} items and {4} folders for agent {5}", + // invFetch.folder_id, + // invFetch.fetch_items, + // invFetch.fetch_folders, + // contents.items.Array.Count, + // contents.categories.Array.Count, + // invFetch.owner_id); + + return reply; + } + + private LLSDInventoryDescendents ToLLSD(InventoryCollection inv, int descendents) + { + LLSDInventoryDescendents reply = new LLSDInventoryDescendents(); + LLSDInventoryFolderContents contents = new LLSDInventoryFolderContents(); + contents.agent_id = inv.OwnerID; + contents.owner_id = inv.OwnerID; + contents.folder_id = inv.FolderID; + + reply.folders.Array.Add(contents); + + if (inv.Folders != null) + { + foreach (InventoryFolderBase invFolder in inv.Folders) + { + contents.categories.Array.Add(ConvertInventoryFolder(invFolder)); + } + + descendents += inv.Folders.Count; + } + + if (inv.Items != null) + { + foreach (InventoryItemBase invItem in inv.Items) + { + contents.items.Array.Add(ConvertInventoryItem(invItem)); + } + } + + contents.descendents = descendents; + contents.version = inv.Version; + + return reply; + } + /// + /// Old style. Soon to be deprecated. + /// + /// + /// + /// + /// + [Obsolete] + private string FetchInventoryDescendentsRequest(ArrayList foldersrequested, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + //m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Received request for {0} folders", foldersrequested.Count); + + string response = ""; + string bad_folders_response = ""; + + for (int i = 0; i < foldersrequested.Count; i++) + { + string inventoryitemstr = ""; + Hashtable inventoryhash = (Hashtable)foldersrequested[i]; + + LLSDFetchInventoryDescendents llsdRequest = new LLSDFetchInventoryDescendents(); + + try + { + LLSDHelpers.DeserialiseOSDMap(inventoryhash, llsdRequest); + } + catch (Exception e) + { + m_log.Debug("[WEB FETCH INV DESC HANDLER]: caught exception doing OSD deserialize" + e); + } + + LLSDInventoryDescendents reply = FetchInventoryReply(llsdRequest); + + if (null == reply) + { + bad_folders_response += "" + llsdRequest.folder_id.ToString() + ""; + } + else + { + inventoryitemstr = LLSDHelpers.SerialiseLLSDReply(reply); + inventoryitemstr = inventoryitemstr.Replace("folders", ""); + inventoryitemstr = inventoryitemstr.Replace("", ""); + } + + response += inventoryitemstr; + } + + if (response.Length == 0) + { + /* Viewers expect a bad_folders array when not available */ + if (bad_folders_response.Length != 0) + { + response = "bad_folders" + bad_folders_response + ""; + } + else + { + response = "folders"; + } + } + else + { + if (bad_folders_response.Length != 0) + { + response = "folders" + response + "bad_folders" + bad_folders_response + ""; + } + else + { + response = "folders" + response + ""; + } + } + + // m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Replying to CAPS fetch inventory request"); + //m_log.Debug("[WEB FETCH INV DESC HANDLER] "+response); + + return response; + + // } + } + + /// + /// Handle the caps inventory descendents fetch. + /// + /// + /// + /// + /// + /// + /// + /// + /// An empty InventoryCollection if the inventory look up failed + [Obsolete] + private InventoryCollection Fetch( + UUID agentID, UUID folderID, UUID ownerID, + bool fetchFolders, bool fetchItems, int sortOrder, out int version, out int descendents) + { + //m_log.DebugFormat( + // "[WEB FETCH INV DESC HANDLER]: Fetching folders ({0}), items ({1}) from {2} for agent {3}", + // fetchFolders, fetchItems, folderID, agentID); + + // FIXME MAYBE: We're not handling sortOrder! + + version = 0; + descendents = 0; + + InventoryFolderImpl fold; + if (m_LibraryService != null && m_LibraryService.LibraryRootFolder != null && agentID == m_LibraryService.LibraryRootFolder.Owner) + { + if ((fold = m_LibraryService.LibraryRootFolder.FindFolder(folderID)) != null) + { + InventoryCollection ret = new InventoryCollection(); + ret.Folders = new List(); + ret.Items = fold.RequestListOfItems(); + descendents = ret.Folders.Count + ret.Items.Count; + + return ret; + } + } + + InventoryCollection contents = new InventoryCollection(); + + if (folderID != UUID.Zero) + { + InventoryCollection fetchedContents = m_InventoryService.GetFolderContent(agentID, folderID); + + if (fetchedContents == null) + { + m_log.WarnFormat("[WEB FETCH INV DESC HANDLER]: Could not get contents of folder {0} for user {1}", folderID, agentID); + return contents; + } + contents = fetchedContents; + InventoryFolderBase containingFolder = new InventoryFolderBase(); + containingFolder.ID = folderID; + containingFolder.Owner = agentID; + containingFolder = m_InventoryService.GetFolder(containingFolder); + + if (containingFolder != null) + { + //m_log.DebugFormat( + // "[WEB FETCH INV DESC HANDLER]: Retrieved folder {0} {1} for agent id {2}", + // containingFolder.Name, containingFolder.ID, agentID); + + version = containingFolder.Version; + + if (fetchItems) + { + List itemsToReturn = contents.Items; + List originalItems = new List(itemsToReturn); + + // descendents must only include the links, not the linked items we add + descendents = originalItems.Count; + + // Add target items for links in this folder before the links themselves. + foreach (InventoryItemBase item in originalItems) + { + if (item.AssetType == (int)AssetType.Link) + { + InventoryItemBase linkedItem = m_InventoryService.GetItem(new InventoryItemBase(item.AssetID)); + + // Take care of genuinely broken links where the target doesn't exist + // HACK: Also, don't follow up links that just point to other links. In theory this is legitimate, + // but no viewer has been observed to set these up and this is the lazy way of avoiding cycles + // rather than having to keep track of every folder requested in the recursion. + if (linkedItem != null && linkedItem.AssetType != (int)AssetType.Link) + itemsToReturn.Insert(0, linkedItem); + } + } + + // Now scan for folder links and insert the items they target and those links at the head of the return data + foreach (InventoryItemBase item in originalItems) + { + if (item.AssetType == (int)AssetType.LinkFolder) + { + InventoryCollection linkedFolderContents = m_InventoryService.GetFolderContent(ownerID, item.AssetID); + List links = linkedFolderContents.Items; + + itemsToReturn.InsertRange(0, links); + + foreach (InventoryItemBase link in linkedFolderContents.Items) + { + // Take care of genuinely broken links where the target doesn't exist + // HACK: Also, don't follow up links that just point to other links. In theory this is legitimate, + // but no viewer has been observed to set these up and this is the lazy way of avoiding cycles + // rather than having to keep track of every folder requested in the recursion. + if (link != null) + { +// m_log.DebugFormat( +// "[WEB FETCH INV DESC HANDLER]: Adding item {0} {1} from folder {2} linked from {3}", +// link.Name, (AssetType)link.AssetType, item.AssetID, containingFolder.Name); + + InventoryItemBase linkedItem + = m_InventoryService.GetItem(new InventoryItemBase(link.AssetID)); + + if (linkedItem != null) + itemsToReturn.Insert(0, linkedItem); + } + } + } + } + } + +// foreach (InventoryItemBase item in contents.Items) +// { +// m_log.DebugFormat( +// "[WEB FETCH INV DESC HANDLER]: Returning item {0}, type {1}, parent {2} in {3} {4}", +// item.Name, (AssetType)item.AssetType, item.Folder, containingFolder.Name, containingFolder.ID); +// } + + // ===== + +// +// foreach (InventoryItemBase linkedItem in linkedItemsToAdd) +// { +// m_log.DebugFormat( +// "[WEB FETCH INV DESC HANDLER]: Inserted linked item {0} for link in folder {1} for agent {2}", +// linkedItem.Name, folderID, agentID); +// +// contents.Items.Add(linkedItem); +// } +// +// // If the folder requested contains links, then we need to send those folders first, otherwise the links +// // will be broken in the viewer. +// HashSet linkedItemFolderIdsToSend = new HashSet(); +// foreach (InventoryItemBase item in contents.Items) +// { +// if (item.AssetType == (int)AssetType.Link) +// { +// InventoryItemBase linkedItem = m_InventoryService.GetItem(new InventoryItemBase(item.AssetID)); +// +// // Take care of genuinely broken links where the target doesn't exist +// // HACK: Also, don't follow up links that just point to other links. In theory this is legitimate, +// // but no viewer has been observed to set these up and this is the lazy way of avoiding cycles +// // rather than having to keep track of every folder requested in the recursion. +// if (linkedItem != null && linkedItem.AssetType != (int)AssetType.Link) +// { +// // We don't need to send the folder if source and destination of the link are in the same +// // folder. +// if (linkedItem.Folder != containingFolder.ID) +// linkedItemFolderIdsToSend.Add(linkedItem.Folder); +// } +// } +// } +// +// foreach (UUID linkedItemFolderId in linkedItemFolderIdsToSend) +// { +// m_log.DebugFormat( +// "[WEB FETCH INV DESC HANDLER]: Recursively fetching folder {0} linked by item in folder {1} for agent {2}", +// linkedItemFolderId, folderID, agentID); +// +// int dummyVersion; +// InventoryCollection linkedCollection +// = Fetch( +// agentID, linkedItemFolderId, ownerID, fetchFolders, fetchItems, sortOrder, out dummyVersion); +// +// InventoryFolderBase linkedFolder = new InventoryFolderBase(linkedItemFolderId); +// linkedFolder.Owner = agentID; +// linkedFolder = m_InventoryService.GetFolder(linkedFolder); +// +//// contents.Folders.AddRange(linkedCollection.Folders); +// +// contents.Folders.Add(linkedFolder); +// contents.Items.AddRange(linkedCollection.Items); +// } +// } + } + } + else + { + // Lost items don't really need a version + version = 1; + } + + return contents; + + } + + private void AddLibraryFolders(List fetchFolders, List result) + { + InventoryFolderImpl fold; + if (m_LibraryService != null && m_LibraryService.LibraryRootFolder != null) + { + List libfolders = fetchFolders.FindAll(f => f.owner_id == m_LibraryService.LibraryRootFolder.Owner); + fetchFolders.RemoveAll(f => libfolders.Contains(f)); + + //m_log.DebugFormat("[XXX]: Found {0} library folders in request", libfolders.Count); + + foreach (LLSDFetchInventoryDescendents f in libfolders) + { + if ((fold = m_LibraryService.LibraryRootFolder.FindFolder(f.folder_id)) != null) + { + InventoryCollectionWithDescendents ret = new InventoryCollectionWithDescendents(); + ret.Collection = new InventoryCollection(); + ret.Collection.Folders = new List(); + ret.Collection.Items = fold.RequestListOfItems(); + ret.Collection.OwnerID = m_LibraryService.LibraryRootFolder.Owner; + ret.Collection.FolderID = f.folder_id; + ret.Collection.Version = fold.Version; + + ret.Descendents = ret.Collection.Items.Count; + result.Add(ret); + + //m_log.DebugFormat("[XXX]: Added libfolder {0} ({1}) {2}", ret.Collection.FolderID, ret.Collection.OwnerID); + } + } + } + } + + private List Fetch(List fetchFolders, List bad_folders) + { + //m_log.DebugFormat( + // "[WEB FETCH INV DESC HANDLER]: Fetching {0} folders for owner {1}", fetchFolders.Count, fetchFolders[0].owner_id); + + // FIXME MAYBE: We're not handling sortOrder! + + List result = new List(); + + AddLibraryFolders(fetchFolders, result); + + // Filter folder Zero right here. Some viewers (Firestorm) send request for folder Zero, which doesn't make sense + // and can kill the sim (all root folders have parent_id Zero) + LLSDFetchInventoryDescendents zero = fetchFolders.Find(f => f.folder_id == UUID.Zero); + if (zero != null) + { + fetchFolders.Remove(zero); + BadFolder(zero, null, bad_folders); + } + + if (fetchFolders.Count > 0) + { + UUID[] fids = new UUID[fetchFolders.Count]; + int i = 0; + foreach (LLSDFetchInventoryDescendents f in fetchFolders) + fids[i++] = f.folder_id; + + //m_log.DebugFormat("[XXX]: {0}", string.Join(",", fids)); + + InventoryCollection[] fetchedContents = m_InventoryService.GetMultipleFoldersContent(fetchFolders[0].owner_id, fids); + + if (fetchedContents == null || (fetchedContents != null && fetchedContents.Length == 0)) + { + m_log.WarnFormat("[WEB FETCH INV DESC HANDLER]: Could not get contents of multiple folders for user {0}", fetchFolders[0].owner_id); + foreach (LLSDFetchInventoryDescendents freq in fetchFolders) + BadFolder(freq, null, bad_folders); + return null; + } + + i = 0; + // Do some post-processing. May need to fetch more from inv server for links + foreach (InventoryCollection contents in fetchedContents) + { + // Find the original request + LLSDFetchInventoryDescendents freq = fetchFolders[i++]; + + InventoryCollectionWithDescendents coll = new InventoryCollectionWithDescendents(); + coll.Collection = contents; + + if (BadFolder(freq, contents, bad_folders)) + continue; + + // Next: link management + ProcessLinks(freq, coll); + + result.Add(coll); + } + } + + return result; + } + + private bool BadFolder(LLSDFetchInventoryDescendents freq, InventoryCollection contents, List bad_folders) + { + bool bad = false; + if (contents == null) + { + bad_folders.Add(freq.folder_id); + bad = true; + } + + // The inventory server isn't sending FolderID in the collection... + // Must fetch it individually + else if (contents.FolderID == UUID.Zero) + { + InventoryFolderBase containingFolder = new InventoryFolderBase(); + containingFolder.ID = freq.folder_id; + containingFolder.Owner = freq.owner_id; + containingFolder = m_InventoryService.GetFolder(containingFolder); + + if (containingFolder != null) + { + contents.FolderID = containingFolder.ID; + contents.OwnerID = containingFolder.Owner; + contents.Version = containingFolder.Version; + } + else + { + // Was it really a request for folder Zero? + // This is an overkill, but Firestorm really asks for folder Zero. + // I'm leaving the code here for the time being, but commented. + if (freq.folder_id == UUID.Zero) + { + //coll.Collection.OwnerID = freq.owner_id; + //coll.Collection.FolderID = contents.FolderID; + //containingFolder = m_InventoryService.GetRootFolder(freq.owner_id); + //if (containingFolder != null) + //{ + // m_log.WarnFormat("[WEB FETCH INV DESC HANDLER]: Request for parent of folder {0}", containingFolder.ID); + // coll.Collection.Folders.Clear(); + // coll.Collection.Folders.Add(containingFolder); + // if (m_LibraryService != null && m_LibraryService.LibraryRootFolder != null) + // { + // InventoryFolderBase lib = new InventoryFolderBase(m_LibraryService.LibraryRootFolder.ID, m_LibraryService.LibraryRootFolder.Owner); + // lib.Name = m_LibraryService.LibraryRootFolder.Name; + // lib.Type = m_LibraryService.LibraryRootFolder.Type; + // lib.Version = m_LibraryService.LibraryRootFolder.Version; + // coll.Collection.Folders.Add(lib); + // } + // coll.Collection.Items.Clear(); + //} + } + else + { + m_log.WarnFormat("[WEB FETCH INV DESC HANDLER]: Unable to fetch folder {0}", freq.folder_id); + bad_folders.Add(freq.folder_id); + } + bad = true; + } + } + + return bad; + } + + private void ProcessLinks(LLSDFetchInventoryDescendents freq, InventoryCollectionWithDescendents coll) + { + InventoryCollection contents = coll.Collection; + + if (freq.fetch_items && contents.Items != null) + { + List itemsToReturn = contents.Items; + + // descendents must only include the links, not the linked items we add + coll.Descendents = itemsToReturn.Count; + + // Add target items for links in this folder before the links themselves. + List itemIDs = new List(); + List folderIDs = new List(); + foreach (InventoryItemBase item in itemsToReturn) + { + //m_log.DebugFormat("[XXX]: {0} {1}", item.Name, item.AssetType); + if (item.AssetType == (int)AssetType.Link) + itemIDs.Add(item.AssetID); + + else if (item.AssetType == (int)AssetType.LinkFolder) + folderIDs.Add(item.AssetID); + } + + //m_log.DebugFormat("[XXX]: folder {0} has {1} links and {2} linkfolders", contents.FolderID, itemIDs.Count, folderIDs.Count); + + // Scan for folder links and insert the items they target and those links at the head of the return data + if (folderIDs.Count > 0) + { + InventoryCollection[] linkedFolders = m_InventoryService.GetMultipleFoldersContent(coll.Collection.OwnerID, folderIDs.ToArray()); + foreach (InventoryCollection linkedFolderContents in linkedFolders) + { + if (linkedFolderContents == null) + continue; + + List links = linkedFolderContents.Items; + + itemsToReturn.InsertRange(0, links); + + } + } + + if (itemIDs.Count > 0) + { + InventoryItemBase[] linked = m_InventoryService.GetMultipleItems(freq.owner_id, itemIDs.ToArray()); + if (linked == null) + { + // OMG!!! One by one!!! This is fallback code, in case the backend isn't updated + m_log.WarnFormat("[WEB FETCH INV DESC HANDLER]: GetMultipleItems failed. Falling back to fetching inventory items one by one."); + linked = new InventoryItemBase[itemIDs.Count]; + int i = 0; + InventoryItemBase item = new InventoryItemBase(); + item.Owner = freq.owner_id; + foreach (UUID id in itemIDs) + { + item.ID = id; + linked[i++] = m_InventoryService.GetItem(item); + } + } + + //m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Processing folder {0}. Existing items:", freq.folder_id); + //foreach (InventoryItemBase item in itemsToReturn) + // m_log.DebugFormat("[XXX]: {0} {1} {2}", item.Name, item.AssetType, item.Folder); + + if (linked != null) + { + foreach (InventoryItemBase linkedItem in linked) + { + // Take care of genuinely broken links where the target doesn't exist + // HACK: Also, don't follow up links that just point to other links. In theory this is legitimate, + // but no viewer has been observed to set these up and this is the lazy way of avoiding cycles + // rather than having to keep track of every folder requested in the recursion. + if (linkedItem != null && linkedItem.AssetType != (int)AssetType.Link) + { + itemsToReturn.Insert(0, linkedItem); + //m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Added {0} {1} {2}", linkedItem.Name, linkedItem.AssetType, linkedItem.Folder); + } + } + } + } + } + + } + + /// + /// Convert an internal inventory folder object into an LLSD object. + /// + /// + /// + private LLSDInventoryFolder ConvertInventoryFolder(InventoryFolderBase invFolder) + { + LLSDInventoryFolder llsdFolder = new LLSDInventoryFolder(); + llsdFolder.folder_id = invFolder.ID; + llsdFolder.parent_id = invFolder.ParentID; + llsdFolder.name = invFolder.Name; + llsdFolder.type = invFolder.Type; + llsdFolder.preferred_type = -1; + + return llsdFolder; + } + + /// + /// Convert an internal inventory item object into an LLSD object. + /// + /// + /// + private LLSDInventoryItem ConvertInventoryItem(InventoryItemBase invItem) + { + LLSDInventoryItem llsdItem = new LLSDInventoryItem(); + llsdItem.asset_id = invItem.AssetID; + llsdItem.created_at = invItem.CreationDate; + llsdItem.desc = invItem.Description; + llsdItem.flags = (int)invItem.Flags; + llsdItem.item_id = invItem.ID; + llsdItem.name = invItem.Name; + llsdItem.parent_id = invItem.Folder; + llsdItem.type = invItem.AssetType; + llsdItem.inv_type = invItem.InvType; + + llsdItem.permissions = new LLSDPermissions(); + llsdItem.permissions.creator_id = invItem.CreatorIdAsUuid; + llsdItem.permissions.base_mask = (int)invItem.CurrentPermissions; + llsdItem.permissions.everyone_mask = (int)invItem.EveryOnePermissions; + llsdItem.permissions.group_id = invItem.GroupID; + llsdItem.permissions.group_mask = (int)invItem.GroupPermissions; + llsdItem.permissions.is_owner_group = invItem.GroupOwned; + llsdItem.permissions.next_owner_mask = (int)invItem.NextPermissions; + llsdItem.permissions.owner_id = invItem.Owner; + llsdItem.permissions.owner_mask = (int)invItem.CurrentPermissions; + llsdItem.sale_info = new LLSDSaleInfo(); + llsdItem.sale_info.sale_price = invItem.SalePrice; + llsdItem.sale_info.sale_type = invItem.SaleType; + + return llsdItem; + } + } + + class InventoryCollectionWithDescendents + { + public InventoryCollection Collection; + public int Descendents; + } +} \ No newline at end of file diff --git a/OpenSim/Capabilities/Handlers/WebFetchInventoryDescendents/WebFetchInvDescServerConnector.cs b/OpenSim/Capabilities/Handlers/FetchInventory/FetchInvDescServerConnector.cs similarity index 91% rename from OpenSim/Capabilities/Handlers/WebFetchInventoryDescendents/WebFetchInvDescServerConnector.cs rename to OpenSim/Capabilities/Handlers/FetchInventory/FetchInvDescServerConnector.cs index 5d86557dc3..9dcfaa48f1 100644 --- a/OpenSim/Capabilities/Handlers/WebFetchInventoryDescendents/WebFetchInvDescServerConnector.cs +++ b/OpenSim/Capabilities/Handlers/FetchInventory/FetchInvDescServerConnector.cs @@ -35,13 +35,13 @@ using OpenMetaverse; namespace OpenSim.Capabilities.Handlers { - public class WebFetchInvDescServerConnector : ServiceConnector + public class FetchInvDescServerConnector : ServiceConnector { private IInventoryService m_InventoryService; private ILibraryService m_LibraryService; private string m_ConfigName = "CapsService"; - public WebFetchInvDescServerConnector(IConfigSource config, IHttpServer server, string configName) : + public FetchInvDescServerConnector(IConfigSource config, IHttpServer server, string configName) : base(config, server, configName) { if (configName != String.Empty) @@ -67,13 +67,13 @@ namespace OpenSim.Capabilities.Handlers m_LibraryService = ServerUtils.LoadPlugin(libService, args); - WebFetchInvDescHandler webFetchHandler = new WebFetchInvDescHandler(m_InventoryService, m_LibraryService); + FetchInvDescHandler webFetchHandler = new FetchInvDescHandler(m_InventoryService, m_LibraryService, null); IRequestHandler reqHandler = new RestStreamHandler( "POST", "/CAPS/WebFetchInvDesc/" /*+ UUID.Random()*/, webFetchHandler.FetchInventoryDescendentsRequest, - "WebFetchInvDesc", + "FetchInvDescendents", null); server.AddStreamHandler(reqHandler); } diff --git a/OpenSim/Capabilities/Handlers/FetchInventory2/FetchInventory2Handler.cs b/OpenSim/Capabilities/Handlers/FetchInventory/FetchInventory2Handler.cs similarity index 78% rename from OpenSim/Capabilities/Handlers/FetchInventory2/FetchInventory2Handler.cs rename to OpenSim/Capabilities/Handlers/FetchInventory/FetchInventory2Handler.cs index d1503ee15c..638e8bc9db 100644 --- a/OpenSim/Capabilities/Handlers/FetchInventory2/FetchInventory2Handler.cs +++ b/OpenSim/Capabilities/Handlers/FetchInventory/FetchInventory2Handler.cs @@ -25,39 +25,36 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -using System; -using System.Collections; -using System.Collections.Generic; using System.Reflection; -using log4net; -using Nini.Config; using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Framework.Capabilities; -using OpenSim.Region.Framework.Interfaces; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Services.Interfaces; -using Caps = OpenSim.Framework.Capabilities.Caps; using OSDArray = OpenMetaverse.StructuredData.OSDArray; using OSDMap = OpenMetaverse.StructuredData.OSDMap; +using log4net; + namespace OpenSim.Capabilities.Handlers { public class FetchInventory2Handler { -// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private IInventoryService m_inventoryService; + private UUID m_agentID; - public FetchInventory2Handler(IInventoryService invService) + public FetchInventory2Handler(IInventoryService invService, UUID agentId) { m_inventoryService = invService; + m_agentID = agentId; } public string FetchInventoryRequest(string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { -// m_log.DebugFormat("[FETCH INVENTORY HANDLER]: Received FetchInventory capabilty request"); + //m_log.DebugFormat("[FETCH INVENTORY HANDLER]: Received FetchInventory capability request {0}", request); OSDMap requestmap = (OSDMap)OSDParser.DeserializeLLSDXml(Utils.StringToBytes(request)); OSDArray itemsRequested = (OSDArray)requestmap["items"]; @@ -65,12 +62,32 @@ namespace OpenSim.Capabilities.Handlers string reply; LLSDFetchInventory llsdReply = new LLSDFetchInventory(); + UUID[] itemIDs = new UUID[itemsRequested.Count]; + int i = 0; foreach (OSDMap osdItemId in itemsRequested) { - UUID itemId = osdItemId["item_id"].AsUUID(); + itemIDs[i++] = osdItemId["item_id"].AsUUID(); + } - InventoryItemBase item = m_inventoryService.GetItem(new InventoryItemBase(itemId)); + InventoryItemBase[] items = m_inventoryService.GetMultipleItems(m_agentID, itemIDs); + if (items == null) + { + // OMG!!! One by one!!! This is fallback code, in case the backend isn't updated + m_log.WarnFormat("[FETCH INVENTORY HANDLER]: GetMultipleItems failed. Falling back to fetching inventory items one by one."); + items = new InventoryItemBase[itemsRequested.Count]; + i = 0; + InventoryItemBase item = new InventoryItemBase(); + item.Owner = m_agentID; + foreach (UUID id in itemIDs) + { + item.ID = id; + items[i++] = m_inventoryService.GetItem(item); + } + } + + foreach (InventoryItemBase item in items) + { if (item != null) { // We don't know the agent that this request belongs to so we'll use the agent id of the item diff --git a/OpenSim/Capabilities/Handlers/FetchInventory/Tests/FetchInventory2HandlerTests.cs b/OpenSim/Capabilities/Handlers/FetchInventory/Tests/FetchInventory2HandlerTests.cs new file mode 100644 index 0000000000..8af3c6485d --- /dev/null +++ b/OpenSim/Capabilities/Handlers/FetchInventory/Tests/FetchInventory2HandlerTests.cs @@ -0,0 +1,170 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text.RegularExpressions; +using log4net; +using log4net.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Capabilities.Handlers; +using OpenSim.Framework; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Capabilities.Handlers.FetchInventory.Tests +{ + [TestFixture] + public class FetchInventory2HandlerTests : OpenSimTestCase + { + private UUID m_userID = UUID.Random(); + private Scene m_scene; + private UUID m_rootFolderID; + private UUID m_notecardsFolder; + private UUID m_objectsFolder; + + private void Init() + { + // Create an inventory that looks like this: + // + // /My Inventory + // + // /Objects + // Object 1 + // Object 2 + // Object 3 + // /Notecards + // Notecard 1 + // Notecard 2 + // Notecard 3 + // Notecard 4 + // Notecard 5 + + m_scene = new SceneHelpers().SetupScene(); + + m_scene.InventoryService.CreateUserInventory(m_userID); + + m_rootFolderID = m_scene.InventoryService.GetRootFolder(m_userID).ID; + + InventoryFolderBase of = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Object); + m_objectsFolder = of.ID; + + // Add 3 objects + InventoryItemBase item; + for (int i = 1; i <= 3; i++) + { + item = new InventoryItemBase(new UUID("b0000000-0000-0000-0000-0000000000b" + i), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Object; + item.Folder = m_objectsFolder; + item.Name = "Object " + i; + m_scene.InventoryService.AddItem(item); + } + + InventoryFolderBase ncf = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Notecard); + m_notecardsFolder = ncf.ID; + + // Add 5 notecards + for (int i = 1; i <= 5; i++) + { + item = new InventoryItemBase(new UUID("10000000-0000-0000-0000-00000000000" + i), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Notecard; + item.Folder = m_notecardsFolder; + item.Name = "Notecard " + i; + m_scene.InventoryService.AddItem(item); + } + + } + + [Test] + public void Test_001_RequestOne() + { + TestHelpers.InMethod(); + + Init(); + + FetchInventory2Handler handler = new FetchInventory2Handler(m_scene.InventoryService, m_userID); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + + string request = "itemsitem_id"; + request += "10000000-0000-0000-0000-000000000001"; // Notecard 1 + request += ""; + + string llsdresponse = handler.FetchInventoryRequest(request, "/FETCH", string.Empty, req, resp); + + Assert.That(llsdresponse != null, Is.True, "Incorrect null response"); + Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response"); + Assert.That(llsdresponse.Contains(m_userID.ToString()), Is.True, "Response should contain userID"); + + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000001"), Is.True, "Response does not contain item uuid"); + Assert.That(llsdresponse.Contains("Notecard 1"), Is.True, "Response does not contain item Name"); + Console.WriteLine(llsdresponse); + } + + [Test] + public void Test_002_RequestMany() + { + TestHelpers.InMethod(); + + Init(); + + FetchInventory2Handler handler = new FetchInventory2Handler(m_scene.InventoryService, m_userID); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + + string request = "items"; + request += "item_id10000000-0000-0000-0000-000000000001"; // Notecard 1 + request += "item_id10000000-0000-0000-0000-000000000002"; // Notecard 2 + request += "item_id10000000-0000-0000-0000-000000000003"; // Notecard 3 + request += "item_id10000000-0000-0000-0000-000000000004"; // Notecard 4 + request += "item_id10000000-0000-0000-0000-000000000005"; // Notecard 5 + request += ""; + + string llsdresponse = handler.FetchInventoryRequest(request, "/FETCH", string.Empty, req, resp); + + Assert.That(llsdresponse != null, Is.True, "Incorrect null response"); + Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response"); + Assert.That(llsdresponse.Contains(m_userID.ToString()), Is.True, "Response should contain userID"); + + Console.WriteLine(llsdresponse); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000001"), Is.True, "Response does not contain notecard 1"); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000002"), Is.True, "Response does not contain notecard 2"); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000003"), Is.True, "Response does not contain notecard 3"); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000004"), Is.True, "Response does not contain notecard 4"); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000005"), Is.True, "Response does not contain notecard 5"); + } + + } + +} \ No newline at end of file diff --git a/OpenSim/Capabilities/Handlers/FetchInventory/Tests/FetchInventoryDescendents2HandlerTests.cs b/OpenSim/Capabilities/Handlers/FetchInventory/Tests/FetchInventoryDescendents2HandlerTests.cs new file mode 100644 index 0000000000..2d5531a1ef --- /dev/null +++ b/OpenSim/Capabilities/Handlers/FetchInventory/Tests/FetchInventoryDescendents2HandlerTests.cs @@ -0,0 +1,292 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text.RegularExpressions; +using log4net; +using log4net.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Capabilities.Handlers; +using OpenSim.Framework; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Capabilities.Handlers.FetchInventory.Tests +{ + [TestFixture] + public class FetchInventoryDescendents2HandlerTests : OpenSimTestCase + { + private UUID m_userID = UUID.Zero; + private Scene m_scene; + private UUID m_rootFolderID; + private int m_rootDescendents; + private UUID m_notecardsFolder; + private UUID m_objectsFolder; + + private void Init() + { + // Create an inventory that looks like this: + // + // /My Inventory + // + // /Objects + // Some Object + // /Notecards + // Notecard 1 + // Notecard 2 + // /Test Folder + // Link to notecard -> /Notecards/Notecard 2 + // Link to Objects folder -> /Objects + + m_scene = new SceneHelpers().SetupScene(); + + m_scene.InventoryService.CreateUserInventory(m_userID); + + m_rootFolderID = m_scene.InventoryService.GetRootFolder(m_userID).ID; + + InventoryFolderBase of = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Object); + m_objectsFolder = of.ID; + + // Add an object + InventoryItemBase item = new InventoryItemBase(new UUID("b0000000-0000-0000-0000-00000000000b"), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Object; + item.Folder = m_objectsFolder; + item.Name = "Some Object"; + m_scene.InventoryService.AddItem(item); + + InventoryFolderBase ncf = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Notecard); + m_notecardsFolder = ncf.ID; + + // Add a notecard + item = new InventoryItemBase(new UUID("10000000-0000-0000-0000-000000000001"), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Notecard; + item.Folder = m_notecardsFolder; + item.Name = "Test Notecard 1"; + m_scene.InventoryService.AddItem(item); + // Add another notecard + item.ID = new UUID("20000000-0000-0000-0000-000000000002"); + item.AssetID = new UUID("a0000000-0000-0000-0000-00000000000a"); + item.Name = "Test Notecard 2"; + m_scene.InventoryService.AddItem(item); + + // Add a folder + InventoryFolderBase folder = new InventoryFolderBase(new UUID("f0000000-0000-0000-0000-00000000000f"), "Test Folder", m_userID, m_rootFolderID); + m_scene.InventoryService.AddFolder(folder); + + // Add a link to notecard 2 in Test Folder + item.AssetID = item.ID; // use item ID of notecard 2 + item.ID = new UUID("40000000-0000-0000-0000-000000000004"); + item.AssetType = (int)AssetType.Link; + item.Folder = folder.ID; + item.Name = "Link to notecard"; + m_scene.InventoryService.AddItem(item); + + // Add a link to the Objects folder in Test Folder + item.AssetID = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Object).ID; // use item ID of Objects folder + item.ID = new UUID("50000000-0000-0000-0000-000000000005"); + item.AssetType = (int)AssetType.LinkFolder; + item.Folder = folder.ID; + item.Name = "Link to Objects folder"; + m_scene.InventoryService.AddItem(item); + + InventoryCollection coll = m_scene.InventoryService.GetFolderContent(m_userID, m_rootFolderID); + m_rootDescendents = coll.Items.Count + coll.Folders.Count; + Console.WriteLine("Number of descendents: " + m_rootDescendents); + } + + [Test] + public void Test_001_SimpleFolder() + { + TestHelpers.InMethod(); + + Init(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + + string request = "foldersfetch_folders1fetch_items1folder_id"; + request += m_rootFolderID; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + + string llsdresponse = handler.FetchInventoryDescendentsRequest(request, "/FETCH", string.Empty, req, resp); + + Assert.That(llsdresponse != null, Is.True, "Incorrect null response"); + Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response"); + Assert.That(llsdresponse.Contains("00000000-0000-0000-0000-000000000000"), Is.True, "Response should contain userID"); + + string descendents = "descendents" + m_rootDescendents + ""; + Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents"); + Console.WriteLine(llsdresponse); + } + + [Test] + public void Test_002_MultipleFolders() + { + TestHelpers.InMethod(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + + string request = "folders"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_rootFolderID; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_notecardsFolder; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += ""; + + string llsdresponse = handler.FetchInventoryDescendentsRequest(request, "/FETCH", string.Empty, req, resp); + Console.WriteLine(llsdresponse); + + string descendents = "descendents" + m_rootDescendents + ""; + Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents for root folder"); + descendents = "descendents2"; + Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents for Notecard folder"); + + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000001"), Is.True, "Notecard 1 is missing from response"); + Assert.That(llsdresponse.Contains("20000000-0000-0000-0000-000000000002"), Is.True, "Notecard 2 is missing from response"); + } + + [Test] + public void Test_003_Links() + { + TestHelpers.InMethod(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + + string request = "foldersfetch_folders1fetch_items1folder_id"; + request += "f0000000-0000-0000-0000-00000000000f"; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + + string llsdresponse = handler.FetchInventoryDescendentsRequest(request, "/FETCH", string.Empty, req, resp); + Console.WriteLine(llsdresponse); + + string descendents = "descendents2"; + Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents for Test Folder"); + + // Make sure that the note card link is included + Assert.That(llsdresponse.Contains("Link to notecard"), Is.True, "Link to notecard is missing"); + + //Make sure the notecard item itself is included + Assert.That(llsdresponse.Contains("Test Notecard 2"), Is.True, "Notecard 2 item (the source) is missing"); + + // Make sure that the source item is before the link item + int pos1 = llsdresponse.IndexOf("Test Notecard 2"); + int pos2 = llsdresponse.IndexOf("Link to notecard"); + Assert.Less(pos1, pos2, "Source of link is after link"); + + // Make sure the folder link is included + Assert.That(llsdresponse.Contains("Link to Objects folder"), Is.True, "Link to Objects folder is missing"); + + // Make sure the objects inside the Objects folder are included + // Note: I'm not entirely sure this is needed, but that's what I found in the implementation + Assert.That(llsdresponse.Contains("Some Object"), Is.True, "Some Object item (contents of the source) is missing"); + + // Make sure that the source item is before the link item + pos1 = llsdresponse.IndexOf("Some Object"); + pos2 = llsdresponse.IndexOf("Link to Objects folder"); + Assert.Less(pos1, pos2, "Contents of source of folder link is after folder link"); + } + + [Test] + public void Test_004_DuplicateFolders() + { + TestHelpers.InMethod(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + + string request = "folders"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_rootFolderID; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_notecardsFolder; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_rootFolderID; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_notecardsFolder; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += ""; + + string llsdresponse = handler.FetchInventoryDescendentsRequest(request, "/FETCH", string.Empty, req, resp); + Console.WriteLine(llsdresponse); + + string root_folder = "folder_id" + m_rootFolderID + ""; + string notecards_folder = "folder_id" + m_notecardsFolder + ""; + + Assert.That(llsdresponse.Contains(root_folder), "Missing root folder"); + Assert.That(llsdresponse.Contains(notecards_folder), "Missing notecards folder"); + int count = Regex.Matches(llsdresponse, root_folder).Count; + Assert.AreEqual(1, count, "More than 1 root folder in response"); + count = Regex.Matches(llsdresponse, notecards_folder).Count; + Assert.AreEqual(2, count, "More than 1 notecards folder in response"); // Notecards will also be under root, so 2 + } + + [Test] + public void Test_005_FolderZero() + { + TestHelpers.InMethod(); + + Init(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + + string request = "foldersfetch_folders1fetch_items1folder_id"; + request += UUID.Zero; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + + string llsdresponse = handler.FetchInventoryDescendentsRequest(request, "/FETCH", string.Empty, req, resp); + + Assert.That(llsdresponse != null, Is.True, "Incorrect null response"); + Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response"); + Assert.That(llsdresponse.Contains("bad_folders00000000-0000-0000-0000-000000000000"), Is.True, "Folder Zero should be a bad folder"); + + Console.WriteLine(llsdresponse); + } + + } + +} \ No newline at end of file diff --git a/OpenSim/Capabilities/Handlers/FetchInventory2/FetchInventory2ServerConnector.cs b/OpenSim/Capabilities/Handlers/FetchInventory2/FetchInventory2ServerConnector.cs deleted file mode 100644 index 5bab52fe94..0000000000 --- a/OpenSim/Capabilities/Handlers/FetchInventory2/FetchInventory2ServerConnector.cs +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using Nini.Config; -using OpenSim.Server.Base; -using OpenSim.Services.Interfaces; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Server.Handlers.Base; -using OpenMetaverse; - -namespace OpenSim.Capabilities.Handlers -{ - public class FetchInventory2ServerConnector : ServiceConnector - { - private IInventoryService m_InventoryService; - private string m_ConfigName = "CapsService"; - - public FetchInventory2ServerConnector(IConfigSource config, IHttpServer server, string configName) - : base(config, server, configName) - { - if (configName != String.Empty) - m_ConfigName = configName; - - IConfig serverConfig = config.Configs[m_ConfigName]; - if (serverConfig == null) - throw new Exception(String.Format("No section '{0}' in config file", m_ConfigName)); - - string invService = serverConfig.GetString("InventoryService", String.Empty); - - if (invService == String.Empty) - throw new Exception("No InventoryService in config file"); - - Object[] args = new Object[] { config }; - m_InventoryService = ServerUtils.LoadPlugin(invService, args); - - if (m_InventoryService == null) - throw new Exception(String.Format("Failed to load InventoryService from {0}; config is {1}", invService, m_ConfigName)); - - FetchInventory2Handler fiHandler = new FetchInventory2Handler(m_InventoryService); - IRequestHandler reqHandler - = new RestStreamHandler( - "POST", "/CAPS/FetchInventory/", fiHandler.FetchInventoryRequest, "FetchInventory", null); - server.AddStreamHandler(reqHandler); - } - } -} diff --git a/OpenSim/Region/ClientStack/Linden/Caps/FetchInventory2Module.cs b/OpenSim/Region/ClientStack/Linden/Caps/FetchInventory2Module.cs index 87d3d1c676..e0a11ccff6 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/FetchInventory2Module.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/FetchInventory2Module.cs @@ -25,20 +25,16 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -using System; -using System.Collections; -using System.Reflection; -using log4net; -using Nini.Config; using Mono.Addins; +using Nini.Config; using OpenMetaverse; -using OpenSim.Framework; +using OpenSim.Capabilities.Handlers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; +using System; using Caps = OpenSim.Framework.Capabilities.Caps; -using OpenSim.Capabilities.Handlers; namespace OpenSim.Region.ClientStack.Linden { @@ -58,8 +54,6 @@ namespace OpenSim.Region.ClientStack.Linden private string m_fetchInventory2Url; - private FetchInventory2Handler m_fetchHandler; - #region ISharedRegionModule Members public void Initialise(IConfigSource source) @@ -98,10 +92,6 @@ namespace OpenSim.Region.ClientStack.Linden m_inventoryService = m_scene.InventoryService; - // We'll reuse the same handler for all requests. - if (m_fetchInventory2Url == "localhost") - m_fetchHandler = new FetchInventory2Handler(m_inventoryService); - m_scene.EventManager.OnRegisterCaps += RegisterCaps; } @@ -131,9 +121,11 @@ namespace OpenSim.Region.ClientStack.Linden { capUrl = "/CAPS/" + UUID.Random(); + FetchInventory2Handler fetchHandler = new FetchInventory2Handler(m_inventoryService, agentID); + IRequestHandler reqHandler = new RestStreamHandler( - "POST", capUrl, m_fetchHandler.FetchInventoryRequest, capName, agentID.ToString()); + "POST", capUrl, fetchHandler.FetchInventoryRequest, capName, agentID.ToString()); caps.RegisterHandler(capName, reqHandler); } diff --git a/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs index 5f3e66a0b6..2a252e17af 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs @@ -66,7 +66,26 @@ namespace OpenSim.Region.ClientStack.Linden private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private Scene m_scene; + /// + /// Control whether requests will be processed asynchronously. + /// + /// + /// Defaults to true. Can currently not be changed once a region has been added to the module. + /// + public bool ProcessQueuedRequestsAsync { get; private set; } + + /// + /// Number of inventory requests processed by this module. + /// + /// + /// It's the PollServiceRequestManager that actually sends completed requests back to the requester. + /// + public static int ProcessedRequestsCount { get; set; } + + private static Stat s_queuedRequestsStat; + private static Stat s_processedRequestsStat; + + public Scene Scene { get; private set; } private IInventoryService m_InventoryService; private ILibraryService m_LibraryService; @@ -76,7 +95,7 @@ namespace OpenSim.Region.ClientStack.Linden private string m_fetchInventoryDescendents2Url; private string m_webFetchInventoryDescendentsUrl; - private static WebFetchInvDescHandler m_webFetchHandler; + private static FetchInvDescHandler m_webFetchHandler; private static Thread[] m_workerThreads = null; @@ -85,6 +104,13 @@ namespace OpenSim.Region.ClientStack.Linden #region ISharedRegionModule Members + public WebFetchInvDescModule() : this(true) {} + + public WebFetchInvDescModule(bool processQueuedResultsAsync) + { + ProcessQueuedRequestsAsync = processQueuedResultsAsync; + } + public void Initialise(IConfigSource source) { IConfig config = source.Configs["ClientStack.LindenCaps"]; @@ -92,8 +118,9 @@ namespace OpenSim.Region.ClientStack.Linden return; m_fetchInventoryDescendents2Url = config.GetString("Cap_FetchInventoryDescendents2", string.Empty); - m_webFetchInventoryDescendentsUrl = config.GetString("Cap_WebFetchInventoryDescendents", string.Empty); +// m_webFetchInventoryDescendentsUrl = config.GetString("Cap_WebFetchInventoryDescendents", string.Empty); +// if (m_fetchInventoryDescendents2Url != string.Empty || m_webFetchInventoryDescendentsUrl != string.Empty) if (m_fetchInventoryDescendents2Url != string.Empty || m_webFetchInventoryDescendentsUrl != string.Empty) { m_Enabled = true; @@ -105,7 +132,7 @@ namespace OpenSim.Region.ClientStack.Linden if (!m_Enabled) return; - m_scene = s; + Scene = s; } public void RemoveRegion(Scene s) @@ -113,12 +140,23 @@ namespace OpenSim.Region.ClientStack.Linden if (!m_Enabled) return; - m_scene.EventManager.OnRegisterCaps -= RegisterCaps; + Scene.EventManager.OnRegisterCaps -= RegisterCaps; - foreach (Thread t in m_workerThreads) - Watchdog.AbortThread(t.ManagedThreadId); + StatsManager.DeregisterStat(s_processedRequestsStat); + StatsManager.DeregisterStat(s_queuedRequestsStat); - m_scene = null; + if (ProcessQueuedRequestsAsync) + { + if (m_workerThreads != null) + { + foreach (Thread t in m_workerThreads) + Watchdog.AbortThread(t.ManagedThreadId); + + m_workerThreads = null; + } + } + + Scene = null; } public void RegionLoaded(Scene s) @@ -126,19 +164,51 @@ namespace OpenSim.Region.ClientStack.Linden if (!m_Enabled) return; - m_InventoryService = m_scene.InventoryService; - m_LibraryService = m_scene.LibraryService; + if (s_processedRequestsStat == null) + s_processedRequestsStat = + new Stat( + "ProcessedFetchInventoryRequests", + "Number of processed fetch inventory requests", + "These have not necessarily yet been dispatched back to the requester.", + "", + "inventory", + "httpfetch", + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => { stat.Value = ProcessedRequestsCount; }, + StatVerbosity.Debug); + + if (s_queuedRequestsStat == null) + s_queuedRequestsStat = + new Stat( + "QueuedFetchInventoryRequests", + "Number of fetch inventory requests queued for processing", + "", + "", + "inventory", + "httpfetch", + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => { stat.Value = m_queue.Count; }, + StatVerbosity.Debug); + + StatsManager.RegisterStat(s_processedRequestsStat); + StatsManager.RegisterStat(s_queuedRequestsStat); + + m_InventoryService = Scene.InventoryService; + m_LibraryService = Scene.LibraryService; // We'll reuse the same handler for all requests. - m_webFetchHandler = new WebFetchInvDescHandler(m_InventoryService, m_LibraryService); + m_webFetchHandler = new FetchInvDescHandler(m_InventoryService, m_LibraryService, Scene); - m_scene.EventManager.OnRegisterCaps += RegisterCaps; + Scene.EventManager.OnRegisterCaps += RegisterCaps; - if (m_workerThreads == null) + int nworkers = 2; // was 2 + if (ProcessQueuedRequestsAsync && m_workerThreads == null) { - m_workerThreads = new Thread[2]; + m_workerThreads = new Thread[nworkers]; - for (uint i = 0; i < 2; i++) + for (uint i = 0; i < nworkers; i++) { m_workerThreads[i] = WorkManager.StartThread(DoInventoryRequests, String.Format("InventoryWorkerThread{0}", i), @@ -173,12 +243,12 @@ namespace OpenSim.Region.ClientStack.Linden private Dictionary responses = new Dictionary(); - private Scene m_scene; + private WebFetchInvDescModule m_module; - public PollServiceInventoryEventArgs(Scene scene, string url, UUID pId) : - base(null, url, null, null, null, pId, int.MaxValue) + public PollServiceInventoryEventArgs(WebFetchInvDescModule module, string url, UUID pId) : + base(null, url, null, null, null, pId, int.MaxValue) { - m_scene = scene; + m_module = module; HasEvents = (x, y) => { lock (responses) return responses.ContainsKey(x); }; GetEvents = (x, y) => @@ -198,12 +268,7 @@ namespace OpenSim.Region.ClientStack.Linden Request = (x, y) => { - ScenePresence sp = m_scene.GetScenePresence(Id); - if (sp == null) - { - m_log.ErrorFormat("[INVENTORY]: Unable to find ScenePresence for {0}", Id); - return; - } + ScenePresence sp = m_module.Scene.GetScenePresence(Id); aPollRequest reqinfo = new aPollRequest(); reqinfo.thepoll = this; @@ -298,7 +363,13 @@ namespace OpenSim.Region.ClientStack.Linden requestinfo.request["body"].ToString(), String.Empty, String.Empty, null, null); lock (responses) + { + if (responses.ContainsKey(requestID)) + m_log.WarnFormat("[FETCH INVENTORY DESCENDENTS2 MODULE]: Caught in the act of loosing responses! Please report this on mantis #7054"); responses[requestID] = response; + } + + WebFetchInvDescModule.ProcessedRequestsCount++; } } @@ -322,7 +393,7 @@ namespace OpenSim.Region.ClientStack.Linden capUrl = "/CAPS/" + UUID.Random() + "/"; // Register this as a poll service - PollServiceInventoryEventArgs args = new PollServiceInventoryEventArgs(m_scene, capUrl, agentID); + PollServiceInventoryEventArgs args = new PollServiceInventoryEventArgs(this, capUrl, agentID); args.Type = PollServiceEventArgs.EventType.Inventory; caps.RegisterPollHandler(capName, args); @@ -331,7 +402,7 @@ namespace OpenSim.Region.ClientStack.Linden else { capUrl = url; - IExternalCapsModule handler = m_scene.RequestModuleInterface(); + IExternalCapsModule handler = Scene.RequestModuleInterface(); if (handler != null) handler.RegisterExternalUserCapsHandler(agentID,caps,capName,capUrl); else @@ -360,10 +431,26 @@ namespace OpenSim.Region.ClientStack.Linden { Watchdog.UpdateThread(); - aPollRequest poolreq = m_queue.Dequeue(); + WaitProcessQueuedInventoryRequest(); + } + } - if (poolreq != null && poolreq.thepoll != null) + public void WaitProcessQueuedInventoryRequest() + { + aPollRequest poolreq = m_queue.Dequeue(); + + if (poolreq != null && poolreq.thepoll != null) + { + try + { poolreq.thepoll.Process(poolreq); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[INVENTORY]: Failed to process queued inventory request {0} for {1} in {2}. Exception {3}", + poolreq.reqID, poolreq.presence != null ? poolreq.presence.Name : "unknown", Scene.Name, e); + } } } }