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