From: Alan Webb <alan_webb@us.ibm.com>

cleanups and assorted fixes to REST inventory, asset, and appearance
services.
0.6.0-stable
Dr Scofield 2008-10-20 18:07:06 +00:00
parent 1fc6872f20
commit 12042cdc2b
8 changed files with 160 additions and 65 deletions

View File

@ -42,11 +42,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path); public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path);
/// <summary> /// <summary>
/// This interface represents the boundary between the general purpose /// This interface exports the generic plugin-handling services
/// REST plugin handling, and the functionally specific handlers. The /// available to each loaded REST services module (IRest implementation)
/// handler knows only to initialize and terminate all such handlers
/// that it finds. Implementing this interface identifies the class as
/// a REST handler implementation.
/// </summary> /// </summary>
internal interface IRestHandler internal interface IRestHandler

View File

@ -167,7 +167,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
// //
internal bool authenticated = false; internal bool authenticated = false;
internal string scheme = null; internal string scheme = Rest.Scheme;
internal string realm = Rest.Realm; internal string realm = Rest.Realm;
internal string domain = null; internal string domain = null;
internal string nonce = null; internal string nonce = null;
@ -287,11 +287,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
request = p_request; request = p_request;
response = p_response; response = p_response;
qprefix = p_qprefix; qprefix = p_qprefix;
sbuilder.Length = 0; sbuilder.Length = 0;
encoding = request.ContentEncoding; encoding = request.ContentEncoding;
if (encoding == null) if (encoding == null)
{ {
encoding = Rest.Encoding; encoding = Rest.Encoding;
@ -448,9 +449,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (realm != null) if (realm != null)
{ {
sbuilder.Append(" realm=\""); sbuilder.Append(" realm=");
sbuilder.Append(Rest.CS_DQUOTE);
sbuilder.Append(realm); sbuilder.Append(realm);
sbuilder.Append("\""); sbuilder.Append(Rest.CS_DQUOTE);
} }
AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
} }
@ -677,7 +679,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
UserProfileData udata = Rest.UserServices.GetUserProfile(first, last); UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
// If we don;t recognize the user id, perhaps it is god? // If we don't recognize the user id, perhaps it is god?
if (udata == null) if (udata == null)
return pass == Rest.GodKey; return pass == Rest.GodKey;
@ -800,6 +802,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (!authparms.ContainsKey("cnonce")) if (!authparms.ContainsKey("cnonce"))
{ {
Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId); Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break; break;
} }
@ -808,6 +811,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (!authparms.TryGetValue("nc", out nck) || nck == null) if (!authparms.TryGetValue("nc", out nck) || nck == null)
{ {
Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break; break;
} }
@ -820,6 +824,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck)) if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck))
{ {
Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break; break;
} }
cntable[nonce] = nck; cntable[nonce] = nck;
@ -840,11 +845,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (authparms.ContainsKey("cnonce")) if (authparms.ContainsKey("cnonce"))
{ {
Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId); Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break; break;
} }
if (authparms.ContainsKey("nc")) if (authparms.ContainsKey("nc"))
{ {
Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId); Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
Fail(Rest.HttpStatusCodeBadRequest);
break; break;
} }
} }
@ -857,6 +864,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
while (false); while (false);
} }
else
Fail(Rest.HttpStatusCodeBadRequest);
} }

View File

