SimianAssetServiceConnector.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  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 System;
  28. using System.Collections.Generic;
  29. using System.IO;
  30. using System.Net;
  31. using System.Reflection;
  32. using log4net;
  33. using Mono.Addins;
  34. using Nini.Config;
  35. using OpenSim.Framework;
  36. using OpenSim.Region.Framework.Interfaces;
  37. using OpenSim.Region.Framework.Scenes;
  38. using OpenSim.Services.Interfaces;
  39. using OpenMetaverse;
  40. using OpenMetaverse.StructuredData;
  41. namespace OpenSim.Services.Connectors.SimianGrid
  42. {
  43. /// <summary>
  44. /// Connects to the SimianGrid asset service
  45. /// </summary>
  46. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")]
  47. public class SimianAssetServiceConnector : IAssetService, ISharedRegionModule
  48. {
  49. private static readonly ILog m_log =
  50. LogManager.GetLogger(
  51. MethodBase.GetCurrentMethod().DeclaringType);
  52. private static string ZeroID = UUID.Zero.ToString();
  53. private string m_serverUrl = String.Empty;
  54. private IImprovedAssetCache m_cache;
  55. private bool m_Enabled = false;
  56. #region ISharedRegionModule
  57. public Type ReplaceableInterface { get { return null; } }
  58. public void RegionLoaded(Scene scene)
  59. {
  60. if (m_cache == null)
  61. {
  62. IImprovedAssetCache cache = scene.RequestModuleInterface<IImprovedAssetCache>();
  63. if (cache is ISharedRegionModule)
  64. m_cache = cache;
  65. }
  66. }
  67. public void PostInitialise() { }
  68. public void Close() { }
  69. public SimianAssetServiceConnector() { }
  70. public string Name { get { return "SimianAssetServiceConnector"; } }
  71. public void AddRegion(Scene scene) { if (m_Enabled) { scene.RegisterModuleInterface<IAssetService>(this); } }
  72. public void RemoveRegion(Scene scene) { if (m_Enabled) { scene.UnregisterModuleInterface<IAssetService>(this); } }
  73. #endregion ISharedRegionModule
  74. public SimianAssetServiceConnector(IConfigSource source)
  75. {
  76. CommonInit(source);
  77. }
  78. public SimianAssetServiceConnector(string url)
  79. {
  80. if (!url.EndsWith("/") && !url.EndsWith("="))
  81. url = url + '/';
  82. m_serverUrl = url;
  83. }
  84. public void Initialise(IConfigSource source)
  85. {
  86. IConfig moduleConfig = source.Configs["Modules"];
  87. if (moduleConfig != null)
  88. {
  89. string name = moduleConfig.GetString("AssetServices", "");
  90. if (name == Name)
  91. CommonInit(source);
  92. }
  93. }
  94. private void CommonInit(IConfigSource source)
  95. {
  96. IConfig gridConfig = source.Configs["AssetService"];
  97. if (gridConfig != null)
  98. {
  99. string serviceUrl = gridConfig.GetString("AssetServerURI");
  100. if (!String.IsNullOrEmpty(serviceUrl))
  101. {
  102. if (!serviceUrl.EndsWith("/") && !serviceUrl.EndsWith("="))
  103. serviceUrl = serviceUrl + '/';
  104. m_serverUrl = serviceUrl;
  105. }
  106. }
  107. if (String.IsNullOrEmpty(m_serverUrl))
  108. m_log.Info("[SIMIAN ASSET CONNECTOR]: No AssetServerURI specified, disabling connector");
  109. else
  110. m_Enabled = true;
  111. }
  112. #region IAssetService
  113. public AssetBase Get(string id)
  114. {
  115. if (String.IsNullOrEmpty(m_serverUrl))
  116. {
  117. m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
  118. throw new InvalidOperationException();
  119. }
  120. // Cache fetch
  121. if (m_cache != null)
  122. {
  123. AssetBase asset = m_cache.Get(id);
  124. if (asset != null)
  125. return asset;
  126. }
  127. return GetRemote(id);
  128. }
  129. public AssetBase GetCached(string id)
  130. {
  131. if (m_cache != null)
  132. return m_cache.Get(id);
  133. return null;
  134. }
  135. /// <summary>
  136. /// Get an asset's metadata
  137. /// </summary>
  138. /// <param name="id"></param>
  139. /// <returns></returns>
  140. public AssetMetadata GetMetadata(string id)
  141. {
  142. if (String.IsNullOrEmpty(m_serverUrl))
  143. {
  144. m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
  145. throw new InvalidOperationException();
  146. }
  147. AssetMetadata metadata = null;
  148. // Cache fetch
  149. if (m_cache != null)
  150. {
  151. AssetBase asset = m_cache.Get(id);
  152. if (asset != null)
  153. return asset.Metadata;
  154. }
  155. Uri url;
  156. // Determine if id is an absolute URL or a grid-relative UUID
  157. if (!Uri.TryCreate(id, UriKind.Absolute, out url))
  158. url = new Uri(m_serverUrl + id);
  159. try
  160. {
  161. HttpWebRequest request = UntrustedHttpWebRequest.Create(url);
  162. request.Method = "HEAD";
  163. using (WebResponse response = request.GetResponse())
  164. {
  165. using (Stream responseStream = response.GetResponseStream())
  166. {
  167. // Create the metadata object
  168. metadata = new AssetMetadata();
  169. metadata.ContentType = response.ContentType;
  170. metadata.ID = id;
  171. UUID uuid;
  172. if (UUID.TryParse(id, out uuid))
  173. metadata.FullID = uuid;
  174. string lastModifiedStr = response.Headers.Get("Last-Modified");
  175. if (!String.IsNullOrEmpty(lastModifiedStr))
  176. {
  177. DateTime lastModified;
  178. if (DateTime.TryParse(lastModifiedStr, out lastModified))
  179. metadata.CreationDate = lastModified;
  180. }
  181. }
  182. }
  183. }
  184. catch (Exception ex)
  185. {
  186. m_log.Warn("[SIMIAN ASSET CONNECTOR]: Asset HEAD from " + url + " failed: " + ex.Message);
  187. }
  188. return metadata;
  189. }
  190. public byte[] GetData(string id)
  191. {
  192. AssetBase asset = Get(id);
  193. if (asset != null)
  194. return asset.Data;
  195. return null;
  196. }
  197. /// <summary>
  198. /// Get an asset asynchronously
  199. /// </summary>
  200. /// <param name="id">The asset id</param>
  201. /// <param name="sender">Represents the requester. Passed back via the handler</param>
  202. /// <param name="handler">The handler to call back once the asset has been retrieved</param>
  203. /// <returns>True if the id was parseable, false otherwise</returns>
  204. public bool Get(string id, Object sender, AssetRetrieved handler)
  205. {
  206. if (String.IsNullOrEmpty(m_serverUrl))
  207. {
  208. m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
  209. throw new InvalidOperationException();
  210. }
  211. // Cache fetch
  212. if (m_cache != null)
  213. {
  214. AssetBase asset = m_cache.Get(id);
  215. if (asset != null)
  216. {
  217. handler(id, sender, asset);
  218. return true;
  219. }
  220. }
  221. Util.FireAndForget(
  222. delegate(object o)
  223. {
  224. AssetBase asset = GetRemote(id);
  225. handler(id, sender, asset);
  226. }
  227. );
  228. return true;
  229. }
  230. /// <summary>
  231. /// Creates a new asset
  232. /// </summary>
  233. /// Returns a random ID if none is passed into it
  234. /// <param name="asset"></param>
  235. /// <returns></returns>
  236. public string Store(AssetBase asset)
  237. {
  238. if (String.IsNullOrEmpty(m_serverUrl))
  239. {
  240. m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
  241. throw new InvalidOperationException();
  242. }
  243. bool storedInCache = false;
  244. string errorMessage = null;
  245. // AssetID handling
  246. if (String.IsNullOrEmpty(asset.ID) || asset.ID == ZeroID)
  247. {
  248. asset.FullID = UUID.Random();
  249. asset.ID = asset.FullID.ToString();
  250. }
  251. // Cache handling
  252. if (m_cache != null)
  253. {
  254. m_cache.Cache(asset);
  255. storedInCache = true;
  256. }
  257. // Local asset handling
  258. if (asset.Local)
  259. {
  260. if (!storedInCache)
  261. {
  262. m_log.Error("Cannot store local " + asset.Metadata.ContentType + " asset without an asset cache");
  263. asset.ID = null;
  264. asset.FullID = UUID.Zero;
  265. }
  266. return asset.ID;
  267. }
  268. // Distinguish public and private assets
  269. bool isPublic = true;
  270. switch ((AssetType)asset.Type)
  271. {
  272. case AssetType.CallingCard:
  273. case AssetType.Gesture:
  274. case AssetType.LSLBytecode:
  275. case AssetType.LSLText:
  276. isPublic = false;
  277. break;
  278. }
  279. // Make sure ContentType is set
  280. if (String.IsNullOrEmpty(asset.Metadata.ContentType))
  281. asset.Metadata.ContentType = SLUtil.SLAssetTypeToContentType(asset.Type);
  282. // Build the remote storage request
  283. List<MultipartForm.Element> postParameters = new List<MultipartForm.Element>()
  284. {
  285. new MultipartForm.Parameter("AssetID", asset.FullID.ToString()),
  286. new MultipartForm.Parameter("CreatorID", asset.Metadata.CreatorID),
  287. new MultipartForm.Parameter("Temporary", asset.Temporary ? "1" : "0"),
  288. new MultipartForm.Parameter("Public", isPublic ? "1" : "0"),
  289. new MultipartForm.File("Asset", asset.Name, asset.Metadata.ContentType, asset.Data)
  290. };
  291. // Make the remote storage request
  292. try
  293. {
  294. // Simian does not require the asset ID to be in the URL because it's in the post data.
  295. // By appending it to the URL also, we allow caching proxies (squid) to invalidate asset URLs
  296. HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(m_serverUrl + asset.FullID.ToString());
  297. HttpWebResponse response = MultipartForm.Post(request, postParameters);
  298. using (Stream responseStream = response.GetResponseStream())
  299. {
  300. string responseStr = null;
  301. try
  302. {
  303. responseStr = responseStream.GetStreamString();
  304. OSD responseOSD = OSDParser.Deserialize(responseStr);
  305. if (responseOSD.Type == OSDType.Map)
  306. {
  307. OSDMap responseMap = (OSDMap)responseOSD;
  308. if (responseMap["Success"].AsBoolean())
  309. return asset.ID;
  310. else
  311. errorMessage = "Upload failed: " + responseMap["Message"].AsString();
  312. }
  313. else
  314. {
  315. errorMessage = "Response format was invalid:\n" + responseStr;
  316. }
  317. }
  318. catch (Exception ex)
  319. {
  320. if (!String.IsNullOrEmpty(responseStr))
  321. errorMessage = "Failed to parse the response:\n" + responseStr;
  322. else
  323. errorMessage = "Failed to retrieve the response: " + ex.Message;
  324. }
  325. }
  326. }
  327. catch (WebException ex)
  328. {
  329. errorMessage = ex.Message;
  330. }
  331. m_log.WarnFormat("[SIMIAN ASSET CONNECTOR]: Failed to store asset \"{0}\" ({1}, {2}): {3}",
  332. asset.Name, asset.ID, asset.Metadata.ContentType, errorMessage);
  333. return null;
  334. }
  335. /// <summary>
  336. /// Update an asset's content
  337. /// </summary>
  338. /// Attachments and bare scripts need this!!
  339. /// <param name="id"> </param>
  340. /// <param name="data"></param>
  341. /// <returns></returns>
  342. public bool UpdateContent(string id, byte[] data)
  343. {
  344. AssetBase asset = Get(id);
  345. if (asset == null)
  346. {
  347. m_log.Warn("[SIMIAN ASSET CONNECTOR]: Failed to fetch asset " + id + " for updating");
  348. return false;
  349. }
  350. asset.Data = data;
  351. string result = Store(asset);
  352. return !String.IsNullOrEmpty(result);
  353. }
  354. /// <summary>
  355. /// Delete an asset
  356. /// </summary>
  357. /// <param name="id"></param>
  358. /// <returns></returns>
  359. public bool Delete(string id)
  360. {
  361. if (String.IsNullOrEmpty(m_serverUrl))
  362. {
  363. m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
  364. throw new InvalidOperationException();
  365. }
  366. //string errorMessage = String.Empty;
  367. string url = m_serverUrl + id;
  368. if (m_cache != null)
  369. m_cache.Expire(id);
  370. try
  371. {
  372. HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
  373. request.Method = "DELETE";
  374. using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
  375. {
  376. if (response.StatusCode != HttpStatusCode.NoContent)
  377. {
  378. m_log.Warn("[SIMIAN ASSET CONNECTOR]: Unexpected response when deleting asset " + url + ": " +
  379. response.StatusCode + " (" + response.StatusDescription + ")");
  380. }
  381. }
  382. return true;
  383. }
  384. catch (Exception ex)
  385. {
  386. m_log.Warn("[SIMIAN ASSET CONNECTOR]: Failed to delete asset " + id + " from the asset service: " + ex.Message);
  387. return false;
  388. }
  389. }
  390. #endregion IAssetService
  391. private AssetBase GetRemote(string id)
  392. {
  393. AssetBase asset = null;
  394. Uri url;
  395. // Determine if id is an absolute URL or a grid-relative UUID
  396. if (!Uri.TryCreate(id, UriKind.Absolute, out url))
  397. url = new Uri(m_serverUrl + id);
  398. try
  399. {
  400. HttpWebRequest request = UntrustedHttpWebRequest.Create(url);
  401. using (WebResponse response = request.GetResponse())
  402. {
  403. using (Stream responseStream = response.GetResponseStream())
  404. {
  405. string creatorID = response.Headers.GetOne("X-Asset-Creator-Id") ?? String.Empty;
  406. // Create the asset object
  407. asset = new AssetBase(id, String.Empty, SLUtil.ContentTypeToSLAssetType(response.ContentType), creatorID);
  408. UUID assetID;
  409. if (UUID.TryParse(id, out assetID))
  410. asset.FullID = assetID;
  411. // Grab the asset data from the response stream
  412. using (MemoryStream stream = new MemoryStream())
  413. {
  414. responseStream.CopyStream(stream, Int32.MaxValue);
  415. asset.Data = stream.ToArray();
  416. }
  417. }
  418. }
  419. // Cache store
  420. if (m_cache != null && asset != null)
  421. m_cache.Cache(asset);
  422. return asset;
  423. }
  424. catch (Exception ex)
  425. {
  426. m_log.Warn("[SIMIAN ASSET CONNECTOR]: Asset GET from " + url + " failed: " + ex.Message);
  427. return null;
  428. }
  429. }
  430. }
  431. }