diff options
Diffstat (limited to 'OpenSim/Services/Connectors/SimianGrid/SimianAssetServiceConnector.cs')
-rw-r--r-- | OpenSim/Services/Connectors/SimianGrid/SimianAssetServiceConnector.cs | 418 |
1 files changed, 418 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..17febf9 --- /dev/null +++ b/OpenSim/Services/Connectors/SimianGrid/SimianAssetServiceConnector.cs | |||
@@ -0,0 +1,418 @@ | |||
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 | AssetBase asset = null; | ||
116 | |||
117 | // Cache fetch | ||
118 | if (m_cache != null) | ||
119 | { | ||
120 | asset = m_cache.Get(id); | ||
121 | if (asset != null) | ||
122 | return asset; | ||
123 | } | ||
124 | |||
125 | Uri url; | ||
126 | |||
127 | // Determine if id is an absolute URL or a grid-relative UUID | ||
128 | if (!Uri.TryCreate(id, UriKind.Absolute, out url)) | ||
129 | url = new Uri(m_serverUrl + id); | ||
130 | |||
131 | try | ||
132 | { | ||
133 | HttpWebRequest request = UntrustedHttpWebRequest.Create(url); | ||
134 | |||
135 | using (WebResponse response = request.GetResponse()) | ||
136 | { | ||
137 | using (Stream responseStream = response.GetResponseStream()) | ||
138 | { | ||
139 | string creatorID = response.Headers.GetOne("X-Asset-Creator-Id") ?? String.Empty; | ||
140 | |||
141 | // Create the asset object | ||
142 | asset = new AssetBase(id, String.Empty, SLUtil.ContentTypeToSLAssetType(response.ContentType), creatorID); | ||
143 | |||
144 | UUID assetID; | ||
145 | if (UUID.TryParse(id, out assetID)) | ||
146 | asset.FullID = assetID; | ||
147 | |||
148 | // Grab the asset data from the response stream | ||
149 | using (MemoryStream stream = new MemoryStream()) | ||
150 | { | ||
151 | responseStream.CopyTo(stream, Int32.MaxValue); | ||
152 | asset.Data = stream.ToArray(); | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | |||
157 | // Cache store | ||
158 | if (m_cache != null && asset != null) | ||
159 | m_cache.Cache(asset); | ||
160 | |||
161 | return asset; | ||
162 | } | ||
163 | catch (Exception ex) | ||
164 | { | ||
165 | m_log.Warn("[SIMIAN ASSET CONNECTOR]: Asset GET from " + url + " failed: " + ex.Message); | ||
166 | return null; | ||
167 | } | ||
168 | } | ||
169 | |||
170 | /// <summary> | ||
171 | /// Get an asset's metadata | ||
172 | /// </summary> | ||
173 | /// <param name="id"></param> | ||
174 | /// <returns></returns> | ||
175 | public AssetMetadata GetMetadata(string id) | ||
176 | { | ||
177 | AssetMetadata metadata = null; | ||
178 | |||
179 | // Cache fetch | ||
180 | if (m_cache != null) | ||
181 | { | ||
182 | AssetBase asset = m_cache.Get(id); | ||
183 | if (asset != null) | ||
184 | return asset.Metadata; | ||
185 | } | ||
186 | |||
187 | Uri url; | ||
188 | |||
189 | // Determine if id is an absolute URL or a grid-relative UUID | ||
190 | if (!Uri.TryCreate(id, UriKind.Absolute, out url)) | ||
191 | url = new Uri(m_serverUrl + id); | ||
192 | |||
193 | try | ||
194 | { | ||
195 | HttpWebRequest request = UntrustedHttpWebRequest.Create(url); | ||
196 | request.Method = "HEAD"; | ||
197 | |||
198 | using (WebResponse response = request.GetResponse()) | ||
199 | { | ||
200 | using (Stream responseStream = response.GetResponseStream()) | ||
201 | { | ||
202 | // Create the metadata object | ||
203 | metadata = new AssetMetadata(); | ||
204 | metadata.ContentType = response.ContentType; | ||
205 | metadata.ID = id; | ||
206 | |||
207 | UUID uuid; | ||
208 | if (UUID.TryParse(id, out uuid)) | ||
209 | metadata.FullID = uuid; | ||
210 | |||
211 | string lastModifiedStr = response.Headers.Get("Last-Modified"); | ||
212 | if (!String.IsNullOrEmpty(lastModifiedStr)) | ||
213 | { | ||
214 | DateTime lastModified; | ||
215 | if (DateTime.TryParse(lastModifiedStr, out lastModified)) | ||
216 | metadata.CreationDate = lastModified; | ||
217 | } | ||
218 | } | ||
219 | } | ||
220 | } | ||
221 | catch (Exception ex) | ||
222 | { | ||
223 | m_log.Warn("[SIMIAN ASSET CONNECTOR]: Asset GET from " + url + " failed: " + ex.Message); | ||
224 | } | ||
225 | |||
226 | return metadata; | ||
227 | } | ||
228 | |||
229 | public byte[] GetData(string id) | ||
230 | { | ||
231 | AssetBase asset = Get(id); | ||
232 | |||
233 | if (asset != null) | ||
234 | return asset.Data; | ||
235 | |||
236 | return null; | ||
237 | } | ||
238 | |||
239 | /// <summary> | ||
240 | /// Get an asset asynchronously | ||
241 | /// </summary> | ||
242 | /// <param name="id">The asset id</param> | ||
243 | /// <param name="sender">Represents the requester. Passed back via the handler</param> | ||
244 | /// <param name="handler">The handler to call back once the asset has been retrieved</param> | ||
245 | /// <returns>True if the id was parseable, false otherwise</returns> | ||
246 | public bool Get(string id, Object sender, AssetRetrieved handler) | ||
247 | { | ||
248 | Util.FireAndForget( | ||
249 | delegate(object o) | ||
250 | { | ||
251 | AssetBase asset = Get(id); | ||
252 | handler(id, sender, asset); | ||
253 | } | ||
254 | ); | ||
255 | |||
256 | return true; | ||
257 | } | ||
258 | |||
259 | /// <summary> | ||
260 | /// Creates a new asset | ||
261 | /// </summary> | ||
262 | /// Returns a random ID if none is passed into it | ||
263 | /// <param name="asset"></param> | ||
264 | /// <returns></returns> | ||
265 | public string Store(AssetBase asset) | ||
266 | { | ||
267 | bool storedInCache = false; | ||
268 | string errorMessage = null; | ||
269 | |||
270 | // AssetID handling | ||
271 | if (String.IsNullOrEmpty(asset.ID) || asset.ID == ZeroID) | ||
272 | { | ||
273 | asset.FullID = UUID.Random(); | ||
274 | asset.ID = asset.FullID.ToString(); | ||
275 | } | ||
276 | |||
277 | // Cache handling | ||
278 | if (m_cache != null) | ||
279 | { | ||
280 | m_cache.Cache(asset); | ||
281 | storedInCache = true; | ||
282 | } | ||
283 | |||
284 | // Local asset handling | ||
285 | if (asset.Local) | ||
286 | { | ||
287 | if (!storedInCache) | ||
288 | { | ||
289 | m_log.Error("Cannot store local " + asset.Metadata.ContentType + " asset without an asset cache"); | ||
290 | asset.ID = null; | ||
291 | asset.FullID = UUID.Zero; | ||
292 | } | ||
293 | |||
294 | return asset.ID; | ||
295 | } | ||
296 | |||
297 | // Distinguish public and private assets | ||
298 | bool isPublic = true; | ||
299 | switch ((AssetType)asset.Type) | ||
300 | { | ||
301 | case AssetType.CallingCard: | ||
302 | case AssetType.Gesture: | ||
303 | case AssetType.LSLBytecode: | ||
304 | case AssetType.LSLText: | ||
305 | isPublic = false; | ||
306 | break; | ||
307 | } | ||
308 | |||
309 | // Make sure ContentType is set | ||
310 | if (String.IsNullOrEmpty(asset.Metadata.ContentType)) | ||
311 | asset.Metadata.ContentType = SLUtil.SLAssetTypeToContentType(asset.Type); | ||
312 | |||
313 | // Build the remote storage request | ||
314 | List<MultipartForm.Element> postParameters = new List<MultipartForm.Element>() | ||
315 | { | ||
316 | new MultipartForm.Parameter("AssetID", asset.FullID.ToString()), | ||
317 | new MultipartForm.Parameter("CreatorID", asset.Metadata.CreatorID), | ||
318 | new MultipartForm.Parameter("Temporary", asset.Temporary ? "1" : "0"), | ||
319 | new MultipartForm.Parameter("Public", isPublic ? "1" : "0"), | ||
320 | new MultipartForm.File("Asset", asset.Name, asset.Metadata.ContentType, asset.Data) | ||
321 | }; | ||
322 | |||
323 | // Make the remote storage request | ||
324 | try | ||
325 | { | ||
326 | HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(m_serverUrl); | ||
327 | |||
328 | HttpWebResponse response = MultipartForm.Post(request, postParameters); | ||
329 | using (Stream responseStream = response.GetResponseStream()) | ||
330 | { | ||
331 | try | ||
332 | { | ||
333 | string responseStr = responseStream.GetStreamString(); | ||
334 | OSD responseOSD = OSDParser.Deserialize(responseStr); | ||
335 | if (responseOSD.Type == OSDType.Map) | ||
336 | { | ||
337 | OSDMap responseMap = (OSDMap)responseOSD; | ||
338 | if (responseMap["Success"].AsBoolean()) | ||
339 | return asset.ID; | ||
340 | else | ||
341 | errorMessage = "Upload failed: " + responseMap["Message"].AsString(); | ||
342 | } | ||
343 | else | ||
344 | { | ||
345 | errorMessage = "Response format was invalid."; | ||
346 | } | ||
347 | } | ||
348 | catch | ||
349 | { | ||
350 | errorMessage = "Failed to parse the response."; | ||
351 | } | ||
352 | } | ||
353 | } | ||
354 | catch (WebException ex) | ||
355 | { | ||
356 | errorMessage = ex.Message; | ||
357 | } | ||
358 | |||
359 | m_log.WarnFormat("[SIMIAN ASSET CONNECTOR]: Failed to store asset \"{0}\" ({1}, {2}): {3}", | ||
360 | asset.Name, asset.ID, asset.Metadata.ContentType, errorMessage); | ||
361 | return null; | ||
362 | } | ||
363 | |||
364 | /// <summary> | ||
365 | /// Update an asset's content | ||
366 | /// </summary> | ||
367 | /// Attachments and bare scripts need this!! | ||
368 | /// <param name="id"> </param> | ||
369 | /// <param name="data"></param> | ||
370 | /// <returns></returns> | ||
371 | public bool UpdateContent(string id, byte[] data) | ||
372 | { | ||
373 | AssetBase asset = Get(id); | ||
374 | |||
375 | if (asset == null) | ||
376 | { | ||
377 | m_log.Warn("[SIMIAN ASSET CONNECTOR]: Failed to fetch asset " + id + " for updating"); | ||
378 | return false; | ||
379 | } | ||
380 | |||
381 | asset.Data = data; | ||
382 | |||
383 | string result = Store(asset); | ||
384 | return !String.IsNullOrEmpty(result); | ||
385 | } | ||
386 | |||
387 | /// <summary> | ||
388 | /// Delete an asset | ||
389 | /// </summary> | ||
390 | /// <param name="id"></param> | ||
391 | /// <returns></returns> | ||
392 | public bool Delete(string id) | ||
393 | { | ||
394 | if (m_cache != null) | ||
395 | m_cache.Expire(id); | ||
396 | |||
397 | string url = m_serverUrl + id; | ||
398 | |||
399 | OSDMap response = WebUtil.ServiceRequest(url, "DELETE"); | ||
400 | if (response["Success"].AsBoolean()) | ||
401 | return true; | ||
402 | else | ||
403 | m_log.Warn("[SIMIAN ASSET CONNECTOR]: Failed to delete asset " + id + " from the asset service"); | ||
404 | |||
405 | return false; | ||
406 | } | ||
407 | |||
408 | #endregion IAssetService | ||
409 | |||
410 | public AssetBase GetCached(string id) | ||
411 | { | ||
412 | if (m_cache != null) | ||
413 | return m_cache.Get(id); | ||
414 | |||
415 | return null; | ||
416 | } | ||
417 | } | ||
418 | } | ||