@ -45,6 +45,21 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal static bool DEBUG = Log.IsDebugEnabled; internal static bool DEBUG = Log.IsDebugEnabled;
/// <summary>
/// Supported authentication schemes
/// </summary>
public const string AS_BASIC = "Basic"; // simple user/password verification
public const string AS_DIGEST = "Digest"; // password safe authentication
/// Supported Digest algorithms
public const string Digest_MD5 = "MD5"; // assumed default if omitted
public const string Digest_MD5Sess = "MD5-sess"; // session-span - not good for REST?
public const string Qop_Auth = "auth"; // authentication only
public const string Qop_Int = "auth-int"; // TODO
/// <summary> /// <summary>
/// These values have a single value for the whole /// These values have a single value for the whole
/// domain and lifetime of the plugin handler. We /// domain and lifetime of the plugin handler. We
@ -67,9 +82,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal static bool Secure = true; internal static bool Secure = true;
internal static bool ExtendedEscape = true; internal static bool ExtendedEscape = true;
internal static bool DumpAsset = false; internal static bool DumpAsset = false;
internal static bool Fill = true; internal static bool Fill = false;
internal static bool FlushEnabled = true; internal static bool FlushEnabled = true;
internal static string Realm = "REST"; internal static string Realm = "OpenSim REST";
internal static string Scheme = AS_BASIC;
internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4
/// <summary> /// <summary>
@ -383,21 +399,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
public const string HttpHeaderWarning = "Warning"; public const string HttpHeaderWarning = "Warning";
public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate"; public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate";
/// <summary>
/// Supported authentication schemes
/// </summary>
public const string AS_BASIC = "Basic";
public const string AS_DIGEST = "Digest";
/// Supported Digest algorithms
public const string Digest_MD5 = "MD5"; // assumed default if omitted
public const string Digest_MD5Sess = "MD5-sess";
public const string Qop_Auth = "auth";
public const string Qop_Int = "auth-int";
/// Utility routines /// Utility routines
public static string StringToBase64(string str) public static string StringToBase64(string str)

View File

