diff options
Diffstat (limited to '')
-rw-r--r-- | OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs | 2343 |
1 files changed, 0 insertions, 2343 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs deleted file mode 100644 index 536f167..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs +++ /dev/null | |||
@@ -1,2343 +0,0 @@ | |||
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.Drawing; | ||
31 | using System.Globalization; | ||
32 | using System.IO; | ||
33 | using System.Threading; | ||
34 | using System.Timers; | ||
35 | using System.Xml; | ||
36 | using OpenMetaverse; | ||
37 | using OpenMetaverse.Imaging; | ||
38 | using OpenSim.Framework; | ||
39 | |||
40 | using OpenSim.Framework.Servers; | ||
41 | using OpenSim.Framework.Servers.HttpServer; | ||
42 | using Timer=System.Timers.Timer; | ||
43 | |||
44 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
45 | { | ||
46 | public class RestInventoryServices : IRest | ||
47 | { | ||
48 | // private static readonly int PARM_USERID = 0; | ||
49 | // private static readonly int PARM_PATH = 1; | ||
50 | |||
51 | // private bool enabled = false; | ||
52 | private string qPrefix = "inventory"; | ||
53 | |||
54 | // private static readonly string PRIVATE_ROOT_NAME = "My Inventory"; | ||
55 | |||
56 | /// <summary> | ||
57 | /// The constructor makes sure that the service prefix is absolute | ||
58 | /// and the registers the service handler and the allocator. | ||
59 | /// </summary> | ||
60 | |||
61 | public RestInventoryServices() | ||
62 | { | ||
63 | Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); | ||
64 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | ||
65 | |||
66 | // If a relative path was specified for the handler's domain, | ||
67 | // add the standard prefix to make it absolute, e.g. /admin | ||
68 | |||
69 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | ||
70 | { | ||
71 | Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); | ||
72 | qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); | ||
73 | Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); | ||
74 | } | ||
75 | |||
76 | // Register interface using the absolute URI. | ||
77 | |||
78 | Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate); | ||
79 | |||
80 | // Activate if everything went OK | ||
81 | |||
82 | // enabled = true; | ||
83 | |||
84 | Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId); | ||
85 | } | ||
86 | |||
87 | /// <summary> | ||
88 | /// Post-construction, pre-enabled initialization opportunity | ||
89 | /// Not currently exploited. | ||
90 | /// </summary> | ||
91 | |||
92 | public void Initialize() | ||
93 | { | ||
94 | } | ||
95 | |||
96 | /// <summary> | ||
97 | /// Called by the plug-in to halt service processing. Local processing is | ||
98 | /// disabled. | ||
99 | /// </summary> | ||
100 | |||
101 | public void Close() | ||
102 | { | ||
103 | // enabled = false; | ||
104 | Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId); | ||
105 | } | ||
106 | |||
107 | /// <summary> | ||
108 | /// This property is declared locally because it is used a lot and | ||
109 | /// brevity is nice. | ||
110 | /// </summary> | ||
111 | internal string MsgId | ||
112 | { | ||
113 | get { return Rest.MsgId; } | ||
114 | } | ||
115 | |||
116 | #region Interface | ||
117 | |||
118 | /// <summary> | ||
119 | /// The plugin (RestHandler) calls this method to allocate the request | ||
120 | /// state carrier for a new request. It is destroyed when the request | ||
121 | /// completes. All request-instance specific state is kept here. This | ||
122 | /// is registered when this service provider is registered. | ||
123 | /// </summary> | ||
124 | /// <param name=request>Inbound HTTP request information</param> | ||
125 | /// <param name=response>Outbound HTTP request information</param> | ||
126 | /// <param name=qPrefix>REST service domain prefix</param> | ||
127 | /// <returns>A RequestData instance suitable for this service</returns> | ||
128 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
129 | { | ||
130 | return (RequestData) new InventoryRequestData(request, response, prefix); | ||
131 | } | ||
132 | |||
133 | /// <summary> | ||
134 | /// This method is registered with the handler when this service provider | ||
135 | /// is initialized. It is called whenever the plug-in identifies this service | ||
136 | /// provider as the best match for a given request. | ||
137 | /// It handles all aspects of inventory REST processing, i.e. /admin/inventory | ||
138 | /// </summary> | ||
139 | /// <param name=hdata>A consolidated HTTP request work area</param> | ||
140 | private void DoInventory(RequestData hdata) | ||
141 | { | ||
142 | // InventoryRequestData rdata = (InventoryRequestData) hdata; | ||
143 | |||
144 | Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); | ||
145 | |||
146 | // !!! REFACTORING PROBLEM | ||
147 | |||
148 | //// If we're disabled, do nothing. | ||
149 | |||
150 | //if (!enabled) | ||
151 | //{ | ||
152 | // return; | ||
153 | //} | ||
154 | |||
155 | //// Now that we know this is a serious attempt to | ||
156 | //// access inventory data, we should find out who | ||
157 | //// is asking, and make sure they are authorized | ||
158 | //// to do so. We need to validate the caller's | ||
159 | //// identity before revealing anything about the | ||
160 | //// status quo. Authenticate throws an exception | ||
161 | //// via Fail if no identity information is present. | ||
162 | //// | ||
163 | //// With the present HTTP server we can't use the | ||
164 | //// builtin authentication mechanisms because they | ||
165 | //// would be enforced for all in-bound requests. | ||
166 | //// Instead we look at the headers ourselves and | ||
167 | //// handle authentication directly. | ||
168 | |||
169 | //try | ||
170 | //{ | ||
171 | // if (!rdata.IsAuthenticated) | ||
172 | // { | ||
173 | // rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); | ||
174 | // } | ||
175 | //} | ||
176 | //catch (RestException e) | ||
177 | //{ | ||
178 | // if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) | ||
179 | // { | ||
180 | // Rest.Log.WarnFormat("{0} User not authenticated", MsgId); | ||
181 | // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
182 | // } | ||
183 | // else | ||
184 | // { | ||
185 | // Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); | ||
186 | // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
187 | // } | ||
188 | // throw (e); | ||
189 | //} | ||
190 | |||
191 | //Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); | ||
192 | |||
193 | //// We can only get here if we are authorized | ||
194 | //// | ||
195 | //// The requestor may have specified an UUID or | ||
196 | //// a conjoined FirstName LastName string. We'll | ||
197 | //// try both. If we fail with the first, UUID, | ||
198 | //// attempt, we try the other. As an example, the | ||
199 | //// URI for a valid inventory request might be: | ||
200 | //// | ||
201 | //// http://<host>:<port>/admin/inventory/Arthur Dent | ||
202 | //// | ||
203 | //// Indicating that this is an inventory request for | ||
204 | //// an avatar named Arthur Dent. This is ALL that is | ||
205 | //// required to designate a GET for an entire | ||
206 | //// inventory. | ||
207 | //// | ||
208 | |||
209 | |||
210 | //// Do we have at least a user agent name? | ||
211 | |||
212 | //if (rdata.Parameters.Length < 1) | ||
213 | //{ | ||
214 | // Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); | ||
215 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified"); | ||
216 | //} | ||
217 | |||
218 | //// The first parameter MUST be the agent identification, either an UUID | ||
219 | //// or a space-separated First-name Last-Name specification. We check for | ||
220 | //// an UUID first, if anyone names their character using a valid UUID | ||
221 | //// that identifies another existing avatar will cause this a problem... | ||
222 | |||
223 | //try | ||
224 | //{ | ||
225 | // rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]); | ||
226 | // Rest.Log.DebugFormat("{0} UUID supplied", MsgId); | ||
227 | // rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); | ||
228 | //} | ||
229 | //catch | ||
230 | //{ | ||
231 | // string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE); | ||
232 | // if (names.Length == 2) | ||
233 | // { | ||
234 | // Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); | ||
235 | // rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]); | ||
236 | // } | ||
237 | // else | ||
238 | // { | ||
239 | // Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); | ||
240 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity"); | ||
241 | // } | ||
242 | //} | ||
243 | |||
244 | //// If the user profile is null then either the server is broken, or the | ||
245 | //// user is not known. We always assume the latter case. | ||
246 | |||
247 | //if (rdata.userProfile != null) | ||
248 | //{ | ||
249 | // Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", | ||
250 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
251 | //} | ||
252 | //else | ||
253 | //{ | ||
254 | // Rest.Log.WarnFormat("{0} No profile for {1}", MsgId, rdata.path); | ||
255 | // rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity"); | ||
256 | //} | ||
257 | |||
258 | //// If we get to here, then we have effectively validated the user's | ||
259 | //// identity. Now we need to get the inventory. If the server does not | ||
260 | //// have the inventory, we reject the request with an appropriate explanation. | ||
261 | //// | ||
262 | //// Note that inventory retrieval is an asynchronous event, we use the rdata | ||
263 | //// class instance as the basis for our synchronization. | ||
264 | //// | ||
265 | |||
266 | //rdata.uuid = rdata.userProfile.ID; | ||
267 | |||
268 | //if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid)) | ||
269 | //{ | ||
270 | // rdata.root = Rest.InventoryServices.GetRootFolder(rdata.uuid); | ||
271 | |||
272 | // Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}", | ||
273 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
274 | |||
275 | // Rest.InventoryServices.GetUserInventory(rdata.uuid, rdata.GetUserInventory); | ||
276 | |||
277 | // Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", | ||
278 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
279 | |||
280 | // lock (rdata) | ||
281 | // { | ||
282 | // if (!rdata.HaveInventory) | ||
283 | // { | ||
284 | // rdata.startWD(1000); | ||
285 | // rdata.timeout = false; | ||
286 | // Monitor.Wait(rdata); | ||
287 | // } | ||
288 | // } | ||
289 | |||
290 | // if (rdata.timeout) | ||
291 | // { | ||
292 | // Rest.Log.WarnFormat("{0} Inventory not available for {1} {2}. No response from service.", | ||
293 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
294 | // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory server not responding"); | ||
295 | // } | ||
296 | |||
297 | // if (rdata.root == null) | ||
298 | // { | ||
299 | // Rest.Log.WarnFormat("{0} Inventory is not available [1] for agent {1} {2}", | ||
300 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
301 | // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory retrieval failed"); | ||
302 | // } | ||
303 | |||
304 | //} | ||
305 | //else | ||
306 | //{ | ||
307 | // Rest.Log.WarnFormat("{0} Inventory is not locally available for agent {1} {2}", | ||
308 | // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
309 | // rdata.Fail(Rest.HttpStatusCodeNotFound, "no local inventory for user"); | ||
310 | //} | ||
311 | |||
312 | //// If we get here, then we have successfully retrieved the user's information | ||
313 | //// and inventory information is now available locally. | ||
314 | |||
315 | //switch (rdata.method) | ||
316 | //{ | ||
317 | // case Rest.HEAD : // Do the processing, set the status code, suppress entity | ||
318 | // DoGet(rdata); | ||
319 | // rdata.buffer = null; | ||
320 | // break; | ||
321 | |||
322 | // case Rest.GET : // Do the processing, set the status code, return entity | ||
323 | // DoGet(rdata); | ||
324 | // break; | ||
325 | |||
326 | // case Rest.PUT : // Update named element | ||
327 | // DoUpdate(rdata); | ||
328 | // break; | ||
329 | |||
330 | // case Rest.POST : // Add new information to identified context. | ||
331 | // DoExtend(rdata); | ||
332 | // break; | ||
333 | |||
334 | // case Rest.DELETE : // Delete information | ||
335 | // DoDelete(rdata); | ||
336 | // break; | ||
337 | |||
338 | // default : | ||
339 | // Rest.Log.WarnFormat("{0} Method {1} not supported for {2}", | ||
340 | // MsgId, rdata.method, rdata.path); | ||
341 | // rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, | ||
342 | // String.Format("{0} not supported", rdata.method)); | ||
343 | // break; | ||
344 | //} | ||
345 | } | ||
346 | |||
347 | #endregion Interface | ||
348 | |||
349 | #region method-specific processing | ||
350 | |||
351 | /// <summary> | ||
352 | /// This method implements GET processing for inventory. | ||
353 | /// Any remaining parameters are used to locate the | ||
354 | /// corresponding subtree based upon node name. | ||
355 | /// </summary> | ||
356 | /// <param name=rdata>HTTP service request work area</param> | ||
357 | // private void DoGet(InventoryRequestData rdata) | ||
358 | // { | ||
359 | // rdata.initXmlWriter(); | ||
360 | // | ||
361 | // rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); | ||
362 | // | ||
363 | // // If there are additional parameters, then these represent | ||
364 | // // a path relative to the root of the inventory. This path | ||
365 | // // must be traversed before we format the sub-tree thus | ||
366 | // // identified. | ||
367 | // | ||
368 | // traverse(rdata, rdata.root, PARM_PATH); | ||
369 | // | ||
370 | // // Close all open elements | ||
371 | // | ||
372 | // rdata.writer.WriteFullEndElement(); | ||
373 | // | ||
374 | // // Indicate a successful request | ||
375 | // | ||
376 | // rdata.Complete(); | ||
377 | // | ||
378 | // // Send the response to the user. The body will be implicitly | ||
379 | // // constructed from the result of the XML writer. | ||
380 | // | ||
381 | // rdata.Respond(String.Format("Inventory {0} Normal completion", rdata.method)); | ||
382 | // } | ||
383 | |||
384 | /// <summary> | ||
385 | /// In the case of the inventory, and probably in general, | ||
386 | /// the distinction between PUT and POST is not always | ||
387 | /// easy to discern. The standard is badly worded in places, | ||
388 | /// and adding a node to a hierarchy can be viewed as | ||
389 | /// an addition, or as a modification to the inventory as | ||
390 | /// a whole. This is exacerbated by an unjustified lack of | ||
391 | /// consistency across different implementations. | ||
392 | /// | ||
393 | /// For OpenSim PUT is an update and POST is an addition. This | ||
394 | /// is the behavior required by the HTTP specification and | ||
395 | /// therefore as required by REST. | ||
396 | /// | ||
397 | /// The best way to explain the distinction is to | ||
398 | /// consider the relationship between the URI and the | ||
399 | /// enclosed entity. For PUT, the URI identifies the | ||
400 | /// actual entity to be modified or replaced, i.e. the | ||
401 | /// enclosed entity. | ||
402 | /// | ||
403 | /// If the operation is POST,then the URI describes the | ||
404 | /// context into which the new entity will be added. | ||
405 | /// | ||
406 | /// As an example, suppose the URI contains: | ||
407 | /// /admin/inventory/Clothing | ||
408 | /// | ||
409 | /// A PUT request will normally result in some modification of | ||
410 | /// the folder or item named "Clothing". Whereas a POST | ||
411 | /// request will normally add some new information into the | ||
412 | /// content identified by Clothing. It follows from this | ||
413 | /// that for POST, the element identified by the URI MUST | ||
414 | /// be a folder. | ||
415 | /// </summary> | ||
416 | |||
417 | /// <summary> | ||
418 | /// POST adds new information to the inventory in the | ||
419 | /// context identified by the URI. | ||
420 | /// </summary> | ||
421 | /// <param name=rdata>HTTP service request work area</param> | ||
422 | // private void DoExtend(InventoryRequestData rdata) | ||
423 | // { | ||
424 | // bool created = false; | ||
425 | // bool modified = false; | ||
426 | // string newnode = String.Empty; | ||
427 | // | ||
428 | // // Resolve the context node specified in the URI. Entity | ||
429 | // // data will be ADDED beneath this node. rdata already contains | ||
430 | // // information about the current content of the user's | ||
431 | // // inventory. | ||
432 | // | ||
433 | // Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); | ||
434 | // | ||
435 | // // Processing depends upon the type of inventory node | ||
436 | // // identified in the URI. This is the CONTEXT for the | ||
437 | // // change. We either got a context or we threw an | ||
438 | // // exception. | ||
439 | // | ||
440 | // // It follows that we can only add information if the URI | ||
441 | // // has identified a folder. So only a type of folder is supported | ||
442 | // // in this case. | ||
443 | // | ||
444 | // if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | ||
445 | // typeof(InventoryFolderImpl) == InventoryNode.GetType()) | ||
446 | // { | ||
447 | // // Cast the context node appropriately. | ||
448 | // | ||
449 | // InventoryFolderBase context = (InventoryFolderBase) InventoryNode; | ||
450 | // | ||
451 | // Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}", | ||
452 | // MsgId, rdata.method, rdata.path); | ||
453 | // | ||
454 | // // Reconstitute the inventory sub-tree from the XML supplied in the entity. | ||
455 | // // The result is a stand-alone inventory subtree, not yet integrated into the | ||
456 | // // existing tree. An inventory collection consists of three components: | ||
457 | // // [1] A (possibly empty) set of folders. | ||
458 | // // [2] A (possibly empty) set of items. | ||
459 | // // [3] A (possibly empty) set of assets. | ||
460 | // // If all of these are empty, then the POST is a harmless no-operation. | ||
461 | // | ||
462 | // XmlInventoryCollection entity = ReconstituteEntity(rdata); | ||
463 | // | ||
464 | // // Inlined assets can be included in entity. These must be incorporated into | ||
465 | // // the asset database before we attempt to update the inventory. If anything | ||
466 | // // fails, return a failure to requestor. | ||
467 | // | ||
468 | // if (entity.Assets.Count > 0) | ||
469 | // { | ||
470 | // Rest.Log.DebugFormat("{0} Adding {1} assets to server", | ||
471 | // MsgId, entity.Assets.Count); | ||
472 | // | ||
473 | // foreach (AssetBase asset in entity.Assets) | ||
474 | // { | ||
475 | // Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", | ||
476 | // MsgId, asset.ID, asset.Type, asset.Name); | ||
477 | // Rest.AssetServices.Store(asset); | ||
478 | // | ||
479 | // created = true; | ||
480 | // rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", | ||
481 | // asset.Name, asset.ID)); | ||
482 | // | ||
483 | // if (Rest.DEBUG && Rest.DumpAsset) | ||
484 | // { | ||
485 | // Rest.Dump(asset.Data); | ||
486 | // } | ||
487 | // } | ||
488 | // } | ||
489 | // | ||
490 | // // Modify the context using the collection of folders and items | ||
491 | // // returned in the XmlInventoryCollection. | ||
492 | // | ||
493 | // foreach (InventoryFolderBase folder in entity.Folders) | ||
494 | // { | ||
495 | // InventoryFolderBase found; | ||
496 | // | ||
497 | // // If the parentID is zero, then this folder is going | ||
498 | // // into the root folder identified by the URI. The requestor | ||
499 | // // may have already set the parent ID explicitly, in which | ||
500 | // // case we don't have to do it here. | ||
501 | // | ||
502 | // if (folder.ParentID == UUID.Zero || folder.ParentID == context.ID) | ||
503 | // { | ||
504 | // if (newnode != String.Empty) | ||
505 | // { | ||
506 | // Rest.Log.DebugFormat("{0} Too many resources", MsgId); | ||
507 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "only one root entity is allowed"); | ||
508 | // } | ||
509 | // folder.ParentID = context.ID; | ||
510 | // newnode = folder.Name; | ||
511 | // } | ||
512 | // | ||
513 | // // Search the existing inventory for an existing entry. If | ||
514 | // // we have one, we need to decide if it has really changed. | ||
515 | // // It could just be present as (unnecessary) context, and we | ||
516 | // // don't want to waste time updating the database in that | ||
517 | // // case, OR, it could be being moved from another location | ||
518 | // // in which case an update is most certainly necessary. | ||
519 | // | ||
520 | // found = null; | ||
521 | // | ||
522 | // foreach (InventoryFolderBase xf in rdata.folders) | ||
523 | // { | ||
524 | // // Compare identifying attribute | ||
525 | // if (xf.ID == folder.ID) | ||
526 | // { | ||
527 | // found = xf; | ||
528 | // break; | ||
529 | // } | ||
530 | // } | ||
531 | // | ||
532 | // if (found != null && FolderHasChanged(folder,found)) | ||
533 | // { | ||
534 | // Rest.Log.DebugFormat("{0} Updating existing folder", MsgId); | ||
535 | // Rest.InventoryServices.MoveFolder(folder); | ||
536 | // | ||
537 | // modified = true; | ||
538 | // rdata.appendStatus(String.Format("<p> Created folder {0}, UUID {1} <p>", | ||
539 | // folder.Name, folder.ID)); | ||
540 | // } | ||
541 | // else | ||
542 | // { | ||
543 | // Rest.Log.DebugFormat("{0} Adding new folder", MsgId); | ||
544 | // Rest.InventoryServices.AddFolder(folder); | ||
545 | // | ||
546 | // created = true; | ||
547 | // rdata.appendStatus(String.Format("<p> Modified folder {0}, UUID {1} <p>", | ||
548 | // folder.Name, folder.ID)); | ||
549 | // } | ||
550 | // } | ||
551 | // | ||
552 | // // Now we repeat a similar process for the items included | ||
553 | // // in the entity. | ||
554 | // | ||
555 | // foreach (InventoryItemBase item in entity.Items) | ||
556 | // { | ||
557 | // InventoryItemBase found = null; | ||
558 | // | ||
559 | // // If the parentID is zero, then this is going | ||
560 | // // directly into the root identified by the URI. | ||
561 | // | ||
562 | // if (item.Folder == UUID.Zero) | ||
563 | // { | ||
564 | // item.Folder = context.ID; | ||
565 | // } | ||
566 | // | ||
567 | // // Determine whether this is a new item or a | ||
568 | // // replacement definition. | ||
569 | // | ||
570 | // foreach (InventoryItemBase xi in rdata.items) | ||
571 | // { | ||
572 | // // Compare identifying attribute | ||
573 | // if (xi.ID == item.ID) | ||
574 | // { | ||
575 | // found = xi; | ||
576 | // break; | ||
577 | // } | ||
578 | // } | ||
579 | // | ||
580 | // if (found != null && ItemHasChanged(item, found)) | ||
581 | // { | ||
582 | // Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}", | ||
583 | // MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); | ||
584 | // Rest.InventoryServices.UpdateItem(item); | ||
585 | // modified = true; | ||
586 | // rdata.appendStatus(String.Format("<p> Modified item {0}, UUID {1} <p>", item.Name, item.ID)); | ||
587 | // } | ||
588 | // else | ||
589 | // { | ||
590 | // Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}", | ||
591 | // MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); | ||
592 | // Rest.InventoryServices.AddItem(item); | ||
593 | // created = true; | ||
594 | // rdata.appendStatus(String.Format("<p> Created item {0}, UUID {1} <p>", item.Name, item.ID)); | ||
595 | // } | ||
596 | // } | ||
597 | // | ||
598 | // if (created) | ||
599 | // { | ||
600 | // // Must include a location header with a URI that identifies the new resource. | ||
601 | // rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}/{3}", | ||
602 | // rdata.hostname, rdata.port,rdata.path,newnode)); | ||
603 | // rdata.Complete(Rest.HttpStatusCodeCreated); | ||
604 | // } | ||
605 | // else | ||
606 | // { | ||
607 | // if (modified) | ||
608 | // { | ||
609 | // rdata.Complete(Rest.HttpStatusCodeOK); | ||
610 | // } | ||
611 | // else | ||
612 | // { | ||
613 | // rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
614 | // } | ||
615 | // } | ||
616 | // | ||
617 | // rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); | ||
618 | // } | ||
619 | // else | ||
620 | // { | ||
621 | // Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", | ||
622 | // MsgId, rdata.method, rdata.path, InventoryNode.GetType()); | ||
623 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid resource context"); | ||
624 | // } | ||
625 | // } | ||
626 | |||
627 | /// <summary> | ||
628 | /// PUT updates the URI-identified element in the inventory. This | ||
629 | /// is actually far more flexible than it might at first sound. For | ||
630 | /// PUT the URI serves two purposes: | ||
631 | /// [1] It identifies the user whose inventory is to be | ||
632 | /// processed. | ||
633 | /// [2] It optionally specifies a subtree of the inventory | ||
634 | /// that is to be used to resolve any relative subtree | ||
635 | /// specifications in the entity. If nothing is specified | ||
636 | /// then the whole of the private inventory is implied. | ||
637 | /// Please note that the subtree specified by the URI is only relevant | ||
638 | /// to an entity containing a URI relative specification, i.e. one or | ||
639 | /// more elements do not specify parent folder information. These | ||
640 | /// elements will be implicitly referenced within the context identified | ||
641 | /// by the URI. | ||
642 | /// If an element in the entity specifies an explicit parent folder, then | ||
643 | /// that parent is effective, regardless of any value specified in the | ||
644 | /// URI. If the parent does not exist, then the element, and any dependent | ||
645 | /// elements, are ignored. This case is actually detected and handled | ||
646 | /// during the reconstitution process. | ||
647 | /// </summary> | ||
648 | /// <param name=rdata>HTTP service request work area</param> | ||
649 | // private void DoUpdate(InventoryRequestData rdata) | ||
650 | // { | ||
651 | // int count = 0; | ||
652 | // bool created = false; | ||
653 | // bool modified = false; | ||
654 | // | ||
655 | // // Resolve the inventory node that is to be modified. | ||
656 | // // rdata already contains information about the current | ||
657 | // // content of the user's inventory. | ||
658 | // | ||
659 | // Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); | ||
660 | // | ||
661 | // // As long as we have a node, then we have something | ||
662 | // // meaningful to do, unlike POST. So we reconstitute the | ||
663 | // // subtree before doing anything else. Note that we | ||
664 | // // etiher got a valid node or we threw an exception. | ||
665 | // | ||
666 | // XmlInventoryCollection entity = ReconstituteEntity(rdata); | ||
667 | // | ||
668 | // // Incorporate any inlined assets first. Any failures | ||
669 | // // will terminate the request. | ||
670 | // | ||
671 | // if (entity.Assets.Count > 0) | ||
672 | // { | ||
673 | // Rest.Log.DebugFormat("{0} Adding {1} assets to server", | ||
674 | // MsgId, entity.Assets.Count); | ||
675 | // | ||
676 | // foreach (AssetBase asset in entity.Assets) | ||
677 | // { | ||
678 | // Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", | ||
679 | // MsgId, asset.ID, asset.Type, asset.Name); | ||
680 | // | ||
681 | // // The asset was validated during the collection process | ||
682 | // | ||
683 | // Rest.AssetServices.Store(asset); | ||
684 | // | ||
685 | // created = true; | ||
686 | // rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.ID)); | ||
687 | // | ||
688 | // if (Rest.DEBUG && Rest.DumpAsset) | ||
689 | // { | ||
690 | // Rest.Dump(asset.Data); | ||
691 | // } | ||
692 | // } | ||
693 | // } | ||
694 | // | ||
695 | // // The URI specifies either a folder or an item to be updated. | ||
696 | // // | ||
697 | // // The root node in the entity will replace the node identified | ||
698 | // // by the URI. This means the parent will remain the same, but | ||
699 | // // any or all attributes associated with the named element | ||
700 | // // will change. | ||
701 | // // | ||
702 | // // If the inventory collection contains an element with a zero | ||
703 | // // parent ID, then this is taken to be the replacement for the | ||
704 | // // named node. The collection MAY also specify an explicit | ||
705 | // // parent ID, in this case it MAY identify the same parent as | ||
706 | // // the current node, or it MAY specify a different parent, | ||
707 | // // indicating that the folder is being moved in addition to any | ||
708 | // // other modifications being made. | ||
709 | // | ||
710 | // if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | ||
711 | // typeof(InventoryFolderImpl) == InventoryNode.GetType()) | ||
712 | // { | ||
713 | // bool rfound = false; | ||
714 | // InventoryFolderBase uri = (InventoryFolderBase) InventoryNode; | ||
715 | // InventoryFolderBase xml = null; | ||
716 | // | ||
717 | // // If the entity to be replaced resolved to be the root | ||
718 | // // directory itself (My Inventory), then make sure that | ||
719 | // // the supplied data include as appropriately typed and | ||
720 | // // named folder. Note that we can;t rule out the possibility | ||
721 | // // of a sub-directory being called "My Inventory", so that | ||
722 | // // is anticipated. | ||
723 | // | ||
724 | // if (uri == rdata.root) | ||
725 | // { | ||
726 | // foreach (InventoryFolderBase folder in entity.Folders) | ||
727 | // { | ||
728 | // if ((rfound = (folder.Name == PRIVATE_ROOT_NAME))) | ||
729 | // { | ||
730 | // if ((rfound = (folder.ParentID == UUID.Zero))) | ||
731 | // break; | ||
732 | // } | ||
733 | // } | ||
734 | // | ||
735 | // if (!rfound) | ||
736 | // { | ||
737 | // Rest.Log.DebugFormat("{0} {1}: Path <{2}> will result in loss of inventory", | ||
738 | // MsgId, rdata.method, rdata.path); | ||
739 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid inventory structure"); | ||
740 | // } | ||
741 | // } | ||
742 | // | ||
743 | // // Scan the set of folders in the entity collection for an | ||
744 | // // entry that matches the context folder. It is assumed that | ||
745 | // // the only reliable indicator of this is a zero UUID (using | ||
746 | // // implicit context), or the parent's UUID matches that of the | ||
747 | // // URI designated node (explicit context). We don't allow | ||
748 | // // ambiguity in this case because this is POST and we are | ||
749 | // // supposed to be modifying a specific node. | ||
750 | // // We assign any element IDs required as an economy; we don't | ||
751 | // // want to iterate over the fodler set again if it can be | ||
752 | // // helped. | ||
753 | // | ||
754 | // foreach (InventoryFolderBase folder in entity.Folders) | ||
755 | // { | ||
756 | // if (folder.ParentID == uri.ParentID || | ||
757 | // folder.ParentID == UUID.Zero) | ||
758 | // { | ||
759 | // folder.ParentID = uri.ParentID; | ||
760 | // xml = folder; | ||
761 | // count++; | ||
762 | // } | ||
763 | // } | ||
764 | // | ||
765 | // // More than one entry is ambiguous. Other folders should be | ||
766 | // // added using the POST verb. | ||
767 | // | ||
768 | // if (count > 1) | ||
769 | // { | ||
770 | // Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", | ||
771 | // MsgId, rdata.method, rdata.path); | ||
772 | // rdata.Fail(Rest.HttpStatusCodeConflict, "context is ambiguous"); | ||
773 | // } | ||
774 | // | ||
775 | // // Exactly one entry means we ARE replacing the node | ||
776 | // // identified by the URI. So we delete the old folder | ||
777 | // // by moving it to the trash and then purging it. | ||
778 | // // We then add all of the folders and items we | ||
779 | // // included in the entity. The subtree has been | ||
780 | // // modified. | ||
781 | // | ||
782 | // if (count == 1) | ||
783 | // { | ||
784 | // InventoryFolderBase TrashCan = GetTrashCan(rdata); | ||
785 | // | ||
786 | // // All went well, so we generate a UUID is one is | ||
787 | // // needed. | ||
788 | // | ||
789 | // if (xml.ID == UUID.Zero) | ||
790 | // { | ||
791 | // xml.ID = UUID.Random(); | ||
792 | // } | ||
793 | // | ||
794 | // uri.ParentID = TrashCan.ID; | ||
795 | // Rest.InventoryServices.MoveFolder(uri); | ||
796 | // Rest.InventoryServices.PurgeFolder(TrashCan); | ||
797 | // modified = true; | ||
798 | // } | ||
799 | // | ||
800 | // // Now, regardelss of what they represent, we | ||
801 | // // integrate all of the elements in the entity. | ||
802 | // | ||
803 | // foreach (InventoryFolderBase f in entity.Folders) | ||
804 | // { | ||
805 | // rdata.appendStatus(String.Format("<p>Moving folder {0} UUID {1} <p>", f.Name, f.ID)); | ||
806 | // Rest.InventoryServices.MoveFolder(f); | ||
807 | // } | ||
808 | // | ||
809 | // foreach (InventoryItemBase it in entity.Items) | ||
810 | // { | ||
811 | // rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1} <p>", it.Name, it.ID)); | ||
812 | // Rest.InventoryServices.AddItem(it); | ||
813 | // } | ||
814 | // } | ||
815 | // | ||
816 | // /// <summary> | ||
817 | // /// URI specifies an item to be updated | ||
818 | // /// </summary> | ||
819 | // /// <remarks> | ||
820 | // /// The entity must contain a single item node to be | ||
821 | // /// updated. ID and Folder ID must be correct. | ||
822 | // /// </remarks> | ||
823 | // | ||
824 | // else | ||
825 | // { | ||
826 | // InventoryItemBase uri = (InventoryItemBase) InventoryNode; | ||
827 | // InventoryItemBase xml = null; | ||
828 | // | ||
829 | // if (entity.Folders.Count != 0) | ||
830 | // { | ||
831 | // Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", | ||
832 | // MsgId, rdata.method, rdata.path); | ||
833 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "folder is not allowed"); | ||
834 | // } | ||
835 | // | ||
836 | // if (entity.Items.Count > 1) | ||
837 | // { | ||
838 | // Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", | ||
839 | // MsgId, rdata.method, rdata.path); | ||
840 | // rdata.Fail(Rest.HttpStatusCodeBadRequest, "too may items"); | ||
841 | // } | ||
842 | // | ||
843 | // xml = entity.Items[0]; | ||
844 | // | ||
845 | // if (xml.ID == UUID.Zero) | ||
846 | // { | ||
847 | // xml.ID = UUID.Random(); | ||
848 | // } | ||
849 | // | ||
850 | // // If the folder reference has changed, then this item is | ||
851 | // // being moved. Otherwise we'll just delete the old, and | ||
852 | // // add in the new. | ||
853 | // | ||
854 | // // Delete the old item | ||
855 | // | ||
856 | // List<UUID> uuids = new List<UUID>(); | ||
857 | // uuids.Add(uri.ID); | ||
858 | // Rest.InventoryServices.DeleteItems(uri.Owner, uuids); | ||
859 | // | ||
860 | // // Add the new item to the inventory | ||
861 | // | ||
862 | // Rest.InventoryServices.AddItem(xml); | ||
863 | // | ||
864 | // rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1} <p>", xml.Name, xml.ID)); | ||
865 | // } | ||
866 | // | ||
867 | // if (created) | ||
868 | // { | ||
869 | // rdata.Complete(Rest.HttpStatusCodeCreated); | ||
870 | // } | ||
871 | // else | ||
872 | // { | ||
873 | // if (modified) | ||
874 | // { | ||
875 | // rdata.Complete(Rest.HttpStatusCodeOK); | ||
876 | // } | ||
877 | // else | ||
878 | // { | ||
879 | // rdata.Complete(Rest.HttpStatusCodeNoContent); | ||
880 | // } | ||
881 | // } | ||
882 | // | ||
883 | // rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); | ||
884 | // } | ||
885 | |||
886 | /// <summary> | ||
887 | /// Arguably the most damaging REST interface. It deletes the inventory | ||
888 | /// item or folder identified by the URI. | ||
889 | /// | ||
890 | /// We only process if the URI identified node appears to exist | ||
891 | /// We do not test for success because we either get a context, | ||
892 | /// or an exception is thrown. | ||
893 | /// | ||
894 | /// Folders are deleted by moving them to another folder and then | ||
895 | /// purging that folder. We'll do that by creating a temporary | ||
896 | /// sub-folder in the TrashCan and purging that folder's | ||
897 | /// contents. If we can't can it, we don't delete it... | ||
898 | /// So, if no trashcan is available, the request does nothing. | ||
899 | /// Items are summarily deleted. | ||
900 | /// | ||
901 | /// In the interests of safety, a delete request should normally | ||
902 | /// be performed using UUID, as a name might identify several | ||
903 | /// elements. | ||
904 | /// </summary> | ||
905 | /// <param name=rdata>HTTP service request work area</param> | ||
906 | // private void DoDelete(InventoryRequestData rdata) | ||
907 | // { | ||
908 | // Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, false); | ||
909 | // | ||
910 | // if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | ||
911 | // typeof(InventoryFolderImpl) == InventoryNode.GetType()) | ||
912 | // { | ||
913 | // InventoryFolderBase TrashCan = GetTrashCan(rdata); | ||
914 | // | ||
915 | // InventoryFolderBase folder = (InventoryFolderBase) InventoryNode; | ||
916 | // Rest.Log.DebugFormat("{0} {1}: Folder {2} will be deleted", | ||
917 | // MsgId, rdata.method, rdata.path); | ||
918 | // folder.ParentID = TrashCan.ID; | ||
919 | // Rest.InventoryServices.MoveFolder(folder); | ||
920 | // Rest.InventoryServices.PurgeFolder(TrashCan); | ||
921 | // | ||
922 | // rdata.appendStatus(String.Format("<p>Deleted folder {0} UUID {1} <p>", folder.Name, folder.ID)); | ||
923 | // } | ||
924 | // | ||
925 | // // Deleting items is much more straight forward. | ||
926 | // | ||
927 | // else | ||
928 | // { | ||
929 | // InventoryItemBase item = (InventoryItemBase) InventoryNode; | ||
930 | // Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted", | ||
931 | // MsgId, rdata.method, rdata.path); | ||
932 | // List<UUID> uuids = new List<UUID>(); | ||
933 | // uuids.Add(item.ID); | ||
934 | // Rest.InventoryServices.DeleteItems(item.Owner, uuids); | ||
935 | // rdata.appendStatus(String.Format("<p>Deleted item {0} UUID {1} <p>", item.Name, item.ID)); | ||
936 | // } | ||
937 | // | ||
938 | // rdata.Complete(); | ||
939 | // rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); | ||
940 | // } | ||
941 | |||
942 | #endregion method-specific processing | ||
943 | |||
944 | /// <summary> | ||
945 | /// This method is called to obtain the OpenSim inventory object identified | ||
946 | /// by the supplied URI. This may be either an Item or a Folder, so a suitably | ||
947 | /// ambiguous return type is employed (Object). This method recurses as | ||
948 | /// necessary to process the designated hierarchy. | ||
949 | /// | ||
950 | /// If we reach the end of the URI then we return the contextual folder to | ||
951 | /// our caller. | ||
952 | /// | ||
953 | /// If we are not yet at the end of the URI we attempt to find a child folder | ||
954 | /// and if we succeed we recurse. | ||
955 | /// | ||
956 | /// If this is the last node, then we look to see if this is an item. If it is, | ||
957 | /// we return that item. | ||
958 | /// | ||
959 | /// If we reach the end of an inventory path and the URI si not yet exhausted, | ||
960 | /// then if 'fill' is specified, we create the intermediate nodes. | ||
961 | /// | ||
962 | /// Otherwise we fail the request on the ground of an invalid URI. | ||
963 | /// | ||
964 | /// An ambiguous request causes the request to fail. | ||
965 | /// | ||
966 | /// </summary> | ||
967 | /// <param name=rdata>HTTP service request work area</param> | ||
968 | /// <param name=folder>The folder to be searched (parent)</param> | ||
969 | /// <param name=pi>URI parameter index</param> | ||
970 | /// <param name=fill>Should missing path members be created?</param> | ||
971 | |||
972 | private Object getInventoryNode(InventoryRequestData rdata, | ||
973 | InventoryFolderBase folder, | ||
974 | int pi, bool fill) | ||
975 | { | ||
976 | InventoryFolderBase foundf = null; | ||
977 | int fk = 0; | ||
978 | |||
979 | Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | ||
980 | |||
981 | // We have just run off the end of the parameter sequence | ||
982 | |||
983 | if (pi >= rdata.Parameters.Length) | ||
984 | { | ||
985 | return folder; | ||
986 | } | ||
987 | |||
988 | // There are more names in the parameter sequence, | ||
989 | // look for the folder named by param[pi] as a | ||
990 | // child of the folder supplied as an argument. | ||
991 | // Note that a UUID may have been supplied as the | ||
992 | // identifier (it is the ONLY guaranteed unambiguous | ||
993 | // option. | ||
994 | |||
995 | if (rdata.folders != null) | ||
996 | { | ||
997 | foreach (InventoryFolderBase f in rdata.folders) | ||
998 | { | ||
999 | // Look for the present node in the directory list | ||
1000 | if (f.ParentID == folder.ID && | ||
1001 | (f.Name == rdata.Parameters[pi] || | ||
1002 | f.ID.ToString() == rdata.Parameters[pi])) | ||
1003 | { | ||
1004 | foundf = f; | ||
1005 | fk++; | ||
1006 | } | ||
1007 | } | ||
1008 | } | ||
1009 | |||
1010 | // If more than one node matched, then the path, as specified | ||
1011 | // is ambiguous. | ||
1012 | |||
1013 | if (fk > 1) | ||
1014 | { | ||
1015 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", | ||
1016 | MsgId, rdata.method, rdata.path); | ||
1017 | rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); | ||
1018 | } | ||
1019 | |||
1020 | // If we find a match, then the method | ||
1021 | // increment the parameter index, and calls itself | ||
1022 | // passing the found folder as the new context. | ||
1023 | |||
1024 | if (foundf != null) | ||
1025 | { | ||
1026 | return getInventoryNode(rdata, foundf, pi+1, fill); | ||
1027 | } | ||
1028 | |||
1029 | // No folders that match. Perhaps this parameter identifies an item? If | ||
1030 | // it does, then it MUST also be the last name in the sequence. | ||
1031 | |||
1032 | if (pi == rdata.Parameters.Length-1) | ||
1033 | { | ||
1034 | if (rdata.items != null) | ||
1035 | { | ||
1036 | int k = 0; | ||
1037 | InventoryItemBase li = null; | ||
1038 | foreach (InventoryItemBase i in rdata.items) | ||
1039 | { | ||
1040 | if (i.Folder == folder.ID && | ||
1041 | (i.Name == rdata.Parameters[pi] || | ||
1042 | i.ID.ToString() == rdata.Parameters[pi])) | ||
1043 | { | ||
1044 | li = i; | ||
1045 | k++; | ||
1046 | } | ||
1047 | } | ||
1048 | if (k == 1) | ||
1049 | { | ||
1050 | return li; | ||
1051 | } | ||
1052 | else if (k > 1) | ||
1053 | { | ||
1054 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", | ||
1055 | MsgId, rdata.method, rdata.path); | ||
1056 | rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); | ||
1057 | } | ||
1058 | } | ||
1059 | } | ||
1060 | |||
1061 | // If fill is enabled, then we must create the missing intermediate nodes. | ||
1062 | // And of course, even this is not straightforward. All intermediate nodes | ||
1063 | // are obviously folders, but the last node may be a folder or an item. | ||
1064 | |||
1065 | if (fill) | ||
1066 | { | ||
1067 | } | ||
1068 | |||
1069 | // No fill, so abandon the request | ||
1070 | |||
1071 | Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", | ||
1072 | MsgId, rdata.method, rdata.path); | ||
1073 | rdata.Fail(Rest.HttpStatusCodeNotFound, | ||
1074 | String.Format("resource {0}:{1} not found", rdata.method, rdata.path)); | ||
1075 | |||
1076 | return null; /* Never reached */ | ||
1077 | } | ||
1078 | |||
1079 | /// <summary> | ||
1080 | /// This routine traverse the inventory's structure until the end-point identified | ||
1081 | /// in the URI is reached, the remainder of the inventory (if any) is then formatted | ||
1082 | /// and returned to the requestor. | ||
1083 | /// | ||
1084 | /// Note that this method is only interested in those folder that match elements of | ||
1085 | /// the URI supplied by the requestor, so once a match is fund, the processing does | ||
1086 | /// not need to consider any further elements. | ||
1087 | /// | ||
1088 | /// Only the last element in the URI should identify an item. | ||
1089 | /// </summary> | ||
1090 | /// <param name=rdata>HTTP service request work area</param> | ||
1091 | /// <param name=folder>The folder to be searched (parent)</param> | ||
1092 | /// <param name=pi>URI parameter index</param> | ||
1093 | |||
1094 | private void traverse(InventoryRequestData rdata, InventoryFolderBase folder, int pi) | ||
1095 | { | ||
1096 | Rest.Log.DebugFormat("{0} Traverse[initial] : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | ||
1097 | |||
1098 | if (rdata.folders != null) | ||
1099 | { | ||
1100 | // If there was only one parameter (avatar name), then the entire | ||
1101 | // inventory is being requested. | ||
1102 | |||
1103 | if (rdata.Parameters.Length == 1) | ||
1104 | { | ||
1105 | formatInventory(rdata, rdata.root, String.Empty); | ||
1106 | } | ||
1107 | |||
1108 | // Has the client specified the root directory name explicitly? | ||
1109 | // if yes, then we just absorb the reference, because the folder | ||
1110 | // we start looking in for a match *is* the root directory. If there | ||
1111 | // are more parameters remaining we tarverse, otehrwise it's time | ||
1112 | // to format. Otherwise,we consider the "My Inventory" to be implied | ||
1113 | // and we just traverse normally. | ||
1114 | |||
1115 | else if (folder.ID.ToString() == rdata.Parameters[pi] || | ||
1116 | folder.Name == rdata.Parameters[pi]) | ||
1117 | { | ||
1118 | // Length is -1 because the avatar name is a parameter | ||
1119 | if (pi<(rdata.Parameters.Length-1)) | ||
1120 | { | ||
1121 | traverseInventory(rdata, folder, pi+1); | ||
1122 | } | ||
1123 | else | ||
1124 | { | ||
1125 | formatInventory(rdata, folder, String.Empty); | ||
1126 | } | ||
1127 | } | ||
1128 | else | ||
1129 | { | ||
1130 | traverseInventory(rdata, folder, pi); | ||
1131 | } | ||
1132 | |||
1133 | return; | ||
1134 | } | ||
1135 | } | ||
1136 | |||
1137 | /// <summary> | ||
1138 | /// This is the recursive method. I've separated them in this way so that | ||
1139 | /// we do not have to waste cycles on any first-case-only processing. | ||
1140 | /// </summary> | ||
1141 | |||
1142 | private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi) | ||
1143 | { | ||
1144 | int fk = 0; | ||
1145 | InventoryFolderBase ffound = null; | ||
1146 | InventoryItemBase ifound = null; | ||
1147 | |||
1148 | Rest.Log.DebugFormat("{0} Traverse Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | ||
1149 | |||
1150 | foreach (InventoryFolderBase f in rdata.folders) | ||
1151 | { | ||
1152 | if (f.ParentID == folder.ID && | ||
1153 | (f.Name == rdata.Parameters[pi] || | ||
1154 | f.ID.ToString() == rdata.Parameters[pi])) | ||
1155 | { | ||
1156 | fk++; | ||
1157 | ffound = f; | ||
1158 | } | ||
1159 | } | ||
1160 | |||
1161 | // If this is the last element in the parameter sequence, then | ||
1162 | // it is reasonable to check for an item. All intermediate nodes | ||
1163 | // MUST be folders. | ||
1164 | |||
1165 | if (pi == rdata.Parameters.Length-1) | ||
1166 | { | ||
1167 | // Only if there are any items, and there pretty much always are. | ||
1168 | |||
1169 | if (rdata.items != null) | ||
1170 | { | ||
1171 | foreach (InventoryItemBase i in rdata.items) | ||
1172 | { | ||
1173 | if (i.Folder == folder.ID && | ||
1174 | (i.Name == rdata.Parameters[pi] || | ||
1175 | i.ID.ToString() == rdata.Parameters[pi])) | ||
1176 | { | ||
1177 | fk++; | ||
1178 | ifound = i; | ||
1179 | } | ||
1180 | } | ||
1181 | } | ||
1182 | } | ||
1183 | |||
1184 | if (fk == 1) | ||
1185 | { | ||
1186 | if (ffound != null) | ||
1187 | { | ||
1188 | if (pi < rdata.Parameters.Length-1) | ||
1189 | { | ||
1190 | traverseInventory(rdata, ffound, pi+1); | ||
1191 | } | ||
1192 | else | ||
1193 | { | ||
1194 | formatInventory(rdata, ffound, String.Empty); | ||
1195 | } | ||
1196 | return; | ||
1197 | } | ||
1198 | else | ||
1199 | { | ||
1200 | // Fetching an Item has a special significance. In this | ||
1201 | // case we also want to fetch the associated asset. | ||
1202 | // To make it interesting, we'll do this via redirection. | ||
1203 | string asseturl = String.Format("http://{0}:{1}/{2}{3}{4}", rdata.hostname, rdata.port, | ||
1204 | "admin/assets",Rest.UrlPathSeparator,ifound.AssetID.ToString()); | ||
1205 | rdata.Redirect(asseturl,Rest.PERMANENT); | ||
1206 | Rest.Log.DebugFormat("{0} Never Reached", MsgId); | ||
1207 | } | ||
1208 | } | ||
1209 | else if (fk > 1) | ||
1210 | { | ||
1211 | rdata.Fail(Rest.HttpStatusCodeConflict, | ||
1212 | String.Format("ambiguous element ({0}) in path specified: <{1}>", | ||
1213 | pi, rdata.path)); | ||
1214 | } | ||
1215 | |||
1216 | Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", | ||
1217 | MsgId, rdata.path); | ||
1218 | rdata.Fail(Rest.HttpStatusCodeNotFound,String.Format("no such item/folder : {0}", | ||
1219 | rdata.Parameters[pi])); | ||
1220 | |||
1221 | } | ||
1222 | |||
1223 | /// <summary> | ||
1224 | /// This method generates XML that describes an instance of InventoryFolderBase. | ||
1225 | /// It recurses as necessary to reflect a folder hierarchy, and calls formatItem | ||
1226 | /// to generate XML for any items encountered along the way. | ||
1227 | /// The indentation parameter is solely for the benefit of trace record | ||
1228 | /// formatting. | ||
1229 | /// </summary> | ||
1230 | /// <param name=rdata>HTTP service request work area</param> | ||
1231 | /// <param name=folder>The folder to be searched (parent)</param> | ||
1232 | /// <param name=indent>pretty print indentation</param> | ||
1233 | private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent) | ||
1234 | { | ||
1235 | if (Rest.DEBUG) | ||
1236 | { | ||
1237 | Rest.Log.DebugFormat("{0} Folder : {1} {2} {3} type = {4}", | ||
1238 | MsgId, folder.ID, indent, folder.Name, folder.Type); | ||
1239 | indent += "\t"; | ||
1240 | } | ||
1241 | |||
1242 | // Start folder item | ||
1243 | |||
1244 | rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty); | ||
1245 | rdata.writer.WriteAttributeString("name",String.Empty,folder.Name); | ||
1246 | rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString()); | ||
1247 | rdata.writer.WriteAttributeString("parent",String.Empty,folder.ParentID.ToString()); | ||
1248 | rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString()); | ||
1249 | rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString()); | ||
1250 | rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString()); | ||
1251 | |||
1252 | if (rdata.folders != null) | ||
1253 | { | ||
1254 | foreach (InventoryFolderBase f in rdata.folders) | ||
1255 | { | ||
1256 | if (f.ParentID == folder.ID) | ||
1257 | { | ||
1258 | formatInventory(rdata, f, indent); | ||
1259 | } | ||
1260 | } | ||
1261 | } | ||
1262 | |||
1263 | if (rdata.items != null) | ||
1264 | { | ||
1265 | foreach (InventoryItemBase i in rdata.items) | ||
1266 | { | ||
1267 | if (i.Folder == folder.ID) | ||
1268 | { | ||
1269 | formatItem(rdata, i, indent); | ||
1270 | } | ||
1271 | } | ||
1272 | } | ||
1273 | |||
1274 | // End folder item | ||
1275 | |||
1276 | rdata.writer.WriteEndElement(); | ||
1277 | } | ||
1278 | |||
1279 | /// <summary> | ||
1280 | /// This method generates XML that describes an instance of InventoryItemBase. | ||
1281 | /// </summary> | ||
1282 | /// <param name="rdata">HTTP service request work area</param> | ||
1283 | /// <param name="i">The item to be formatted</param> | ||
1284 | /// <param name="indent">Pretty print indentation</param> | ||
1285 | private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent) | ||
1286 | { | ||
1287 | Rest.Log.DebugFormat("{0} Item : {1} {2} {3} Type = {4}, AssetType = {5}", | ||
1288 | MsgId, i.ID, indent, i.Name, i.InvType, i.AssetType); | ||
1289 | |||
1290 | rdata.writer.WriteStartElement(String.Empty, "Item", String.Empty); | ||
1291 | |||
1292 | rdata.writer.WriteAttributeString("name", String.Empty, i.Name); | ||
1293 | rdata.writer.WriteAttributeString("desc", String.Empty, i.Description); | ||
1294 | rdata.writer.WriteAttributeString("uuid", String.Empty, i.ID.ToString()); | ||
1295 | rdata.writer.WriteAttributeString("folder", String.Empty, i.Folder.ToString()); | ||
1296 | rdata.writer.WriteAttributeString("owner", String.Empty, i.Owner.ToString()); | ||
1297 | rdata.writer.WriteAttributeString("creator", String.Empty, i.CreatorId); | ||
1298 | rdata.writer.WriteAttributeString("creatordata", String.Empty, i.CreatorData); | ||
1299 | rdata.writer.WriteAttributeString("creationdate", String.Empty, i.CreationDate.ToString()); | ||
1300 | rdata.writer.WriteAttributeString("invtype", String.Empty, i.InvType.ToString()); | ||
1301 | rdata.writer.WriteAttributeString("assettype", String.Empty, i.AssetType.ToString()); | ||
1302 | rdata.writer.WriteAttributeString("groupowned", String.Empty, i.GroupOwned.ToString()); | ||
1303 | rdata.writer.WriteAttributeString("groupid", String.Empty, i.GroupID.ToString()); | ||
1304 | rdata.writer.WriteAttributeString("saletype", String.Empty, i.SaleType.ToString()); | ||
1305 | rdata.writer.WriteAttributeString("saleprice", String.Empty, i.SalePrice.ToString()); | ||
1306 | rdata.writer.WriteAttributeString("flags", String.Empty, i.Flags.ToString()); | ||
1307 | |||
1308 | rdata.writer.WriteStartElement(String.Empty, "Permissions", String.Empty); | ||
1309 | rdata.writer.WriteAttributeString("current", String.Empty, i.CurrentPermissions.ToString("X")); | ||
1310 | rdata.writer.WriteAttributeString("next", String.Empty, i.NextPermissions.ToString("X")); | ||
1311 | rdata.writer.WriteAttributeString("group", String.Empty, i.GroupPermissions.ToString("X")); | ||
1312 | rdata.writer.WriteAttributeString("everyone", String.Empty, i.EveryOnePermissions.ToString("X")); | ||
1313 | rdata.writer.WriteAttributeString("base", String.Empty, i.BasePermissions.ToString("X")); | ||
1314 | rdata.writer.WriteEndElement(); | ||
1315 | |||
1316 | rdata.writer.WriteElementString("Asset", i.AssetID.ToString()); | ||
1317 | |||
1318 | rdata.writer.WriteEndElement(); | ||
1319 | } | ||
1320 | |||
1321 | /// <summary> | ||
1322 | /// This method creates a "trashcan" folder to support folder and item | ||
1323 | /// deletions by this interface. The xisting trash folder is found and | ||
1324 | /// this folder is created within it. It is called "tmp" to indicate to | ||
1325 | /// the client that it is OK to delete this folder. The REST interface | ||
1326 | /// will recreate the folder on an as-required basis. | ||
1327 | /// If the trash can cannot be created, then by implication the request | ||
1328 | /// that required it cannot be completed, and it fails accordingly. | ||
1329 | /// </summary> | ||
1330 | /// <param name=rdata>HTTP service request work area</param> | ||
1331 | private InventoryFolderBase GetTrashCan(InventoryRequestData rdata) | ||
1332 | { | ||
1333 | InventoryFolderBase TrashCan = null; | ||
1334 | |||
1335 | foreach (InventoryFolderBase f in rdata.folders) | ||
1336 | { | ||
1337 | if (f.Name == "Trash") | ||
1338 | { | ||
1339 | foreach (InventoryFolderBase t in rdata.folders) | ||
1340 | { | ||
1341 | if (t.Name == "tmp") | ||
1342 | { | ||
1343 | TrashCan = t; | ||
1344 | } | ||
1345 | } | ||
1346 | if (TrashCan == null) | ||
1347 | { | ||
1348 | TrashCan = new InventoryFolderBase(); | ||
1349 | TrashCan.Name = "tmp"; | ||
1350 | TrashCan.ID = UUID.Random(); | ||
1351 | TrashCan.Version = 1; | ||
1352 | TrashCan.Type = (short) AssetType.TrashFolder; | ||
1353 | TrashCan.ParentID = f.ID; | ||
1354 | TrashCan.Owner = f.Owner; | ||
1355 | Rest.InventoryServices.AddFolder(TrashCan); | ||
1356 | } | ||
1357 | } | ||
1358 | } | ||
1359 | |||
1360 | if (TrashCan == null) | ||
1361 | { | ||
1362 | Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); | ||
1363 | rdata.Fail(Rest.HttpStatusCodeServerError, "unable to create trash can"); | ||
1364 | } | ||
1365 | |||
1366 | return TrashCan; | ||
1367 | } | ||
1368 | |||
1369 | /// <summary> | ||
1370 | /// Make sure that an unchanged folder is not unnecessarily | ||
1371 | /// processed. | ||
1372 | /// </summary> | ||
1373 | /// <param name=newf>Folder obtained from enclosed entity</param> | ||
1374 | /// <param name=oldf>Folder obtained from the user's inventory</param> | ||
1375 | private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf) | ||
1376 | { | ||
1377 | return (newf.Name != oldf.Name | ||
1378 | || newf.ParentID != oldf.ParentID | ||
1379 | || newf.Owner != oldf.Owner | ||
1380 | || newf.Type != oldf.Type | ||
1381 | || newf.Version != oldf.Version | ||
1382 | ); | ||
1383 | } | ||
1384 | |||
1385 | /// <summary> | ||
1386 | /// Make sure that an unchanged item is not unnecessarily | ||
1387 | /// processed. | ||
1388 | /// </summary> | ||
1389 | /// <param name=newf>Item obtained from enclosed entity</param> | ||
1390 | /// <param name=oldf>Item obtained from the user's inventory</param> | ||
1391 | private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf) | ||
1392 | { | ||
1393 | return (newf.Name != oldf.Name | ||
1394 | || newf.Folder != oldf.Folder | ||
1395 | || newf.Description != oldf.Description | ||
1396 | || newf.Owner != oldf.Owner | ||
1397 | || newf.CreatorId != oldf.CreatorId | ||
1398 | || newf.AssetID != oldf.AssetID | ||
1399 | || newf.GroupID != oldf.GroupID | ||
1400 | || newf.GroupOwned != oldf.GroupOwned | ||
1401 | || newf.InvType != oldf.InvType | ||
1402 | || newf.AssetType != oldf.AssetType | ||
1403 | ); | ||
1404 | } | ||
1405 | |||
1406 | /// <summary> | ||
1407 | /// This method is called by PUT and POST to create an XmlInventoryCollection | ||
1408 | /// instance that reflects the content of the entity supplied on the request. | ||
1409 | /// Any elements in the completed collection whose UUID is zero, are | ||
1410 | /// considered to be located relative to the end-point identified int he | ||
1411 | /// URI. In this way, an entire sub-tree can be conveyed in a single REST | ||
1412 | /// PUT or POST request. | ||
1413 | /// | ||
1414 | /// A new instance of XmlInventoryCollection is created and, if the request | ||
1415 | /// has an entity, it is more completely initialized. thus, if no entity was | ||
1416 | /// provided the collection is valid, but empty. | ||
1417 | /// | ||
1418 | /// The entity is then scanned and each tag is processed to produce the | ||
1419 | /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection | ||
1420 | /// will reflect the subtree described by the entity. | ||
1421 | /// | ||
1422 | /// This is a very flexible mechanism, the entity may contain arbitrary, | ||
1423 | /// discontiguous tree fragments, or may contain single element. The caller is | ||
1424 | /// responsible for integrating this collection (and ensuring that any | ||
1425 | /// missing parent IDs are resolved). | ||
1426 | /// </summary> | ||
1427 | /// <param name=rdata>HTTP service request work area</param> | ||
1428 | internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata) | ||
1429 | { | ||
1430 | Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId); | ||
1431 | |||
1432 | XmlInventoryCollection ic = new XmlInventoryCollection(); | ||
1433 | |||
1434 | if (rdata.request.HasEntityBody) | ||
1435 | { | ||
1436 | Rest.Log.DebugFormat("{0} Entity present", MsgId); | ||
1437 | |||
1438 | ic.init(rdata); | ||
1439 | |||
1440 | try | ||
1441 | { | ||
1442 | while (ic.xml.Read()) | ||
1443 | { | ||
1444 | switch (ic.xml.NodeType) | ||
1445 | { | ||
1446 | case XmlNodeType.Element: | ||
1447 | Rest.Log.DebugFormat("{0} StartElement: <{1}>", | ||
1448 | MsgId, ic.xml.Name); | ||
1449 | |||
1450 | switch (ic.xml.Name) | ||
1451 | { | ||
1452 | case "Folder": | ||
1453 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1454 | MsgId, ic.xml.Name); | ||
1455 | CollectFolder(ic); | ||
1456 | break; | ||
1457 | case "Item": | ||
1458 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1459 | MsgId, ic.xml.Name); | ||
1460 | CollectItem(ic); | ||
1461 | break; | ||
1462 | case "Asset": | ||
1463 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1464 | MsgId, ic.xml.Name); | ||
1465 | CollectAsset(ic); | ||
1466 | break; | ||
1467 | case "Permissions": | ||
1468 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1469 | MsgId, ic.xml.Name); | ||
1470 | CollectPermissions(ic); | ||
1471 | break; | ||
1472 | default: | ||
1473 | Rest.Log.DebugFormat("{0} Ignoring {1} element", | ||
1474 | MsgId, ic.xml.Name); | ||
1475 | break; | ||
1476 | } | ||
1477 | |||
1478 | // This stinks, but the ReadElement call above not only reads | ||
1479 | // the imbedded data, but also consumes the end tag for Asset | ||
1480 | // and moves the element pointer on to the containing Item's | ||
1481 | // element-end, however, if there was a permissions element | ||
1482 | // following, it would get us to the start of that.. | ||
1483 | if (ic.xml.NodeType == XmlNodeType.EndElement && | ||
1484 | ic.xml.Name == "Item") | ||
1485 | { | ||
1486 | Validate(ic); | ||
1487 | } | ||
1488 | break; | ||
1489 | |||
1490 | case XmlNodeType.EndElement : | ||
1491 | switch (ic.xml.Name) | ||
1492 | { | ||
1493 | case "Folder": | ||
1494 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1495 | MsgId, ic.xml.Name); | ||
1496 | ic.Pop(); | ||
1497 | break; | ||
1498 | case "Item": | ||
1499 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1500 | MsgId, ic.xml.Name); | ||
1501 | Validate(ic); | ||
1502 | break; | ||
1503 | case "Asset": | ||
1504 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1505 | MsgId, ic.xml.Name); | ||
1506 | break; | ||
1507 | case "Permissions": | ||
1508 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1509 | MsgId, ic.xml.Name); | ||
1510 | break; | ||
1511 | default: | ||
1512 | Rest.Log.DebugFormat("{0} Ignoring {1} element", | ||
1513 | MsgId, ic.xml.Name); | ||
1514 | break; | ||
1515 | } | ||
1516 | break; | ||
1517 | |||
1518 | default: | ||
1519 | Rest.Log.DebugFormat("{0} Ignoring: <{1}>:<{2}>", | ||
1520 | MsgId, ic.xml.NodeType, ic.xml.Value); | ||
1521 | break; | ||
1522 | } | ||
1523 | } | ||
1524 | } | ||
1525 | catch (XmlException e) | ||
1526 | { | ||
1527 | Rest.Log.WarnFormat("{0} XML parsing error: {1}", MsgId, e.Message); | ||
1528 | throw e; | ||
1529 | } | ||
1530 | catch (Exception e) | ||
1531 | { | ||
1532 | Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message); | ||
1533 | throw e; | ||
1534 | } | ||
1535 | } | ||
1536 | else | ||
1537 | { | ||
1538 | Rest.Log.DebugFormat("{0} Entity absent", MsgId); | ||
1539 | } | ||
1540 | |||
1541 | if (Rest.DEBUG) | ||
1542 | { | ||
1543 | Rest.Log.DebugFormat("{0} Reconstituted entity", MsgId); | ||
1544 | Rest.Log.DebugFormat("{0} {1} assets", MsgId, ic.Assets.Count); | ||
1545 | Rest.Log.DebugFormat("{0} {1} folder", MsgId, ic.Folders.Count); | ||
1546 | Rest.Log.DebugFormat("{0} {1} items", MsgId, ic.Items.Count); | ||
1547 | } | ||
1548 | |||
1549 | return ic; | ||
1550 | } | ||
1551 | |||
1552 | /// <summary> | ||
1553 | /// This method creates an inventory Folder from the | ||
1554 | /// information supplied in the request's entity. | ||
1555 | /// A folder instance is created and initialized to reflect | ||
1556 | /// default values. These values are then overridden | ||
1557 | /// by information supplied in the entity. | ||
1558 | /// If context was not explicitly provided, then the | ||
1559 | /// appropriate ID values are determined. | ||
1560 | /// </summary> | ||
1561 | |||
1562 | private void CollectFolder(XmlInventoryCollection ic) | ||
1563 | { | ||
1564 | Rest.Log.DebugFormat("{0} Interpret folder element", MsgId); | ||
1565 | |||
1566 | InventoryFolderBase result = new InventoryFolderBase(); | ||
1567 | |||
1568 | // Default values | ||
1569 | |||
1570 | result.Name = String.Empty; | ||
1571 | result.ID = UUID.Zero; | ||
1572 | result.Owner = ic.UserID; | ||
1573 | result.ParentID = UUID.Zero; // Context | ||
1574 | result.Type = (short) AssetType.Folder; | ||
1575 | result.Version = 1; | ||
1576 | |||
1577 | if (ic.xml.HasAttributes) | ||
1578 | { | ||
1579 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1580 | { | ||
1581 | ic.xml.MoveToAttribute(i); | ||
1582 | switch (ic.xml.Name) | ||
1583 | { | ||
1584 | case "name": | ||
1585 | result.Name = ic.xml.Value; | ||
1586 | break; | ||
1587 | case "uuid": | ||
1588 | result.ID = new UUID(ic.xml.Value); | ||
1589 | break; | ||
1590 | case "parent": | ||
1591 | result.ParentID = new UUID(ic.xml.Value); | ||
1592 | break; | ||
1593 | case "owner": | ||
1594 | result.Owner = new UUID(ic.xml.Value); | ||
1595 | break; | ||
1596 | case "type": | ||
1597 | result.Type = Int16.Parse(ic.xml.Value); | ||
1598 | break; | ||
1599 | case "version": | ||
1600 | result.Version = UInt16.Parse(ic.xml.Value); | ||
1601 | break; | ||
1602 | default: | ||
1603 | Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", | ||
1604 | MsgId, ic.xml.Name, ic.xml.Value); | ||
1605 | ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>", | ||
1606 | ic.xml.Name)); | ||
1607 | break; | ||
1608 | } | ||
1609 | } | ||
1610 | } | ||
1611 | |||
1612 | ic.xml.MoveToElement(); | ||
1613 | |||
1614 | // The client is relying upon the reconstitution process | ||
1615 | // to determine the parent's UUID based upon context. This | ||
1616 | // is necessary where a new folder may have been | ||
1617 | // introduced. | ||
1618 | |||
1619 | if (result.ParentID == UUID.Zero) | ||
1620 | { | ||
1621 | result.ParentID = ic.Parent(); | ||
1622 | } | ||
1623 | else | ||
1624 | { | ||
1625 | bool found = false; | ||
1626 | |||
1627 | foreach (InventoryFolderBase parent in ic.rdata.folders) | ||
1628 | { | ||
1629 | if (parent.ID == result.ParentID) | ||
1630 | { | ||
1631 | found = true; | ||
1632 | break; | ||
1633 | } | ||
1634 | } | ||
1635 | |||
1636 | if (!found) | ||
1637 | { | ||
1638 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", | ||
1639 | MsgId, ic.Item.Folder, result.ID); | ||
1640 | ic.Fail(Rest.HttpStatusCodeBadRequest, "invalid parent"); | ||
1641 | } | ||
1642 | } | ||
1643 | |||
1644 | // This is a new folder, so no existing UUID is available | ||
1645 | // or appropriate | ||
1646 | |||
1647 | if (result.ID == UUID.Zero) | ||
1648 | { | ||
1649 | result.ID = UUID.Random(); | ||
1650 | } | ||
1651 | |||
1652 | // Treat this as a new context. Any other information is | ||
1653 | // obsolete as a consequence. | ||
1654 | |||
1655 | ic.Push(result); | ||
1656 | } | ||
1657 | |||
1658 | /// <summary> | ||
1659 | /// This method is called to handle the construction of an Item | ||
1660 | /// instance from the supplied request entity. It is called | ||
1661 | /// whenever an Item start tag is detected. | ||
1662 | /// An instance of an Item is created and initialized to default | ||
1663 | /// values. These values are then overridden from values supplied | ||
1664 | /// as attributes to the Item element. | ||
1665 | /// This item is then stored in the XmlInventoryCollection and | ||
1666 | /// will be verified by Validate. | ||
1667 | /// All context is reset whenever the effective folder changes | ||
1668 | /// or an item is successfully validated. | ||
1669 | /// </summary> | ||
1670 | private void CollectItem(XmlInventoryCollection ic) | ||
1671 | { | ||
1672 | Rest.Log.DebugFormat("{0} Interpret item element", MsgId); | ||
1673 | |||
1674 | InventoryItemBase result = new InventoryItemBase(); | ||
1675 | |||
1676 | result.Name = String.Empty; | ||
1677 | result.Description = String.Empty; | ||
1678 | result.ID = UUID.Zero; | ||
1679 | result.Folder = UUID.Zero; | ||
1680 | result.Owner = ic.UserID; | ||
1681 | result.CreatorId = ic.UserID.ToString(); | ||
1682 | result.AssetID = UUID.Zero; | ||
1683 | result.GroupID = UUID.Zero; | ||
1684 | result.GroupOwned = false; | ||
1685 | result.InvType = (int) InventoryType.Unknown; | ||
1686 | result.AssetType = (int) AssetType.Unknown; | ||
1687 | |||
1688 | if (ic.xml.HasAttributes) | ||
1689 | { | ||
1690 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1691 | { | ||
1692 | ic.xml.MoveToAttribute(i); | ||
1693 | |||
1694 | switch (ic.xml.Name) | ||
1695 | { | ||
1696 | case "name": | ||
1697 | result.Name = ic.xml.Value; | ||
1698 | break; | ||
1699 | case "desc": | ||
1700 | result.Description = ic.xml.Value; | ||
1701 | break; | ||
1702 | case "uuid": | ||
1703 | result.ID = new UUID(ic.xml.Value); | ||
1704 | break; | ||
1705 | case "folder": | ||
1706 | result.Folder = new UUID(ic.xml.Value); | ||
1707 | break; | ||
1708 | case "owner": | ||
1709 | result.Owner = new UUID(ic.xml.Value); | ||
1710 | break; | ||
1711 | case "invtype": | ||
1712 | result.InvType = Int32.Parse(ic.xml.Value); | ||
1713 | break; | ||
1714 | case "creator": | ||
1715 | result.CreatorId = ic.xml.Value; | ||
1716 | break; | ||
1717 | case "assettype": | ||
1718 | result.AssetType = Int32.Parse(ic.xml.Value); | ||
1719 | break; | ||
1720 | case "groupowned": | ||
1721 | result.GroupOwned = Boolean.Parse(ic.xml.Value); | ||
1722 | break; | ||
1723 | case "groupid": | ||
1724 | result.GroupID = new UUID(ic.xml.Value); | ||
1725 | break; | ||
1726 | case "flags": | ||
1727 | result.Flags = UInt32.Parse(ic.xml.Value); | ||
1728 | break; | ||
1729 | case "creationdate": | ||
1730 | result.CreationDate = Int32.Parse(ic.xml.Value); | ||
1731 | break; | ||
1732 | case "saletype": | ||
1733 | result.SaleType = Byte.Parse(ic.xml.Value); | ||
1734 | break; | ||
1735 | case "saleprice": | ||
1736 | result.SalePrice = Int32.Parse(ic.xml.Value); | ||
1737 | break; | ||
1738 | |||
1739 | default: | ||
1740 | Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", | ||
1741 | MsgId, ic.xml.Name, ic.xml.Value); | ||
1742 | ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute", | ||
1743 | ic.xml.Name)); | ||
1744 | break; | ||
1745 | } | ||
1746 | } | ||
1747 | } | ||
1748 | |||
1749 | ic.xml.MoveToElement(); | ||
1750 | |||
1751 | ic.Push(result); | ||
1752 | } | ||
1753 | |||
1754 | /// <summary> | ||
1755 | /// This method assembles an asset instance from the | ||
1756 | /// information supplied in the request's entity. It is | ||
1757 | /// called as a result of detecting a start tag for a | ||
1758 | /// type of Asset. | ||
1759 | /// The information is collected locally, and an asset | ||
1760 | /// instance is created only if the basic XML parsing | ||
1761 | /// completes successfully. | ||
1762 | /// Default values for all parts of the asset are | ||
1763 | /// established before overriding them from the supplied | ||
1764 | /// XML. | ||
1765 | /// If an asset has inline=true as an attribute, then | ||
1766 | /// the element contains the data representing the | ||
1767 | /// asset. This is saved as the data component. | ||
1768 | /// inline=false means that the element's payload is | ||
1769 | /// simply the UUID of the asset referenced by the | ||
1770 | /// item being constructed. | ||
1771 | /// An asset, if created is stored in the | ||
1772 | /// XmlInventoryCollection | ||
1773 | /// </summary> | ||
1774 | private void CollectAsset(XmlInventoryCollection ic) | ||
1775 | { | ||
1776 | Rest.Log.DebugFormat("{0} Interpret asset element", MsgId); | ||
1777 | |||
1778 | string name = String.Empty; | ||
1779 | string desc = String.Empty; | ||
1780 | sbyte type = (sbyte) AssetType.Unknown; | ||
1781 | bool temp = false; | ||
1782 | bool local = false; | ||
1783 | |||
1784 | // This is not a persistent attribute | ||
1785 | bool inline = false; | ||
1786 | |||
1787 | UUID uuid = UUID.Zero; | ||
1788 | |||
1789 | // Attribute is optional | ||
1790 | if (ic.xml.HasAttributes) | ||
1791 | { | ||
1792 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1793 | { | ||
1794 | ic.xml.MoveToAttribute(i); | ||
1795 | switch (ic.xml.Name) | ||
1796 | { | ||
1797 | case "name" : | ||
1798 | name = ic.xml.Value; | ||
1799 | break; | ||
1800 | |||
1801 | case "type" : | ||
1802 | type = SByte.Parse(ic.xml.Value); | ||
1803 | break; | ||
1804 | |||
1805 | case "description" : | ||
1806 | desc = ic.xml.Value; | ||
1807 | break; | ||
1808 | |||
1809 | case "temporary" : | ||
1810 | temp = Boolean.Parse(ic.xml.Value); | ||
1811 | break; | ||
1812 | |||
1813 | case "uuid" : | ||
1814 | uuid = new UUID(ic.xml.Value); | ||
1815 | break; | ||
1816 | |||
1817 | case "inline" : | ||
1818 | inline = Boolean.Parse(ic.xml.Value); | ||
1819 | break; | ||
1820 | |||
1821 | case "local" : | ||
1822 | local = Boolean.Parse(ic.xml.Value); | ||
1823 | break; | ||
1824 | |||
1825 | default : | ||
1826 | Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}", | ||
1827 | MsgId, ic.xml.Name, ic.xml.Value); | ||
1828 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1829 | String.Format("unrecognized attribute <{0}>", ic.xml.Name)); | ||
1830 | break; | ||
1831 | } | ||
1832 | } | ||
1833 | } | ||
1834 | |||
1835 | ic.xml.MoveToElement(); | ||
1836 | |||
1837 | // If this is a reference to an existing asset, just store the | ||
1838 | // asset ID into the item. | ||
1839 | |||
1840 | if (!inline) | ||
1841 | { | ||
1842 | if (ic.Item != null) | ||
1843 | { | ||
1844 | ic.Item.AssetID = new UUID(ic.xml.ReadElementContentAsString()); | ||
1845 | Rest.Log.DebugFormat("{0} Asset ID supplied: {1}", MsgId, ic.Item.AssetID); | ||
1846 | } | ||
1847 | else | ||
1848 | { | ||
1849 | Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); | ||
1850 | ic.Fail(Rest.HttpStatusCodeBadRequest, "no context for asset"); | ||
1851 | } | ||
1852 | } | ||
1853 | |||
1854 | // Otherwise, generate an asset ID, store that into the item, and | ||
1855 | // create an entry in the asset list for the inlined asset. But | ||
1856 | // only if the size is non-zero. | ||
1857 | |||
1858 | else | ||
1859 | { | ||
1860 | AssetBase asset = null; | ||
1861 | string b64string = null; | ||
1862 | |||
1863 | // Generate a UUID if none were given, and generally none should | ||
1864 | // be. Ever. | ||
1865 | |||
1866 | if (uuid == UUID.Zero) | ||
1867 | { | ||
1868 | uuid = UUID.Random(); | ||
1869 | } | ||
1870 | |||
1871 | // Create AssetBase entity to hold the inlined asset | ||
1872 | |||
1873 | asset = new AssetBase(uuid, name, type, UUID.Zero.ToString()); | ||
1874 | |||
1875 | asset.Description = desc; | ||
1876 | asset.Local = local; | ||
1877 | asset.Temporary = temp; | ||
1878 | |||
1879 | b64string = ic.xml.ReadElementContentAsString(); | ||
1880 | |||
1881 | Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length); | ||
1882 | Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId, | ||
1883 | b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length)); | ||
1884 | |||
1885 | asset.Data = Convert.FromBase64String(b64string); | ||
1886 | |||
1887 | // Ensure the asset always has some kind of data component | ||
1888 | |||
1889 | if (asset.Data == null) | ||
1890 | { | ||
1891 | asset.Data = new byte[1]; | ||
1892 | } | ||
1893 | |||
1894 | // If this is in the context of an item, establish | ||
1895 | // a link with the item in context. | ||
1896 | |||
1897 | if (ic.Item != null && ic.Item.AssetID == UUID.Zero) | ||
1898 | { | ||
1899 | ic.Item.AssetID = uuid; | ||
1900 | } | ||
1901 | |||
1902 | ic.Push(asset); | ||
1903 | } | ||
1904 | } | ||
1905 | |||
1906 | /// <summary> | ||
1907 | /// Store any permissions information provided by the request. | ||
1908 | /// This overrides the default permissions set when the | ||
1909 | /// XmlInventoryCollection object was created. | ||
1910 | /// </summary> | ||
1911 | private void CollectPermissions(XmlInventoryCollection ic) | ||
1912 | { | ||
1913 | if (ic.xml.HasAttributes) | ||
1914 | { | ||
1915 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1916 | { | ||
1917 | ic.xml.MoveToAttribute(i); | ||
1918 | switch (ic.xml.Name) | ||
1919 | { | ||
1920 | case "current": | ||
1921 | ic.CurrentPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1922 | break; | ||
1923 | case "next": | ||
1924 | ic.NextPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1925 | break; | ||
1926 | case "group": | ||
1927 | ic.GroupPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1928 | break; | ||
1929 | case "everyone": | ||
1930 | ic.EveryOnePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1931 | break; | ||
1932 | case "base": | ||
1933 | ic.BasePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); | ||
1934 | break; | ||
1935 | default: | ||
1936 | Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}", | ||
1937 | MsgId,ic.xml.Name, ic.xml.Value); | ||
1938 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1939 | String.Format("invalid attribute <{0}>", ic.xml.Name)); | ||
1940 | break; | ||
1941 | } | ||
1942 | } | ||
1943 | } | ||
1944 | |||
1945 | ic.xml.MoveToElement(); | ||
1946 | } | ||
1947 | |||
1948 | /// <summary> | ||
1949 | /// This method is called whenever an Item has been successfully | ||
1950 | /// reconstituted from the request's entity. | ||
1951 | /// It uses the information curren tin the XmlInventoryCollection | ||
1952 | /// to complete the item's specification, including any implied | ||
1953 | /// context and asset associations. | ||
1954 | /// It fails the request if any necessary item or asset information | ||
1955 | /// is missing. | ||
1956 | /// </summary> | ||
1957 | |||
1958 | private void Validate(XmlInventoryCollection ic) | ||
1959 | { | ||
1960 | // There really should be an item present if we've | ||
1961 | // called validate. So fail if there is not. | ||
1962 | |||
1963 | if (ic.Item == null) | ||
1964 | { | ||
1965 | Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); | ||
1966 | ic.Fail(Rest.HttpStatusCodeBadRequest, "request parse error"); | ||
1967 | } | ||
1968 | |||
1969 | // Every item is required to have a name (via REST anyway) | ||
1970 | |||
1971 | if (ic.Item.Name == String.Empty) | ||
1972 | { | ||
1973 | Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); | ||
1974 | ic.Fail(Rest.HttpStatusCodeBadRequest, "item name required"); | ||
1975 | } | ||
1976 | |||
1977 | // An item MUST have an asset ID. AssetID should never be zero | ||
1978 | // here. It should always get set from the information stored | ||
1979 | // when the Asset element was processed. | ||
1980 | |||
1981 | if (ic.Item.AssetID == UUID.Zero) | ||
1982 | { | ||
1983 | Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); | ||
1984 | Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); | ||
1985 | ic.Fail(Rest.HttpStatusCodeBadRequest, "asset information required"); | ||
1986 | } | ||
1987 | |||
1988 | // If the item is new, then assign it an ID | ||
1989 | |||
1990 | if (ic.Item.ID == UUID.Zero) | ||
1991 | { | ||
1992 | ic.Item.ID = UUID.Random(); | ||
1993 | } | ||
1994 | |||
1995 | // If the context is being implied, obtain the current | ||
1996 | // folder item's ID. If it was specified explicitly, make | ||
1997 | // sure that theparent folder exists. | ||
1998 | |||
1999 | if (ic.Item.Folder == UUID.Zero) | ||
2000 | { | ||
2001 | ic.Item.Folder = ic.Parent(); | ||
2002 | } | ||
2003 | else | ||
2004 | { | ||
2005 | bool found = false; | ||
2006 | |||
2007 | foreach (InventoryFolderBase parent in ic.rdata.folders) | ||
2008 | { | ||
2009 | if (parent.ID == ic.Item.Folder) | ||
2010 | { | ||
2011 | found = true; | ||
2012 | break; | ||
2013 | } | ||
2014 | } | ||
2015 | |||
2016 | if (!found) | ||
2017 | { | ||
2018 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", | ||
2019 | MsgId, ic.Item.Folder, ic.Item.ID); | ||
2020 | ic.Fail(Rest.HttpStatusCodeBadRequest, "parent information required"); | ||
2021 | } | ||
2022 | } | ||
2023 | |||
2024 | // If this is an inline asset being constructed in the context | ||
2025 | // of a new Item, then use the itm's name here too. | ||
2026 | |||
2027 | if (ic.Asset != null) | ||
2028 | { | ||
2029 | if (ic.Asset.Name == String.Empty) | ||
2030 | ic.Asset.Name = ic.Item.Name; | ||
2031 | if (ic.Asset.Description == String.Empty) | ||
2032 | ic.Asset.Description = ic.Item.Description; | ||
2033 | } | ||
2034 | |||
2035 | // Assign permissions | ||
2036 | |||
2037 | ic.Item.CurrentPermissions = ic.CurrentPermissions; | ||
2038 | ic.Item.EveryOnePermissions = ic.EveryOnePermissions; | ||
2039 | ic.Item.BasePermissions = ic.BasePermissions; | ||
2040 | ic.Item.GroupPermissions = ic.GroupPermissions; | ||
2041 | ic.Item.NextPermissions = ic.NextPermissions; | ||
2042 | |||
2043 | // If no type was specified for this item, we can attempt to | ||
2044 | // infer something from the file type maybe. This is NOT as | ||
2045 | // good as having type be specified in the XML. | ||
2046 | |||
2047 | if (ic.Item.AssetType == (int) AssetType.Unknown || | ||
2048 | ic.Item.InvType == (int) InventoryType.Unknown) | ||
2049 | { | ||
2050 | Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId); | ||
2051 | |||
2052 | string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD); | ||
2053 | |||
2054 | if (Rest.DEBUG) | ||
2055 | { | ||
2056 | for (int i = 0; i < parts.Length; i++) | ||
2057 | { | ||
2058 | Rest.Log.DebugFormat("{0} Name part {1} : {2}", | ||
2059 | MsgId, i, parts[i]); | ||
2060 | } | ||
2061 | } | ||
2062 | |||
2063 | // If the associated item name is multi-part, then maybe | ||
2064 | // the last part will indicate the item type - if we're | ||
2065 | // lucky. | ||
2066 | |||
2067 | if (parts.Length > 1) | ||
2068 | { | ||
2069 | Rest.Log.DebugFormat("{0} File type is {1}", | ||
2070 | MsgId, parts[parts.Length - 1]); | ||
2071 | switch (parts[parts.Length - 1]) | ||
2072 | { | ||
2073 | case "jpeg2000" : | ||
2074 | case "jpeg-2000" : | ||
2075 | case "jpg2000" : | ||
2076 | case "jpg-2000" : | ||
2077 | Rest.Log.DebugFormat("{0} Type {1} inferred", | ||
2078 | MsgId, parts[parts.Length-1]); | ||
2079 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
2080 | ic.Item.AssetType = (int) AssetType.ImageJPEG; | ||
2081 | if (ic.Item.InvType == (int) InventoryType.Unknown) | ||
2082 | ic.Item.InvType = (int) InventoryType.Texture; | ||
2083 | break; | ||
2084 | case "jpg" : | ||
2085 | case "jpeg" : | ||
2086 | Rest.Log.DebugFormat("{0} Type {1} inferred", | ||
2087 | MsgId, parts[parts.Length - 1]); | ||
2088 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
2089 | ic.Item.AssetType = (int) AssetType.ImageJPEG; | ||
2090 | if (ic.Item.InvType == (int) InventoryType.Unknown) | ||
2091 | ic.Item.InvType = (int) InventoryType.Texture; | ||
2092 | break; | ||
2093 | case "tga" : | ||
2094 | if (parts[parts.Length - 2].IndexOf("_texture") != -1) | ||
2095 | { | ||
2096 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
2097 | ic.Item.AssetType = (int) AssetType.TextureTGA; | ||
2098 | if (ic.Item.InvType == (int) AssetType.Unknown) | ||
2099 | ic.Item.InvType = (int) InventoryType.Texture; | ||
2100 | } | ||
2101 | else | ||
2102 | { | ||
2103 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
2104 | ic.Item.AssetType = (int) AssetType.ImageTGA; | ||
2105 | if (ic.Item.InvType == (int) InventoryType.Unknown) | ||
2106 | ic.Item.InvType = (int) InventoryType.Snapshot; | ||
2107 | } | ||
2108 | break; | ||
2109 | default : | ||
2110 | Rest.Log.DebugFormat("{0} Asset/Inventory type could not be inferred for {1}", | ||
2111 | MsgId,ic.Item.Name); | ||
2112 | break; | ||
2113 | } | ||
2114 | } | ||
2115 | } | ||
2116 | |||
2117 | /// If this is a TGA remember the fact | ||
2118 | |||
2119 | if (ic.Item.AssetType == (int) AssetType.TextureTGA || | ||
2120 | ic.Item.AssetType == (int) AssetType.ImageTGA) | ||
2121 | { | ||
2122 | Bitmap temp; | ||
2123 | Stream tgadata = new MemoryStream(ic.Asset.Data); | ||
2124 | |||
2125 | temp = LoadTGAClass.LoadTGA(tgadata); | ||
2126 | try | ||
2127 | { | ||
2128 | ic.Asset.Data = OpenJPEG.EncodeFromImage(temp, true); | ||
2129 | } | ||
2130 | catch (DllNotFoundException) | ||
2131 | { | ||
2132 | Rest.Log.ErrorFormat("OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", ic.Item.Name); | ||
2133 | ic.Asset.Data = new Byte[0]; | ||
2134 | } | ||
2135 | catch (IndexOutOfRangeException) | ||
2136 | { | ||
2137 | Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name); | ||
2138 | ic.Asset.Data = new Byte[0]; | ||
2139 | } | ||
2140 | catch (Exception) | ||
2141 | { | ||
2142 | Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name); | ||
2143 | ic.Asset.Data = new Byte[0]; | ||
2144 | } | ||
2145 | } | ||
2146 | |||
2147 | ic.reset(); | ||
2148 | } | ||
2149 | |||
2150 | #region Inventory RequestData extension | ||
2151 | |||
2152 | internal class InventoryRequestData : RequestData | ||
2153 | { | ||
2154 | /// <summary> | ||
2155 | /// These are the inventory specific request/response state | ||
2156 | /// extensions. | ||
2157 | /// </summary> | ||
2158 | |||
2159 | internal UUID uuid = UUID.Zero; | ||
2160 | internal bool HaveInventory = false; | ||
2161 | internal ICollection<InventoryFolderImpl> folders = null; | ||
2162 | internal ICollection<InventoryItemBase> items = null; | ||
2163 | internal UserProfileData userProfile = null; | ||
2164 | internal InventoryFolderBase root = null; | ||
2165 | internal bool timeout = false; | ||
2166 | internal Timer watchDog = new Timer(); | ||
2167 | |||
2168 | internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
2169 | : base(request, response, prefix) | ||
2170 | { | ||
2171 | } | ||
2172 | |||
2173 | internal void startWD(double interval) | ||
2174 | { | ||
2175 | Rest.Log.DebugFormat("{0} Setting watchdog", MsgId); | ||
2176 | watchDog.Elapsed += new ElapsedEventHandler(OnTimeOut); | ||
2177 | watchDog.Interval = interval; | ||
2178 | watchDog.AutoReset = false; | ||
2179 | watchDog.Enabled = true; | ||
2180 | lock (watchDog) | ||
2181 | watchDog.Start(); | ||
2182 | |||
2183 | } | ||
2184 | |||
2185 | internal void stopWD() | ||
2186 | { | ||
2187 | Rest.Log.DebugFormat("{0} Reset watchdog", MsgId); | ||
2188 | lock (watchDog) | ||
2189 | watchDog.Stop(); | ||
2190 | } | ||
2191 | |||
2192 | /// <summary> | ||
2193 | /// This is the callback method required by the inventory watchdog. The | ||
2194 | /// requestor issues an inventory request and then blocks until the | ||
2195 | /// request completes, or this method signals the monitor. | ||
2196 | /// </summary> | ||
2197 | |||
2198 | private void OnTimeOut(object sender, ElapsedEventArgs args) | ||
2199 | { | ||
2200 | Rest.Log.DebugFormat("{0} Asynchronous inventory update timed-out", MsgId); | ||
2201 | // InventoryRequestData rdata = (InventoryRequestData) sender; | ||
2202 | lock (this) | ||
2203 | { | ||
2204 | this.folders = null; | ||
2205 | this.items = null; | ||
2206 | this.HaveInventory = false; | ||
2207 | this.timeout = true; | ||
2208 | Monitor.Pulse(this); | ||
2209 | } | ||
2210 | } | ||
2211 | |||
2212 | /// <summary> | ||
2213 | /// This is the callback method required by inventory services. The | ||
2214 | /// requestor issues an inventory request and then blocks until this | ||
2215 | /// method signals the monitor. | ||
2216 | /// </summary> | ||
2217 | |||
2218 | internal void GetUserInventory(ICollection<InventoryFolderImpl> folders, ICollection<InventoryItemBase> items) | ||
2219 | { | ||
2220 | Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId); | ||
2221 | lock (this) | ||
2222 | { | ||
2223 | if (watchDog.Enabled) | ||
2224 | { | ||
2225 | this.stopWD(); | ||
2226 | } | ||
2227 | this.folders = folders; | ||
2228 | this.items = items; | ||
2229 | this.HaveInventory = true; | ||
2230 | this.timeout = false; | ||
2231 | Monitor.Pulse(this); | ||
2232 | } | ||
2233 | } | ||
2234 | } | ||
2235 | |||
2236 | #endregion Inventory RequestData extension | ||
2237 | |||
2238 | /// <summary> | ||
2239 | /// This class is used to record and manage the hierarchy | ||
2240 | /// constructed from the entity supplied in the request for | ||
2241 | /// PUT and POST. | ||
2242 | /// </summary> | ||
2243 | |||
2244 | internal class XmlInventoryCollection : InventoryCollection | ||
2245 | { | ||
2246 | internal InventoryRequestData rdata; | ||
2247 | private Stack<InventoryFolderBase> stk; | ||
2248 | |||
2249 | internal List<AssetBase> Assets; | ||
2250 | |||
2251 | internal InventoryItemBase Item; | ||
2252 | internal AssetBase Asset; | ||
2253 | internal XmlReader xml; | ||
2254 | |||
2255 | internal /*static*/ const uint DefaultCurrent = 0x7FFFFFFF; | ||
2256 | internal /*static*/ const uint DefaultNext = 0x82000; | ||
2257 | internal /*static*/ const uint DefaultBase = 0x7FFFFFFF; | ||
2258 | internal /*static*/ const uint DefaultEveryOne = 0x0; | ||
2259 | internal /*static*/ const uint DefaultGroup = 0x0; | ||
2260 | |||
2261 | internal uint CurrentPermissions = 0x00; | ||
2262 | internal uint NextPermissions = 0x00; | ||
2263 | internal uint BasePermissions = 0x00; | ||
2264 | internal uint EveryOnePermissions = 0x00; | ||
2265 | internal uint GroupPermissions = 0x00; | ||
2266 | |||
2267 | internal XmlInventoryCollection() | ||
2268 | { | ||
2269 | Folders = new List<InventoryFolderBase>(); | ||
2270 | Items = new List<InventoryItemBase>(); | ||
2271 | Assets = new List<AssetBase>(); | ||
2272 | } | ||
2273 | |||
2274 | internal void init(InventoryRequestData p_rdata) | ||
2275 | { | ||
2276 | rdata = p_rdata; | ||
2277 | UserID = rdata.uuid; | ||
2278 | stk = new Stack<InventoryFolderBase>(); | ||
2279 | rdata.initXmlReader(); | ||
2280 | xml = rdata.reader; | ||
2281 | initPermissions(); | ||
2282 | } | ||
2283 | |||
2284 | internal void initPermissions() | ||
2285 | { | ||
2286 | CurrentPermissions = DefaultCurrent; | ||
2287 | NextPermissions = DefaultNext; | ||
2288 | BasePermissions = DefaultBase; | ||
2289 | GroupPermissions = DefaultGroup; | ||
2290 | EveryOnePermissions = DefaultEveryOne; | ||
2291 | } | ||
2292 | |||
2293 | internal UUID Parent() | ||
2294 | { | ||
2295 | if (stk.Count != 0) | ||
2296 | { | ||
2297 | return stk.Peek().ID; | ||
2298 | } | ||
2299 | else | ||
2300 | { | ||
2301 | return UUID.Zero; | ||
2302 | } | ||
2303 | } | ||
2304 | |||
2305 | internal void Push(InventoryFolderBase folder) | ||
2306 | { | ||
2307 | stk.Push(folder); | ||
2308 | Folders.Add(folder); | ||
2309 | reset(); | ||
2310 | } | ||
2311 | |||
2312 | internal void Push(InventoryItemBase item) | ||
2313 | { | ||
2314 | Item = item; | ||
2315 | Items.Add(item); | ||
2316 | } | ||
2317 | |||
2318 | internal void Push(AssetBase asset) | ||
2319 | { | ||
2320 | Asset = asset; | ||
2321 | Assets.Add(asset); | ||
2322 | } | ||
2323 | |||
2324 | internal void Pop() | ||
2325 | { | ||
2326 | stk.Pop(); | ||
2327 | reset(); | ||
2328 | } | ||
2329 | |||
2330 | internal void reset() | ||
2331 | { | ||
2332 | Item = null; | ||
2333 | Asset = null; | ||
2334 | initPermissions(); | ||
2335 | } | ||
2336 | |||
2337 | internal void Fail(int code, string addendum) | ||
2338 | { | ||
2339 | rdata.Fail(code, addendum); | ||
2340 | } | ||
2341 | } | ||
2342 | } | ||
2343 | } | ||