/* * 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 log4net; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using Nini.Config; using OpenSim.Framework; using OpenSim.Framework.Console; using OpenSim.Framework.Monitoring; using OpenSim.Services.Interfaces; using OpenSim.Server.Base; using OpenMetaverse; namespace OpenSim.Services.Connectors { public class XInventoryServicesConnector : BaseServiceConnector, IInventoryService { private static readonly ILog m_log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType); /// /// Number of requests made to the remote inventory service. /// public int RequestsMade { get; private set; } private string m_ServerURI = String.Empty; private int m_maxRetries = 0; /// /// Timeout for remote requests. /// /// /// In this case, -1 is default timeout (100 seconds), not infinite. /// private int m_requestTimeoutSecs = -1; private const double CACHE_EXPIRATION_SECONDS = 20.0; private static ExpiringCache m_ItemCache = new ExpiringCache(); public XInventoryServicesConnector() { } public XInventoryServicesConnector(string serverURI) { m_ServerURI = serverURI.TrimEnd('/'); } public XInventoryServicesConnector(IConfigSource source) : base(source, "InventoryService") { Initialise(source); } public virtual void Initialise(IConfigSource source) { IConfig config = source.Configs["InventoryService"]; if (config == null) { m_log.Error("[INVENTORY CONNECTOR]: InventoryService missing from OpenSim.ini"); throw new Exception("Inventory connector init error"); } string serviceURI = config.GetString("InventoryServerURI", String.Empty); if (serviceURI == String.Empty) { m_log.Error("[INVENTORY CONNECTOR]: No Server URI named in section InventoryService"); throw new Exception("Inventory connector init error"); } m_ServerURI = serviceURI; m_requestTimeoutSecs = config.GetInt("RemoteRequestTimeout", m_requestTimeoutSecs); m_maxRetries = config.GetInt("MaxRetries", m_maxRetries); StatsManager.RegisterStat( new Stat( "RequestsMade", "Requests made", "Number of requests made to the remove inventory service", "requests", "inventory", serviceURI, StatType.Pull, MeasuresOfInterest.AverageChangeOverTime, s => s.Value = RequestsMade, StatVerbosity.Debug)); } private bool CheckReturn(Dictionary ret) { if (ret == null) return false; if (ret.Count == 0) return false; if (ret.ContainsKey("RESULT")) { if (ret["RESULT"] is string) { bool result; if (bool.TryParse((string)ret["RESULT"], out result)) return result; return false; } } return true; } public bool CreateUserInventory(UUID principalID) { Dictionary ret = MakeRequest("CREATEUSERINVENTORY", new Dictionary { { "PRINCIPAL", principalID.ToString() } }); return CheckReturn(ret); } public List GetInventorySkeleton(UUID principalID) { Dictionary ret = MakeRequest("GETINVENTORYSKELETON", new Dictionary { { "PRINCIPAL", principalID.ToString() } }); if (!CheckReturn(ret)) return null; Dictionary folders = (Dictionary)ret["FOLDERS"]; List fldrs = new List(); try { foreach (Object o in folders.Values) fldrs.Add(BuildFolder((Dictionary)o)); } catch (Exception e) { m_log.Error("[XINVENTORY SERVICES CONNECTOR]: Exception unwrapping folder list: ", e); } return fldrs; } public InventoryFolderBase GetRootFolder(UUID principalID) { Dictionary ret = MakeRequest("GETROOTFOLDER", new Dictionary { { "PRINCIPAL", principalID.ToString() } }); if (!CheckReturn(ret)) return null; return BuildFolder((Dictionary)ret["folder"]); } public InventoryFolderBase GetFolderForType(UUID principalID, FolderType type) { Dictionary ret = MakeRequest("GETFOLDERFORTYPE", new Dictionary { { "PRINCIPAL", principalID.ToString() }, { "TYPE", ((int)type).ToString() } }); if (!CheckReturn(ret)) return null; return BuildFolder((Dictionary)ret["folder"]); } public InventoryCollection GetFolderContent(UUID principalID, UUID folderID) { InventoryCollection inventory = new InventoryCollection(); inventory.Folders = new List(); inventory.Items = new List(); inventory.OwnerID = principalID; try { Dictionary ret = MakeRequest("GETFOLDERCONTENT", new Dictionary { { "PRINCIPAL", principalID.ToString() }, { "FOLDER", folderID.ToString() } }); if (!CheckReturn(ret)) return null; Dictionary folders = ret.ContainsKey("FOLDERS") ? (Dictionary)ret["FOLDERS"] : null; Dictionary items = ret.ContainsKey("ITEMS") ? (Dictionary)ret["ITEMS"] : null; if (folders != null) foreach (Object o in folders.Values) // getting the values directly, we don't care about the keys folder_i inventory.Folders.Add(BuildFolder((Dictionary)o)); if (items != null) foreach (Object o in items.Values) // getting the values directly, we don't care about the keys item_i inventory.Items.Add(BuildItem((Dictionary)o)); } catch (Exception e) { m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: Exception in GetFolderContent: {0}", e.Message); } return inventory; } public virtual InventoryCollection[] GetMultipleFoldersContent(UUID principalID, UUID[] folderIDs) { InventoryCollection[] inventoryArr = new InventoryCollection[folderIDs.Length]; // m_log.DebugFormat("[XXX]: In GetMultipleFoldersContent {0}", String.Join(",", folderIDs)); try { Dictionary resultSet = MakeRequest("GETMULTIPLEFOLDERSCONTENT", new Dictionary { { "PRINCIPAL", principalID.ToString() }, { "FOLDERS", String.Join(",", folderIDs) }, { "COUNT", folderIDs.Length.ToString() } }); if (!CheckReturn(resultSet)) return null; int i = 0; foreach (KeyValuePair kvp in resultSet) { InventoryCollection inventory = new InventoryCollection(); if (kvp.Key.StartsWith("F_")) { UUID fid = UUID.Zero; if (UUID.TryParse(kvp.Key.Substring(2), out fid) && fid == folderIDs[i]) { inventory.Folders = new List(); inventory.Items = new List(); Dictionary ret = (Dictionary)kvp.Value; if (ret.ContainsKey("FID")) { if (!UUID.TryParse(ret["FID"].ToString(), out inventory.FolderID)) m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: Could not parse folder id {0}", ret["FID"].ToString()); } else m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: FID key not present in response"); inventory.Version = -1; if (ret.ContainsKey("VERSION")) Int32.TryParse(ret["VERSION"].ToString(), out inventory.Version); if (ret.ContainsKey("OWNER")) UUID.TryParse(ret["OWNER"].ToString(), out inventory.OwnerID); //m_log.DebugFormat("[XXX]: Received {0} ({1}) {2} {3}", inventory.FolderID, fid, inventory.Version, inventory.OwnerID); Dictionary folders = (Dictionary)ret["FOLDERS"]; Dictionary items = (Dictionary)ret["ITEMS"]; foreach (Object o in folders.Values) // getting the values directly, we don't care about the keys folder_i { inventory.Folders.Add(BuildFolder((Dictionary)o)); } foreach (Object o in items.Values) // getting the values directly, we don't care about the keys item_i { inventory.Items.Add(BuildItem((Dictionary)o)); } inventoryArr[i] = inventory; } else { m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: Folder id does not match. Expected {0} got {1}", folderIDs[i], fid); m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: {0} {1}", String.Join(",", folderIDs), String.Join(",", resultSet.Keys)); } i += 1; } } } catch (Exception e) { m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: Exception in GetMultipleFoldersContent: {0}", e.Message); } return inventoryArr; } public List GetFolderItems(UUID principalID, UUID folderID) { Dictionary ret = MakeRequest("GETFOLDERITEMS", new Dictionary { { "PRINCIPAL", principalID.ToString() }, { "FOLDER", folderID.ToString() } }); if (!CheckReturn(ret)) return null; Dictionary items = (Dictionary)ret["ITEMS"]; List fitems = new List(); foreach (Object o in items.Values) // getting the values directly, we don't care about the keys item_i fitems.Add(BuildItem((Dictionary)o)); return fitems; } public bool AddFolder(InventoryFolderBase folder) { Dictionary ret = MakeRequest("ADDFOLDER", new Dictionary { { "ParentID", folder.ParentID.ToString() }, { "Type", folder.Type.ToString() }, { "Version", folder.Version.ToString() }, { "Name", folder.Name.ToString() }, { "Owner", folder.Owner.ToString() }, { "ID", folder.ID.ToString() } }); return CheckReturn(ret); } public bool UpdateFolder(InventoryFolderBase folder) { Dictionary ret = MakeRequest("UPDATEFOLDER", new Dictionary { { "ParentID", folder.ParentID.ToString() }, { "Type", folder.Type.ToString() }, { "Version", folder.Version.ToString() }, { "Name", folder.Name.ToString() }, { "Owner", folder.Owner.ToString() }, { "ID", folder.ID.ToString() } }); return CheckReturn(ret); } public bool MoveFolder(InventoryFolderBase folder) { Dictionary ret = MakeRequest("MOVEFOLDER", new Dictionary { { "ParentID", folder.ParentID.ToString() }, { "ID", folder.ID.ToString() }, { "PRINCIPAL", folder.Owner.ToString() } }); return CheckReturn(ret); } public bool DeleteFolders(UUID principalID, List folderIDs) { List slist = new List(); foreach (UUID f in folderIDs) slist.Add(f.ToString()); Dictionary ret = MakeRequest("DELETEFOLDERS", new Dictionary { { "PRINCIPAL", principalID.ToString() }, { "FOLDERS", slist } }); return CheckReturn(ret); } public bool PurgeFolder(InventoryFolderBase folder) { Dictionary ret = MakeRequest("PURGEFOLDER", new Dictionary { { "ID", folder.ID.ToString() } }); return CheckReturn(ret); } public bool AddItem(InventoryItemBase item) { if (item.Description == null) item.Description = String.Empty; if (item.CreatorData == null) item.CreatorData = String.Empty; if (item.CreatorId == null) item.CreatorId = String.Empty; Dictionary ret = MakeRequest("ADDITEM", new Dictionary { { "AssetID", item.AssetID.ToString() }, { "AssetType", item.AssetType.ToString() }, { "Name", item.Name.ToString() }, { "Owner", item.Owner.ToString() }, { "ID", item.ID.ToString() }, { "InvType", item.InvType.ToString() }, { "Folder", item.Folder.ToString() }, { "CreatorId", item.CreatorId.ToString() }, { "CreatorData", item.CreatorData.ToString() }, { "Description", item.Description.ToString() }, { "NextPermissions", item.NextPermissions.ToString() }, { "CurrentPermissions", item.CurrentPermissions.ToString() }, { "BasePermissions", item.BasePermissions.ToString() }, { "EveryOnePermissions", item.EveryOnePermissions.ToString() }, { "GroupPermissions", item.GroupPermissions.ToString() }, { "GroupID", item.GroupID.ToString() }, { "GroupOwned", item.GroupOwned.ToString() }, { "SalePrice", item.SalePrice.ToString() }, { "SaleType", item.SaleType.ToString() }, { "Flags", item.Flags.ToString() }, { "CreationDate", item.CreationDate.ToString() } }); return CheckReturn(ret); } public bool UpdateItem(InventoryItemBase item) { if (item.CreatorData == null) item.CreatorData = String.Empty; Dictionary ret = MakeRequest("UPDATEITEM", new Dictionary { { "AssetID", item.AssetID.ToString() }, { "AssetType", item.AssetType.ToString() }, { "Name", item.Name.ToString() }, { "Owner", item.Owner.ToString() }, { "ID", item.ID.ToString() }, { "InvType", item.InvType.ToString() }, { "Folder", item.Folder.ToString() }, { "CreatorId", item.CreatorId.ToString() }, { "CreatorData", item.CreatorData.ToString() }, { "Description", item.Description.ToString() }, { "NextPermissions", item.NextPermissions.ToString() }, { "CurrentPermissions", item.CurrentPermissions.ToString() }, { "BasePermissions", item.BasePermissions.ToString() }, { "EveryOnePermissions", item.EveryOnePermissions.ToString() }, { "GroupPermissions", item.GroupPermissions.ToString() }, { "GroupID", item.GroupID.ToString() }, { "GroupOwned", item.GroupOwned.ToString() }, { "SalePrice", item.SalePrice.ToString() }, { "SaleType", item.SaleType.ToString() }, { "Flags", item.Flags.ToString() }, { "CreationDate", item.CreationDate.ToString() } }); bool result = CheckReturn(ret); if (result) { m_ItemCache.AddOrUpdate(item.ID, item, CACHE_EXPIRATION_SECONDS); } return result; } public bool MoveItems(UUID principalID, List items) { List idlist = new List(); List destlist = new List(); foreach (InventoryItemBase item in items) { idlist.Add(item.ID.ToString()); destlist.Add(item.Folder.ToString()); } Dictionary ret = MakeRequest("MOVEITEMS", new Dictionary { { "PRINCIPAL", principalID.ToString() }, { "IDLIST", idlist }, { "DESTLIST", destlist } }); return CheckReturn(ret); } public bool DeleteItems(UUID principalID, List itemIDs) { List slist = new List(); foreach (UUID f in itemIDs) slist.Add(f.ToString()); Dictionary ret = MakeRequest("DELETEITEMS", new Dictionary { { "PRINCIPAL", principalID.ToString() }, { "ITEMS", slist } }); return CheckReturn(ret); } public InventoryItemBase GetItem(UUID principalID, UUID itemID) { InventoryItemBase retrieved = null; if (m_ItemCache.TryGetValue(itemID, out retrieved)) { return retrieved; } try { Dictionary ret = MakeRequest("GETITEM", new Dictionary { { "ID", itemID.ToString() }, { "PRINCIPAL", principalID.ToString() } }); if (!CheckReturn(ret)) return null; retrieved = BuildItem((Dictionary)ret["item"]); } catch (Exception e) { m_log.Error("[XINVENTORY SERVICES CONNECTOR]: Exception in GetItem: ", e); } m_ItemCache.AddOrUpdate(itemID, retrieved, CACHE_EXPIRATION_SECONDS); return retrieved; } public virtual InventoryItemBase[] GetMultipleItems(UUID principalID, UUID[] itemIDs) { //m_log.DebugFormat("[XXX]: In GetMultipleItems {0}", String.Join(",", itemIDs)); InventoryItemBase[] itemArr = new InventoryItemBase[itemIDs.Length]; // Try to get them from the cache List pending = new List(); InventoryItemBase item = null; int i = 0; foreach (UUID id in itemIDs) { if (m_ItemCache.TryGetValue(id, out item)) itemArr[i++] = item; else pending.Add(id); } if (pending.Count == 0) // we're done, everything was in the cache return itemArr; try { Dictionary resultSet = MakeRequest("GETMULTIPLEITEMS", new Dictionary { { "PRINCIPAL", principalID.ToString() }, { "ITEMS", String.Join(",", pending.ToArray()) }, { "COUNT", pending.Count.ToString() } }); if (!CheckReturn(resultSet)) { if (i == 0) return null; else return itemArr; } // carry over index i where we left above foreach (KeyValuePair kvp in resultSet) { InventoryCollection inventory = new InventoryCollection(); if (kvp.Key.StartsWith("item_")) { if (kvp.Value is Dictionary) { item = BuildItem((Dictionary)kvp.Value); m_ItemCache.AddOrUpdate(item.ID, item, CACHE_EXPIRATION_SECONDS); itemArr[i++] = item; } else itemArr[i++] = null; } } } catch (Exception e) { m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: Exception in GetMultipleItems: {0}", e.Message); } return itemArr; } public InventoryFolderBase GetFolder(UUID principalID, UUID folderID) { try { Dictionary ret = MakeRequest("GETFOLDER", new Dictionary { { "ID", folderID.ToString() }, { "PRINCIPAL", principalID.ToString() } }); if (!CheckReturn(ret)) return null; return BuildFolder((Dictionary)ret["folder"]); } catch (Exception e) { m_log.Error("[XINVENTORY SERVICES CONNECTOR]: Exception in GetFolder: ", e); } return null; } public List GetActiveGestures(UUID principalID) { Dictionary ret = MakeRequest("GETACTIVEGESTURES", new Dictionary { { "PRINCIPAL", principalID.ToString() } }); if (!CheckReturn(ret)) return null; List items = new List(); foreach (Object o in ((Dictionary)ret["ITEMS"]).Values) items.Add(BuildItem((Dictionary)o)); return items; } public int GetAssetPermissions(UUID principalID, UUID assetID) { Dictionary ret = MakeRequest("GETASSETPERMISSIONS", new Dictionary { { "PRINCIPAL", principalID.ToString() }, { "ASSET", assetID.ToString() } }); // We cannot use CheckReturn() here because valid values for RESULT are "false" (in the case of request failure) or an int if (ret == null) return 0; if (ret.ContainsKey("RESULT")) { if (ret["RESULT"] is string) { int intResult; if (int.TryParse ((string)ret["RESULT"], out intResult)) return intResult; } } return 0; } public bool HasInventoryForUser(UUID principalID) { return false; } // Helpers // private Dictionary MakeRequest(string method, Dictionary sendData) { // Add "METHOD" as the first key in the dictionary. This ensures that it will be // visible even when using partial logging ("debug http all 5"). Dictionary temp = sendData; sendData = new Dictionary{ { "METHOD", method } }; foreach (KeyValuePair kvp in temp) sendData.Add(kvp.Key, kvp.Value); RequestsMade++; string reply = String.Empty; int retries = 0; do { reply = SynchronousRestFormsRequester.MakeRequest( "POST", m_ServerURI + "/xinventory", ServerUtils.BuildQueryString(sendData), m_requestTimeoutSecs, m_Auth); if (reply != String.Empty) break; retries++; } while (retries <= m_maxRetries); Dictionary replyData = ServerUtils.ParseXmlResponse( reply); return replyData; } private InventoryFolderBase BuildFolder(Dictionary data) { InventoryFolderBase folder = new InventoryFolderBase(); try { folder.ParentID = new UUID(data["ParentID"].ToString()); folder.Type = short.Parse(data["Type"].ToString()); folder.Version = ushort.Parse(data["Version"].ToString()); folder.Name = data["Name"].ToString(); folder.Owner = new UUID(data["Owner"].ToString()); folder.ID = new UUID(data["ID"].ToString()); } catch (Exception e) { m_log.Error("[XINVENTORY SERVICES CONNECTOR]: Exception building folder: ", e); } return folder; } private InventoryItemBase BuildItem(Dictionary data) { InventoryItemBase item = new InventoryItemBase(); try { item.AssetID = new UUID(data["AssetID"].ToString()); item.AssetType = int.Parse(data["AssetType"].ToString()); item.Name = data["Name"].ToString(); item.Owner = new UUID(data["Owner"].ToString()); item.ID = new UUID(data["ID"].ToString()); item.InvType = int.Parse(data["InvType"].ToString()); item.Folder = new UUID(data["Folder"].ToString()); item.CreatorId = data["CreatorId"].ToString(); if (data.ContainsKey("CreatorData")) item.CreatorData = data["CreatorData"].ToString(); else item.CreatorData = String.Empty; item.Description = data["Description"].ToString(); item.NextPermissions = uint.Parse(data["NextPermissions"].ToString()); item.CurrentPermissions = uint.Parse(data["CurrentPermissions"].ToString()); item.BasePermissions = uint.Parse(data["BasePermissions"].ToString()); item.EveryOnePermissions = uint.Parse(data["EveryOnePermissions"].ToString()); item.GroupPermissions = uint.Parse(data["GroupPermissions"].ToString()); item.GroupID = new UUID(data["GroupID"].ToString()); item.GroupOwned = bool.Parse(data["GroupOwned"].ToString()); item.SalePrice = int.Parse(data["SalePrice"].ToString()); item.SaleType = byte.Parse(data["SaleType"].ToString()); item.Flags = uint.Parse(data["Flags"].ToString()); item.CreationDate = int.Parse(data["CreationDate"].ToString()); } catch (Exception e) { m_log.Error("[XINVENTORY CONNECTOR]: Exception building item: ", e); } return item; } } }