@ -63,6 +63,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId); Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// This is better than a null reference.
if (Rest.AvatarServices == null)
throw new Exception(String.Format("{0} OpenSim inventory services are not available",
MsgId));
if (Rest.UserServices == null)
throw new Exception(String.Format("{0} OpenSim user profile services are not available",
MsgId));
// If a relative path was specified for the handler's domain, // If a relative path was specified for the handler's domain,
// add the standard prefix to make it absolute, e.g. /admin // add the standard prefix to make it absolute, e.g. /admin
@ -170,9 +180,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
try try
{ {
// digest scheme seems borked: disable it for the time
// being
rdata.scheme = Rest.AS_BASIC;
if (!rdata.IsAuthenticated) if (!rdata.IsAuthenticated)
{ {
rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
@ -731,7 +738,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (asset != UUID.Zero) if (asset != UUID.Zero)
{ {
rdata.writer.WriteAttributeString("Item",asset.ToString()); rdata.writer.WriteAttributeString("Asset",asset.ToString());
} }
rdata.writer.WriteEndElement(); rdata.writer.WriteEndElement();
} }

View File

@ -52,6 +52,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); Rest.Log.InfoFormat("{0} Asset services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// This is better than a null reference.
if (Rest.AssetServices == null)
throw new Exception(String.Format("{0} OpenSim asset services are not available",
MsgId));
// If the handler specifies a relative path for its domain // If the handler specifies a relative path for its domain
// then we must add the standard absolute prefix, e.g. /admin // then we must add the standard absolute prefix, e.g. /admin
@ -130,9 +136,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
try try
{ {
// digest scheme seems borked: disable it for the time
// being
rdata.scheme = Rest.AS_BASIC;
if (!rdata.IsAuthenticated) if (!rdata.IsAuthenticated)
{ {
rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated")); rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));

View File

@ -239,14 +239,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Prefix = Prefix; Rest.Prefix = Prefix;
Rest.GodKey = GodKey; Rest.GodKey = GodKey;
Rest.Authenticate = Rest.Config.GetBoolean("authenticate",true); Rest.Authenticate = Rest.Config.GetBoolean("authenticate", Rest.Authenticate);
Rest.Secure = Rest.Config.GetBoolean("secured",true); Rest.Scheme = Rest.Config.GetString("auth-scheme", Rest.Scheme);
Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true); Rest.Secure = Rest.Config.GetBoolean("secured", Rest.Secure);
Rest.Realm = Rest.Config.GetString("realm","OpenSim REST"); Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape", Rest.ExtendedEscape);
Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false); Rest.Realm = Rest.Config.GetString("realm", Rest.Realm);
Rest.Fill = Rest.Config.GetBoolean("path-fill",true); Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset", Rest.DumpAsset);
Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32); Rest.Fill = Rest.Config.GetBoolean("path-fill", Rest.Fill);
Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error",true); Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size", Rest.DumpLineSize);
Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error", Rest.FlushEnabled);
// Note: Odd spacing is required in the following strings
Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId, Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
(Rest.Authenticate ? "" : "not ")); (Rest.Authenticate ? "" : "not "));
@ -374,13 +377,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
string path = request.RawUrl.ToLower(); string path = request.RawUrl.ToLower();
Rest.Log.DebugFormat("{0} Match ENTRY", MsgId); // Rest.Log.DebugFormat("{0} Match ENTRY", MsgId);
try try
{ {
foreach (string key in pathHandlers.Keys) foreach (string key in pathHandlers.Keys)
{ {
Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key); // Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key);
// Note that Match will not necessarily find the handler that will // Note that Match will not necessarily find the handler that will
// actually be used - it does no test for the "closest" fit. It // actually be used - it does no test for the "closest" fit. It
@ -388,7 +391,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (path.StartsWith(key)) if (path.StartsWith(key))
{ {
Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
// This apparently odd evaluation is needed to prevent a match // This apparently odd evaluation is needed to prevent a match
// on anything other than a URI token boundary. Otherwise we // on anything other than a URI token boundary. Otherwise we
@ -404,7 +407,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
foreach (string key in streamHandlers.Keys) foreach (string key in streamHandlers.Keys)
{ {
Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key); // Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key);
// Note that Match will not necessarily find the handler that will // Note that Match will not necessarily find the handler that will
// actually be used - it does no test for the "closest" fit. It // actually be used - it does no test for the "closest" fit. It
@ -412,7 +415,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (path.StartsWith(key)) if (path.StartsWith(key))
{ {
Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
// This apparently odd evaluation is needed to prevent a match // This apparently odd evaluation is needed to prevent a match
// on anything other than a URI token boundary. Otherwise we // on anything other than a URI token boundary. Otherwise we
@ -434,7 +437,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// <summary> /// <summary>
/// This is called by the HTTP server once the handler has indicated /// This is called by the HTTP server once the handler has indicated
/// that t is able to handle the request. /// that it is able to handle the request.
/// Preconditions: /// Preconditions:
/// [1] request != null and is a valid request object /// [1] request != null and is a valid request object
/// [2] response != null and is a valid response object /// [2] response != null and is a valid response object
@ -474,7 +477,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{ {
// A raw exception indicates that something we weren't expecting has // A raw exception indicates that something we weren't expecting has
// happened. This should always reflect a shortcoming in the plugin, // happened. This should always reflect a shortcoming in the plugin,
// or a failure to satisfy the preconditions. // or a failure to satisfy the preconditions. It should not reflect
// an error in the request itself. Under such circumstances the state
// of the request cannot be determined and we are obliged to mark it
// as 'handled'.
Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message); Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message);
handled = true; handled = true;
} }
@ -497,7 +504,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{ {
RequestData rdata = new RequestData(request, response, String.Empty); RequestData rdata = new RequestData(request, response, String.Empty);
string bestMatch = null; string bestMatch = String.Empty;
string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower(); string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower();
Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path); Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
@ -511,7 +518,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{ {
if (path.StartsWith(pattern)) if (path.StartsWith(pattern))
{ {
if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) if (pattern.Length > bestMatch.Length)
{ {
bestMatch = pattern; bestMatch = pattern;
} }
@ -520,7 +527,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
// Handle using the best match available // Handle using the best match available
if (!String.IsNullOrEmpty(bestMatch)) if (bestMatch.Length > 0)
{ {
Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch); Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch);
RestStreamHandler handler = streamHandlers[bestMatch]; RestStreamHandler handler = streamHandlers[bestMatch];

View File

@ -31,6 +31,7 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Xml; using System.Xml;
using System.Drawing; using System.Drawing;
using System.Timers;
using OpenSim.Framework; using OpenSim.Framework;
using OpenSim.Framework.Servers; using OpenSim.Framework.Servers;
using OpenSim.Framework.Communications; using OpenSim.Framework.Communications;
@ -61,6 +62,20 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
// This is better than a null reference.
if (Rest.InventoryServices == null)
throw new Exception(String.Format("{0} OpenSim inventory services are not available",
MsgId));
if (Rest.UserServices == null)
throw new Exception(String.Format("{0} OpenSim user services are not available",
MsgId));
if (Rest.AssetServices == null)
throw new Exception(String.Format("{0} OpenSim asset services are not available",
MsgId));
// If a relative path was specified for the handler's domain, // If a relative path was specified for the handler's domain,
// add the standard prefix to make it absolute, e.g. /admin // add the standard prefix to make it absolute, e.g. /admin
@ -167,9 +182,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
try try
{ {
// digest scheme seems borked: disable it for the time
// being
rdata.scheme = Rest.AS_BASIC;
if (!rdata.IsAuthenticated) if (!rdata.IsAuthenticated)
{ {
rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
@ -283,12 +295,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}",
MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
lock (rdata)
lock (rdata)
{
if (!rdata.HaveInventory)
{
rdata.startWD(1000);
rdata.timeout = false;
Monitor.Wait(rdata);
}
}
if (rdata.timeout)
{ {
if (!rdata.HaveInventory) Rest.Log.WarnFormat("{0} Inventory not available for {1} {2}. No response from service.",
{ MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
Monitor.Wait(rdata); rdata.Fail(Rest.HttpStatusCodeServerError, "inventory server not responding");
}
} }
if (rdata.root == null) if (rdata.root == null)
@ -2145,12 +2167,50 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal ICollection<InventoryItemBase> items = null; internal ICollection<InventoryItemBase> items = null;
internal UserProfileData userProfile = null; internal UserProfileData userProfile = null;
internal InventoryFolderBase root = null; internal InventoryFolderBase root = null;
internal bool timeout = false;
internal System.Timers.Timer watchDog = new System.Timers.Timer();
internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
: base(request, response, prefix) : base(request, response, prefix)
{ {
} }
internal void startWD(double interval)
{
Rest.Log.DebugFormat("{0} Setting watchdog", MsgId);
watchDog.Elapsed += new ElapsedEventHandler(OnTimeOut);
watchDog.Interval = interval;
watchDog.AutoReset = false;
watchDog.Enabled = true;
watchDog.Start();
}
internal void stopWD()
{
Rest.Log.DebugFormat("{0} Reset watchdog", MsgId);
watchDog.Stop();
}
/// <summary>
/// This is the callback method required by the inventory watchdog. The
/// requestor issues an inventory request and then blocks until the
/// request completes, or this method signals the monitor.
/// </summary>
private void OnTimeOut(object sender, ElapsedEventArgs args)
{
Rest.Log.DebugFormat("{0} Asynchronous inventory update timed-out", MsgId);
// InventoryRequestData rdata = (InventoryRequestData) sender;
lock (this)
{
this.folders = null;
this.items = null;
this.HaveInventory = false;
this.timeout = true;
Monitor.Pulse(this);
}
}
/// <summary> /// <summary>
/// This is the callback method required by inventory services. The /// This is the callback method required by inventory services. The
/// requestor issues an inventory request and then blocks until this /// requestor issues an inventory request and then blocks until this
@ -2160,11 +2220,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal void GetUserInventory(ICollection<InventoryFolderImpl> folders, ICollection<InventoryItemBase> items) internal void GetUserInventory(ICollection<InventoryFolderImpl> folders, ICollection<InventoryItemBase> items)
{ {
Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId); Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId);
this.folders = folders;
this.items = items;
this.HaveInventory = true;
lock (this) lock (this)
{ {
if (watchDog.Enabled)
{
this.stopWD();
}
this.folders = folders;
this.items = items;
this.HaveInventory = true;
this.timeout = false;
Monitor.Pulse(this); Monitor.Pulse(this);
} }
} }

View File

@ -178,8 +178,14 @@ namespace OpenSim.Framework.Communications.Cache
if (libraryFolders.ContainsKey(item.Folder)) if (libraryFolders.ContainsKey(item.Folder))
{ {
InventoryFolderImpl parentFolder = libraryFolders[item.Folder]; InventoryFolderImpl parentFolder = libraryFolders[item.Folder];
try
parentFolder.Items.Add(item.ID, item); {
parentFolder.Items.Add(item.ID, item);
}
catch (Exception)
{
m_log.WarnFormat("[LIBRARY INVENTORY] Item {1} [{0}] not added, duplicate item", item.ID, item.Name);
}
} }
else else
{ {