aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs2343
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
28using System;
29using System.Collections.Generic;
30using System.Drawing;
31using System.Globalization;
32using System.IO;
33using System.Threading;
34using System.Timers;
35using System.Xml;
36using OpenMetaverse;
37using OpenMetaverse.Imaging;
38using OpenSim.Framework;
39
40using OpenSim.Framework.Servers;
41using OpenSim.Framework.Servers.HttpServer;
42using Timer=System.Timers.Timer;
43
44namespace 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}