XInventoryServicesConnector.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using log4net;
  28. using System;
  29. using System.Collections.Generic;
  30. using System.IO;
  31. using System.Net;
  32. using System.Net.Http;
  33. using System.Reflection;
  34. using Nini.Config;
  35. using OpenSim.Framework;
  36. using OpenSim.Framework.Monitoring;
  37. using OpenSim.Framework.ServiceAuth;
  38. using OpenSim.Services.Interfaces;
  39. using OpenSim.Server.Base;
  40. using OpenMetaverse;
  41. using System.Text;
  42. using System.Threading;
  43. namespace OpenSim.Services.Connectors
  44. {
  45. public class XInventoryServicesConnector : BaseServiceConnector, IInventoryService
  46. {
  47. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  48. /// <summary>
  49. /// Number of requests made to the remote inventory service.
  50. /// </summary>
  51. public int RequestsMade { get; private set; }
  52. private string m_InventoryURL = string.Empty;
  53. /// <summary>
  54. /// Timeout for remote requests.
  55. /// </summary>
  56. /// <remarks>
  57. /// In this case, -1 is default timeout (100 seconds), not infinite.
  58. /// </remarks>
  59. private int m_requestTimeout = -1;
  60. private readonly string m_configName = "InventoryService";
  61. private const double CACHE_EXPIRATION_SECONDS = 30.0;
  62. private static readonly ExpiringCacheOS<UUID, InventoryItemBase> m_ItemCache = new(15000);
  63. public XInventoryServicesConnector()
  64. {
  65. }
  66. public XInventoryServicesConnector(string serverURI)
  67. {
  68. if (serverURI.EndsWith('/'))
  69. m_InventoryURL = serverURI + "xinventory";
  70. else
  71. m_InventoryURL = serverURI + "/xinventory";
  72. }
  73. public XInventoryServicesConnector(IConfigSource source, string configName)
  74. : base(source, configName)
  75. {
  76. m_configName = configName;
  77. Initialise(source);
  78. }
  79. public XInventoryServicesConnector(IConfigSource source)
  80. : base(source, "InventoryService")
  81. {
  82. Initialise(source);
  83. }
  84. public virtual void Initialise(IConfigSource source)
  85. {
  86. IConfig config = source.Configs[m_configName];
  87. if (config is null)
  88. {
  89. m_log.ErrorFormat("[INVENTORY CONNECTOR]: {0} missing from OpenSim.ini", m_configName);
  90. throw new Exception("Inventory connector init error");
  91. }
  92. string serviceURI = config.GetString("InventoryServerURI", string.Empty);
  93. if (serviceURI.Length == 0)
  94. {
  95. m_log.Error("[INVENTORY CONNECTOR]: No Server URI named in section InventoryService");
  96. throw new Exception("Inventory connector init error");
  97. }
  98. if (serviceURI.EndsWith('/'))
  99. m_InventoryURL = serviceURI + "xinventory";
  100. else
  101. m_InventoryURL = serviceURI + "/xinventory";
  102. m_requestTimeout = 1000 * config.GetInt("RemoteRequestTimeout", -1);
  103. StatsManager.RegisterStat(
  104. new Stat(
  105. "RequestsMade",
  106. "Requests made",
  107. "Number of requests made to the remove inventory service",
  108. "requests",
  109. "inventory",
  110. serviceURI,
  111. StatType.Pull,
  112. MeasuresOfInterest.AverageChangeOverTime,
  113. s => s.Value = RequestsMade,
  114. StatVerbosity.Debug));
  115. }
  116. private static bool CheckReturn(Dictionary<string, object> ret)
  117. {
  118. if (ret is null || ret.Count == 0)
  119. return false;
  120. if (ret.TryGetValue("RESULT", out object retResult))
  121. {
  122. if (retResult is string sretResult)
  123. {
  124. if (bool.TryParse(sretResult, out bool result))
  125. return result;
  126. return false;
  127. }
  128. }
  129. return true;
  130. }
  131. public bool CreateUserInventory(UUID principalID)
  132. {
  133. Dictionary<string,object> ret = MakeRequest(
  134. $"METHOD=CREATEUSERINVENTORY&PRINCIPAL={principalID}");
  135. return CheckReturn(ret);
  136. }
  137. public List<InventoryFolderBase> GetInventorySkeleton(UUID principalID)
  138. {
  139. Dictionary<string,object> ret = MakeRequest(
  140. $"METHOD=GETINVENTORYSKELETON&PRINCIPAL={principalID}");
  141. if (!CheckReturn(ret))
  142. return null;
  143. Dictionary<string, object> folders = (Dictionary<string, object>)ret["FOLDERS"];
  144. List<InventoryFolderBase> fldrs = new();
  145. try
  146. {
  147. foreach (Object o in folders.Values)
  148. fldrs.Add(BuildFolder((Dictionary<string, object>)o));
  149. }
  150. catch (Exception e)
  151. {
  152. m_log.Error("[XINVENTORY SERVICES CONNECTOR]: Exception unwrapping folder list: " + e.Message);
  153. }
  154. return fldrs;
  155. }
  156. public InventoryFolderBase GetRootFolder(UUID principalID)
  157. {
  158. Dictionary<string,object> ret = MakeRequest($"METHOD=GETROOTFOLDER&PRINCIPAL={principalID}");
  159. if (!CheckReturn(ret))
  160. return null;
  161. return BuildFolder((Dictionary<string, object>)ret["folder"]);
  162. }
  163. public InventoryFolderBase GetFolderForType(UUID principalID, FolderType type)
  164. {
  165. Dictionary<string,object> ret = MakeRequest(
  166. $"METHOD=GETFOLDERFORTYPE&PRINCIPAL={principalID}&TYPE={(int)type}");
  167. if (!CheckReturn(ret))
  168. return null;
  169. return BuildFolder((Dictionary<string, object>)ret["folder"]);
  170. }
  171. public InventoryCollection GetFolderContent(UUID principalID, UUID folderID)
  172. {
  173. InventoryCollection inventory = new()
  174. {
  175. Folders = new(),
  176. Items = new(),
  177. OwnerID = principalID
  178. };
  179. try
  180. {
  181. Dictionary<string,object> ret = MakeRequest(
  182. $"METHOD=GETFOLDERCONTENT&PRINCIPAL={principalID}&FOLDER={folderID}");
  183. if (!CheckReturn(ret))
  184. return null;
  185. if(ret.TryGetValue("FOLDERS", out object ofolders))
  186. {
  187. var folders = (Dictionary<string, object>)ofolders;
  188. foreach (object o in folders.Values) // getting the values directly, we don't care about the keys folder_i
  189. inventory.Folders.Add(BuildFolder((Dictionary<string, object>)o));
  190. }
  191. if(ret.TryGetValue("ITEMS", out object oitems))
  192. {
  193. var items = (Dictionary<string, object>)oitems;
  194. foreach (object o in items.Values) // getting the values directly, we don't care about the keys item_i
  195. inventory.Items.Add(BuildItem((Dictionary<string, object>)o));
  196. }
  197. }
  198. catch (Exception e)
  199. {
  200. m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: Exception in GetFolderContent: {0}", e.Message);
  201. }
  202. return inventory;
  203. }
  204. public virtual InventoryCollection[] GetMultipleFoldersContent(UUID principalID, UUID[] folderIDs)
  205. {
  206. InventoryCollection[] inventoryArr = new InventoryCollection[folderIDs.Length];
  207. // m_log.DebugFormat("[XXX]: In GetMultipleFoldersContent {0}", String.Join(",", folderIDs));
  208. try
  209. {
  210. Dictionary<string, object> resultSet = MakeRequest(
  211. $"METHOD=GETMULTIPLEFOLDERSCONTENT&PRINCIPAL={principalID}&FOLDERS={string.Join(',', folderIDs)}&COUNT={folderIDs.Length}");
  212. if (!CheckReturn(resultSet))
  213. return null;
  214. int i = 0;
  215. foreach (UUID u in folderIDs.AsSpan())
  216. {
  217. if(resultSet.TryGetValue($"F_{u}", out object oret) && oret is Dictionary<string, object> ret)
  218. {
  219. UUID inventoryFolderID;
  220. if (ret.TryGetValue("FID", out object retFID))
  221. {
  222. if (!UUID.TryParse((string)retFID, out inventoryFolderID))
  223. {
  224. m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: Could not parse folder id {0}", retFID.ToString());
  225. inventoryArr[i] = null;
  226. continue;
  227. }
  228. }
  229. else
  230. {
  231. inventoryArr[i] = null;
  232. m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: FID key not present in response");
  233. continue;
  234. }
  235. if (!ret.TryGetValue("OWNER", out object retOwner) ||
  236. !UUID.TryParse((string)retOwner, out UUID inventoryOwnerID))
  237. {
  238. inventoryArr[i] = null;
  239. m_log.Warn($"[XINVENTORY SERVICES CONNECTOR]: Could not parse folder {retFID} owner id");
  240. continue;
  241. }
  242. InventoryCollection inventory = new()
  243. {
  244. FolderID = inventoryFolderID,
  245. OwnerID = inventoryOwnerID,
  246. Folders = new List<InventoryFolderBase>(),
  247. Items = new List<InventoryItemBase>()
  248. };
  249. if (!ret.TryGetValue("VERSION", out object retVer) ||
  250. !Int32.TryParse((string)retVer, out inventory.Version))
  251. inventory.Version = -1;
  252. //m_log.DebugFormat("[XXX]: Received {0} ({1}) {2} {3}", inventory.FolderID, fid, inventory.Version, inventory.OwnerID);
  253. if (ret.TryGetValue("FOLDERS", out object ofolders) && ofolders is Dictionary<string, object> folders)
  254. {
  255. foreach (object o in folders.Values) // getting the values directly, we don't care about the keys folder_i
  256. {
  257. inventory.Folders.Add(BuildFolder((Dictionary<string, object>)o));
  258. }
  259. }
  260. if (ret.TryGetValue("ITEMS", out object oitems) && oitems is Dictionary<string, object> items)
  261. {
  262. foreach (object o in items.Values) // getting the values directly, we don't care about the keys item_i
  263. {
  264. inventory.Items.Add(BuildItem((Dictionary<string, object>)o));
  265. }
  266. }
  267. inventoryArr[i] = inventory;
  268. }
  269. else
  270. {
  271. inventoryArr[i] = null;
  272. //m_log.Warn($"[XINVENTORY SERVICES CONNECTOR]: Folder {folderIDs[i]} not on reply");,
  273. }
  274. i++;
  275. }
  276. }
  277. catch (Exception e)
  278. {
  279. m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: Exception in GetMultipleFoldersContent: {0}", e.Message);
  280. }
  281. return inventoryArr;
  282. }
  283. public List<InventoryItemBase> GetFolderItems(UUID principalID, UUID folderID)
  284. {
  285. Dictionary<string,object> ret = MakeRequest(
  286. $"METHOD=GETFOLDERITEMS&PRINCIPAL={principalID}&FOLDER={folderID}");
  287. if (!CheckReturn(ret))
  288. return null;
  289. Dictionary<string, object> items = (Dictionary<string, object>)ret["ITEMS"];
  290. List<InventoryItemBase> fitems = new(items.Count);
  291. foreach (object o in items.Values) // getting the values directly, we don't care about the keys item_i
  292. fitems.Add(BuildItem((Dictionary<string, object>)o));
  293. return fitems;
  294. }
  295. public bool AddFolder(InventoryFolderBase folder)
  296. {
  297. Dictionary<string,object> ret = MakeRequest(
  298. new Dictionary<string,object> {
  299. { "METHOD", "ADDFOLDER"},
  300. { "ParentID", folder.ParentID.ToString() },
  301. { "Type", folder.Type.ToString() },
  302. { "Version", folder.Version.ToString() },
  303. { "Name", folder.Name.ToString() },
  304. { "Owner", folder.Owner.ToString() },
  305. { "ID", folder.ID.ToString() }
  306. });
  307. return CheckReturn(ret);
  308. }
  309. public bool UpdateFolder(InventoryFolderBase folder)
  310. {
  311. Dictionary<string,object> ret = MakeRequest(
  312. $"METHOD=UPDATEFOLDER&ParentID={folder.ParentID}&Type={folder.Type}&Version={folder.Version}&Name={folder.Name}&Owner={folder.Owner}&ID={folder.ID}");
  313. return CheckReturn(ret);
  314. }
  315. public bool MoveFolder(InventoryFolderBase folder)
  316. {
  317. Dictionary<string,object> ret = MakeRequest(
  318. $"METHOD=MOVEFOLDER&ParentID={folder.ParentID}&ID={folder.ID}&PRINCIPAL={folder.Owner}");
  319. return CheckReturn(ret);
  320. }
  321. public bool DeleteFolders(UUID principalID, List<UUID> folderIDs)
  322. {
  323. List<string> slist = new();
  324. foreach (UUID f in folderIDs)
  325. slist.Add(f.ToString());
  326. Dictionary<string,object> ret = MakeRequest(
  327. new Dictionary<string,object> {
  328. { "METHOD", "DELETEFOLDERS"},
  329. { "PRINCIPAL", principalID.ToString() },
  330. { "FOLDERS", slist }
  331. });
  332. return CheckReturn(ret);
  333. }
  334. public bool PurgeFolder(InventoryFolderBase folder)
  335. {
  336. Dictionary<string,object> ret = MakeRequest(
  337. $"METHOD=PURGEFOLDER&ID={folder.ID}");
  338. return CheckReturn(ret);
  339. }
  340. public bool AddItem(InventoryItemBase item)
  341. {
  342. item.Description ??= string.Empty;
  343. item.CreatorData ??= string.Empty;
  344. item.CreatorId ??= string.Empty;
  345. Dictionary<string, object> ret = MakeRequest(
  346. new Dictionary<string,object> {
  347. { "METHOD", "ADDITEM"},
  348. { "AssetID", item.AssetID.ToString() },
  349. { "AssetType", item.AssetType.ToString() },
  350. { "Name", item.Name.ToString() },
  351. { "Owner", item.Owner.ToString() },
  352. { "ID", item.ID.ToString() },
  353. { "InvType", item.InvType.ToString() },
  354. { "Folder", item.Folder.ToString() },
  355. { "CreatorId", item.CreatorId.ToString() },
  356. { "CreatorData", item.CreatorData.ToString() },
  357. { "Description", item.Description.ToString() },
  358. { "NextPermissions", item.NextPermissions.ToString() },
  359. { "CurrentPermissions", item.CurrentPermissions.ToString() },
  360. { "BasePermissions", item.BasePermissions.ToString() },
  361. { "EveryOnePermissions", item.EveryOnePermissions.ToString() },
  362. { "GroupPermissions", item.GroupPermissions.ToString() },
  363. { "GroupID", item.GroupID.ToString() },
  364. { "GroupOwned", item.GroupOwned.ToString() },
  365. { "SalePrice", item.SalePrice.ToString() },
  366. { "SaleType", item.SaleType.ToString() },
  367. { "Flags", item.Flags.ToString() },
  368. { "CreationDate", item.CreationDate.ToString() }
  369. });
  370. return CheckReturn(ret);
  371. }
  372. public bool UpdateItem(InventoryItemBase item)
  373. {
  374. item.CreatorData ??= string.Empty;
  375. Dictionary<string,object> ret = MakeRequest(
  376. new Dictionary<string,object> {
  377. { "METHOD", "UPDATEITEM"},
  378. { "AssetID", item.AssetID.ToString() },
  379. { "AssetType", item.AssetType.ToString() },
  380. { "Name", item.Name },
  381. { "Owner", item.Owner.ToString() },
  382. { "ID", item.ID.ToString() },
  383. { "InvType", item.InvType.ToString() },
  384. { "Folder", item.Folder.ToString() },
  385. { "CreatorId", item.CreatorId },
  386. { "CreatorData", item.CreatorData },
  387. { "Description", item.Description },
  388. { "NextPermissions", item.NextPermissions.ToString() },
  389. { "CurrentPermissions", item.CurrentPermissions.ToString() },
  390. { "BasePermissions", item.BasePermissions.ToString() },
  391. { "EveryOnePermissions", item.EveryOnePermissions.ToString() },
  392. { "GroupPermissions", item.GroupPermissions.ToString() },
  393. { "GroupID", item.GroupID.ToString() },
  394. { "GroupOwned", item.GroupOwned.ToString() },
  395. { "SalePrice", item.SalePrice.ToString() },
  396. { "SaleType", item.SaleType.ToString() },
  397. { "Flags", item.Flags.ToString() },
  398. { "CreationDate", item.CreationDate.ToString() }
  399. });
  400. bool result = CheckReturn(ret);
  401. if (result)
  402. {
  403. m_ItemCache.AddOrUpdate(item.ID, item, CACHE_EXPIRATION_SECONDS);
  404. }
  405. return result;
  406. }
  407. public bool MoveItems(UUID principalID, List<InventoryItemBase> items)
  408. {
  409. List<string> idlist = new();
  410. List<string> destlist = new();
  411. foreach (InventoryItemBase item in items)
  412. {
  413. idlist.Add(item.ID.ToString());
  414. m_ItemCache.Remove(item.ID);
  415. destlist.Add(item.Folder.ToString());
  416. }
  417. Dictionary<string,object> ret = MakeRequest(
  418. new Dictionary<string,object> {
  419. { "METHOD", "MOVEITEMS"},
  420. { "PRINCIPAL", principalID.ToString() },
  421. { "IDLIST", idlist },
  422. { "DESTLIST", destlist }
  423. });
  424. return CheckReturn(ret);
  425. }
  426. public bool DeleteItems(UUID principalID, List<UUID> itemIDs)
  427. {
  428. List<string> slist = new();
  429. foreach (UUID f in itemIDs)
  430. {
  431. slist.Add(f.ToString());
  432. m_ItemCache.Remove(f);
  433. }
  434. Dictionary<string,object> ret = MakeRequest(
  435. new Dictionary<string,object> {
  436. { "METHOD", "DELETEITEMS"},
  437. { "PRINCIPAL", principalID.ToString() },
  438. { "ITEMS", slist }
  439. });
  440. return CheckReturn(ret);
  441. }
  442. public InventoryItemBase GetItem(UUID principalID, UUID itemID)
  443. {
  444. if (m_ItemCache.TryGetValue(itemID, out InventoryItemBase retrieved))
  445. return retrieved;
  446. try
  447. {
  448. Dictionary<string, object> ret = MakeRequest($"METHOD=GETITEM&ID={itemID}&PRINCIPAL={principalID}");
  449. if (!CheckReturn(ret))
  450. return null;
  451. retrieved = BuildItem((Dictionary<string, object>)ret["item"]);
  452. }
  453. catch (Exception e)
  454. {
  455. m_log.Error("[XINVENTORY SERVICES CONNECTOR]: Exception in GetItem: " + e.Message);
  456. }
  457. m_ItemCache.AddOrUpdate(itemID, retrieved, CACHE_EXPIRATION_SECONDS);
  458. return retrieved;
  459. }
  460. public virtual InventoryItemBase[] GetMultipleItems(UUID principalID, UUID[] itemIDs)
  461. {
  462. //m_log.DebugFormat("[XXX]: In GetMultipleItems {0}", String.Join(",", itemIDs));
  463. InventoryItemBase[] itemArr = new InventoryItemBase[itemIDs.Length];
  464. // Try to get them from the cache
  465. InventoryItemBase item;
  466. int i = 0;
  467. int pending = 0;
  468. StringBuilder sb = new(4096);
  469. sb.Append($"METHOD=GETMULTIPLEITEMS&PRINCIPAL={principalID}&ITEMS=");
  470. foreach (UUID id in itemIDs.AsSpan())
  471. {
  472. if (m_ItemCache.TryGetValue(id, out item))
  473. itemArr[i++] = item;
  474. else
  475. {
  476. sb.Append(id.ToString());
  477. sb.Append(',');
  478. pending++;
  479. }
  480. }
  481. if(pending == 0)
  482. {
  483. return itemArr;
  484. }
  485. sb.Remove(sb.Length - 1, 1);
  486. sb.Append($"&COUNT={pending}");
  487. try
  488. {
  489. Dictionary<string, object> resultSet = MakeRequest(sb.ToString());
  490. if (!CheckReturn(resultSet))
  491. {
  492. return i == 0 ? null : itemArr;
  493. }
  494. // carry over index i where we left above
  495. foreach (KeyValuePair<string, object> kvp in resultSet)
  496. {
  497. if (kvp.Key.StartsWith("item_"))
  498. {
  499. if (kvp.Value is Dictionary<string, object> dic)
  500. {
  501. item = BuildItem(dic);
  502. m_ItemCache.AddOrUpdate(item.ID, item, CACHE_EXPIRATION_SECONDS);
  503. itemArr[i++] = item;
  504. }
  505. else
  506. itemArr[i++] = null;
  507. }
  508. }
  509. }
  510. catch (Exception e)
  511. {
  512. m_log.WarnFormat("[XINVENTORY SERVICES CONNECTOR]: Exception in GetMultipleItems: {0}", e.Message);
  513. }
  514. return itemArr;
  515. }
  516. public InventoryFolderBase GetFolder(UUID principalID, UUID folderID)
  517. {
  518. try
  519. {
  520. Dictionary<string, object> ret = MakeRequest(
  521. $"METHOD=GETFOLDER&ID={folderID}&PRINCIPAL={principalID}");
  522. if (!CheckReturn(ret))
  523. return null;
  524. return BuildFolder((Dictionary<string, object>)ret["folder"]);
  525. }
  526. catch (Exception e)
  527. {
  528. m_log.Error("[XINVENTORY SERVICES CONNECTOR]: Exception in GetFolder: " + e.Message);
  529. }
  530. return null;
  531. }
  532. public List<InventoryItemBase> GetActiveGestures(UUID principalID)
  533. {
  534. Dictionary<string,object> ret = MakeRequest(
  535. $"METHOD=GETACTIVEGESTURES&PRINCIPAL={principalID}");
  536. if (!CheckReturn(ret))
  537. return null;
  538. if (ret["ITEMS"] is not Dictionary<string,object> itemsDict)
  539. return null;
  540. List<InventoryItemBase> items = new(itemsDict.Count);
  541. foreach (object o in itemsDict.Values)
  542. items.Add(BuildItem((Dictionary<string, object>)o));
  543. return items;
  544. }
  545. public int GetAssetPermissions(UUID principalID, UUID assetID)
  546. {
  547. Dictionary<string,object> ret = MakeRequest(
  548. $"METHOD=GETASSETPERMISSIONS&PRINCIPAL={principalID}&ASSET={assetID}");
  549. // We cannot use CheckReturn() here because valid values for RESULT are "false" (in the case of request failure) or an int
  550. if (ret is null)
  551. return 0;
  552. if (ret.TryGetValue("RESULT", out object retRes))
  553. {
  554. if (retRes is string res)
  555. {
  556. if (int.TryParse (res, out int intResult))
  557. return intResult;
  558. }
  559. }
  560. return 0;
  561. }
  562. public bool HasInventoryForUser(UUID principalID)
  563. {
  564. return false;
  565. }
  566. // Helpers
  567. //
  568. private Dictionary<string, object> MakeRequest(Dictionary<string, object> sendData)
  569. {
  570. RequestsMade++;
  571. Dictionary<string, object> replyData = MakePostDicRequest(ServerUtils.BuildQueryString(sendData));
  572. return replyData;
  573. }
  574. private Dictionary<string, object> MakeRequest(string query)
  575. {
  576. RequestsMade++;
  577. Dictionary<string, object> replyData = MakePostDicRequest(query);
  578. return replyData;
  579. }
  580. private static InventoryFolderBase BuildFolder(Dictionary<string,object> data)
  581. {
  582. try
  583. {
  584. InventoryFolderBase folder = new()
  585. {
  586. ParentID = new UUID((string)data["ParentID"]),
  587. Type = short.Parse((string)data["Type"]),
  588. Version = ushort.Parse((string)data["Version"]),
  589. Name = (string)data["Name"],
  590. Owner = new UUID((string)data["Owner"]),
  591. ID = new UUID((string)data["ID"])
  592. };
  593. return folder;
  594. }
  595. catch (Exception e)
  596. {
  597. m_log.Error($"[XINVENTORY SERVICES CONNECTOR]: Exception building folder: {e.Message}");
  598. }
  599. return new InventoryFolderBase();
  600. }
  601. private static InventoryItemBase BuildItem(Dictionary<string,object> data)
  602. {
  603. try
  604. {
  605. InventoryItemBase item = new()
  606. {
  607. AssetID = new UUID((string)data["AssetID"]),
  608. AssetType = int.Parse((string)data["AssetType"]),
  609. Name = (string)data["Name"],
  610. Owner = new UUID((string)data["Owner"]),
  611. ID = new UUID((string)data["ID"]),
  612. InvType = int.Parse((string)data["InvType"]),
  613. Folder = new UUID((string)data["Folder"]),
  614. CreatorId = (string)data["CreatorId"],
  615. NextPermissions = uint.Parse((string)data["NextPermissions"]),
  616. CurrentPermissions = uint.Parse((string)data["CurrentPermissions"]),
  617. BasePermissions = uint.Parse((string)data["BasePermissions"]),
  618. EveryOnePermissions = uint.Parse((string)data["EveryOnePermissions"]),
  619. GroupPermissions = uint.Parse((string)data["GroupPermissions"]),
  620. GroupID = new UUID((string)data["GroupID"]),
  621. GroupOwned = bool.Parse((string)data["GroupOwned"]),
  622. SalePrice = int.Parse((string)data["SalePrice"]),
  623. SaleType = byte.Parse((string)data["SaleType"]),
  624. Flags = uint.Parse((string)data["Flags"]),
  625. CreationDate = int.Parse((string)data["CreationDate"]),
  626. Description = (string)data["Description"]
  627. };
  628. if (data.TryGetValue("CreatorData", out object oCreatorData))
  629. item.CreatorData = (string)oCreatorData;
  630. return item;
  631. }
  632. catch (Exception e)
  633. {
  634. m_log.Error($"[XINVENTORY CONNECTOR]: Exception building item: {e.Message}");
  635. }
  636. return new InventoryItemBase();
  637. }
  638. public Dictionary<string, object> MakePostDicRequest(string obj)
  639. {
  640. if (WebUtil.DebugLevel >= 3)
  641. m_log.Debug($"[XInventory]: HTTP OUT SynchronousRestForms POST to {m_InventoryURL}");
  642. if (string.IsNullOrEmpty(obj))
  643. {
  644. m_log.Warn($"[XInventory]: empty post data");
  645. return new Dictionary<string, object>();
  646. }
  647. Dictionary<string, object> respDic = null;
  648. int ticks = Util.EnvironmentTickCount();
  649. int sendlen = 0;
  650. int rcvlen = 0;
  651. HttpResponseMessage responseMessage = null;
  652. HttpRequestMessage request = null;
  653. HttpClient client = null;
  654. try
  655. {
  656. client = WebUtil.GetNewGlobalHttpClient(m_requestTimeout);
  657. request = new(HttpMethod.Post, m_InventoryURL);
  658. m_Auth?.AddAuthorization(request.Headers);
  659. //if (keepalive)
  660. {
  661. request.Headers.TryAddWithoutValidation("Keep-Alive", "timeout=30, max=10");
  662. request.Headers.TryAddWithoutValidation("Connection", "Keep-Alive");
  663. request.Headers.ConnectionClose = false;
  664. }
  665. //else
  666. // request.Headers.TryAddWithoutValidation("Connection", "close");
  667. request.Headers.ExpectContinue = false;
  668. request.Headers.TransferEncodingChunked = false;
  669. byte[] data = Util.UTF8NBGetbytes(obj);
  670. sendlen = data.Length;
  671. request.Content = new ByteArrayContent(data);
  672. request.Content.Headers.TryAddWithoutValidation("Content-Type", "application/x-www-form-urlencoded");
  673. request.Content.Headers.TryAddWithoutValidation("Content-Length", sendlen.ToString());
  674. responseMessage = client.Send(request, HttpCompletionOption.ResponseHeadersRead);
  675. responseMessage.EnsureSuccessStatusCode();
  676. if ((responseMessage.Content.Headers.ContentLength is long contentLength) && contentLength != 0)
  677. {
  678. rcvlen = (int)contentLength;
  679. respDic = ServerUtils.ParseXmlResponse(responseMessage.Content.ReadAsStream());
  680. }
  681. }
  682. catch (Exception e)
  683. {
  684. m_log.Info($"[XInventory]: Error receiving response from {m_InventoryURL}: {e.Message}");
  685. throw;
  686. }
  687. finally
  688. {
  689. request?.Dispose();
  690. responseMessage?.Dispose();
  691. client?.Dispose();
  692. }
  693. ticks = Util.EnvironmentTickCountSubtract(ticks);
  694. if (ticks > WebUtil.LongCallTime)
  695. {
  696. m_log.Info($"[XInventory]: POST {m_InventoryURL} took {ticks}ms {sendlen}/{rcvlen}bytes");
  697. }
  698. return respDic ?? new Dictionary<string, object>();
  699. }
  700. }
  701. }