From 48be04df1d804137a736d83de157265555a63d31 Mon Sep 17 00:00:00 2001 From: Justin Clarke Casey Date: Fri, 11 Jan 2008 17:00:21 +0000 Subject: * Do database implementation for prim inventory items in mysql * Properly clean up items when a region object is deleted * Update persisted prim when an inventory script is changed * No user functionality yet --- OpenSim/Framework/Data.MySQL/MySQLDataStore.cs | 325 ++++++++++++++++++++- OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs | 51 ++-- .../Scenes/SceneObjectGroup.Inventory.cs | 7 + 3 files changed, 351 insertions(+), 32 deletions(-) diff --git a/OpenSim/Framework/Data.MySQL/MySQLDataStore.cs b/OpenSim/Framework/Data.MySQL/MySQLDataStore.cs index 3c25533..6d33a36 100644 --- a/OpenSim/Framework/Data.MySQL/MySQLDataStore.cs +++ b/OpenSim/Framework/Data.MySQL/MySQLDataStore.cs @@ -44,6 +44,7 @@ namespace OpenSim.Framework.Data.MySQL { private const string m_primSelect = "select * from prims"; private const string m_shapeSelect = "select * from primshapes"; + private const string m_itemsSelect = "select * from primitems"; private const string m_terrainSelect = "select * from terrain limit 1"; private const string m_landSelect = "select * from land"; private const string m_landAccessListSelect = "select * from landaccesslist"; @@ -51,15 +52,21 @@ namespace OpenSim.Framework.Data.MySQL private DataSet m_dataSet; private MySqlDataAdapter m_primDataAdapter; private MySqlDataAdapter m_shapeDataAdapter; + private MySqlDataAdapter m_itemsDataAdapter; private MySqlConnection m_connection; private MySqlDataAdapter m_terrainDataAdapter; private MySqlDataAdapter m_landDataAdapter; private MySqlDataAdapter m_landAccessListDataAdapter; + private DataTable m_primTable; private DataTable m_shapeTable; + private DataTable m_itemsTable; private DataTable m_terrainTable; private DataTable m_landTable; private DataTable m_landAccessListTable; + + // Temporary attribute while this is experimental + private bool persistPrimInventories; /*********************************************************************** * @@ -71,6 +78,7 @@ namespace OpenSim.Framework.Data.MySQL public void Initialise(string connectionstring, bool persistPrimInventories) { m_dataSet = new DataSet(); + this.persistPrimInventories = persistPrimInventories; MainLog.Instance.Verbose("DATASTORE", "MySql - connecting: " + connectionstring); m_connection = new MySqlConnection(connectionstring); @@ -80,6 +88,9 @@ namespace OpenSim.Framework.Data.MySQL MySqlCommand shapeSelectCmd = new MySqlCommand(m_shapeSelect, m_connection); m_shapeDataAdapter = new MySqlDataAdapter(shapeSelectCmd); + + MySqlCommand itemsSelectCmd = new MySqlCommand(m_itemsSelect, m_connection); + m_itemsDataAdapter = new MySqlDataAdapter(itemsSelectCmd); MySqlCommand terrainSelectCmd = new MySqlCommand(m_terrainSelect, m_connection); m_terrainDataAdapter = new MySqlDataAdapter(terrainSelectCmd); @@ -103,6 +114,14 @@ namespace OpenSim.Framework.Data.MySQL m_dataSet.Tables.Add(m_shapeTable); SetupShapeCommands(m_shapeDataAdapter, m_connection); m_shapeDataAdapter.Fill(m_shapeTable); + + if (persistPrimInventories) + { + m_itemsTable = createItemsTable(); + m_dataSet.Tables.Add(m_itemsTable); + SetupItemsCommands(m_itemsDataAdapter, m_connection); + m_itemsDataAdapter.Fill(m_itemsTable); + } m_terrainTable = createTerrainTable(); m_dataSet.Tables.Add(m_terrainTable); @@ -146,6 +165,7 @@ namespace OpenSim.Framework.Data.MySQL { DataTable prims = m_primTable; DataTable shapes = m_shapeTable; + DataTable items = m_itemsTable; string selectExp = "SceneGroupID = '" + Util.ToRawUuidString(obj) + "'"; lock (m_dataSet) @@ -153,12 +173,27 @@ namespace OpenSim.Framework.Data.MySQL DataRow[] primRows = prims.Select(selectExp); foreach (DataRow row in primRows) { + // Remove shapes row LLUUID uuid = new LLUUID((string) row["UUID"]); DataRow shapeRow = shapes.Rows.Find(Util.ToRawUuidString(uuid)); if (shapeRow != null) { shapeRow.Delete(); } + + if (persistPrimInventories) + { + // Remove items rows + String sql = String.Format("primID = '{0}'", uuid); + DataRow[] itemRows = items.Select(sql); + + foreach (DataRow itemsRow in itemRows) + { + shapeRow.Delete(); + } + } + + // Remove prim row row.Delete(); } } @@ -166,6 +201,9 @@ namespace OpenSim.Framework.Data.MySQL Commit(); } + /// + /// Load persisted objects from region storage. + /// public List LoadObjects(LLUUID regionUUID) { Dictionary createdObjects = new Dictionary(); @@ -186,14 +224,17 @@ namespace OpenSim.Framework.Data.MySQL foreach (DataRow primRow in primsForRegion) { - try + try { string uuid = (string) primRow["UUID"]; string objID = (string) primRow["SceneGroupID"]; + + SceneObjectPart prim = buildPrim(primRow); + if (uuid == objID) //is new SceneObjectGroup ? { SceneObjectGroup group = new SceneObjectGroup(); - SceneObjectPart prim = buildPrim(primRow); + DataRow shapeRow = shapes.Rows.Find(Util.ToRawUuidString(prim.UUID)); if (shapeRow != null) { @@ -213,7 +254,6 @@ namespace OpenSim.Framework.Data.MySQL } else { - SceneObjectPart prim = buildPrim(primRow); DataRow shapeRow = shapes.Rows.Find(Util.ToRawUuidString(prim.UUID)); if (shapeRow != null) { @@ -227,6 +267,11 @@ namespace OpenSim.Framework.Data.MySQL } createdObjects[new LLUUID(objID)].AddPart(prim); } + + if (persistPrimInventories) + { + LoadItems(prim); + } } catch (Exception e) { @@ -241,7 +286,39 @@ namespace OpenSim.Framework.Data.MySQL } return retvals; } - + + /// + /// Load in a prim's persisted inventory. + /// + /// + private void LoadItems(SceneObjectPart prim) + { + MainLog.Instance.Verbose("DATASTORE", "Loading inventory for {0}, {1}", prim.Name, prim.UUID); + + DataTable dbItems = m_itemsTable; + + String sql = String.Format("primID = '{0}'", prim.UUID.ToString()); + DataRow[] dbItemRows = dbItems.Select(sql); + + IList inventory = new List(); + + foreach (DataRow row in dbItemRows) + { + TaskInventoryItem item = buildItem(row); + inventory.Add(item); + + MainLog.Instance.Verbose("DATASTORE", "Restored item {0}, {1}", item.name, item.item_id); + } + + prim.AddInventoryItems(inventory); + + // XXX A nasty little hack to recover the folder id for the prim (which is currently stored in + // every item). This data should really be stored in the prim table itself. + if (dbItemRows.Length > 0) + { + prim.FolderID = inventory[0].parent_id; + } + } public void StoreTerrain(double[,] ter, LLUUID regionID) { @@ -437,6 +514,12 @@ namespace OpenSim.Framework.Data.MySQL m_primDataAdapter.Update(m_primTable); m_shapeDataAdapter.Update(m_shapeTable); + + if (persistPrimInventories) + { + m_itemsDataAdapter.Update(m_itemsTable); + } + m_terrainDataAdapter.Update(m_terrainTable); m_landDataAdapter.Update(m_landTable); m_landAccessListDataAdapter.Update(m_landAccessListTable); @@ -630,6 +713,38 @@ namespace OpenSim.Framework.Data.MySQL return shapes; } + + private DataTable createItemsTable() + { + DataTable items = new DataTable("primitems"); + + createCol(items, "itemID", typeof (String)); + createCol(items, "primID", typeof (String)); + createCol(items, "assetID", typeof (String)); + createCol(items, "parentFolderID", typeof (String)); + + createCol(items, "invType", typeof (Int32)); + createCol(items, "assetType", typeof (Int32)); + + createCol(items, "name", typeof (String)); + createCol(items, "description", typeof (String)); + + createCol(items, "creationDate", typeof (Int64)); + createCol(items, "creatorID", typeof (String)); + createCol(items, "ownerID", typeof (String)); + createCol(items, "lastOwnerID", typeof (String)); + createCol(items, "groupID", typeof (String)); + + createCol(items, "nextPermissions", typeof (Int32)); + createCol(items, "currentPermissions", typeof (Int32)); + createCol(items, "basePermissions", typeof (Int32)); + createCol(items, "everyonePermissions", typeof (Int32)); + createCol(items, "groupPermissions", typeof (Int32)); + + items.PrimaryKey = new DataColumn[] {items.Columns["itemID"]}; + + return items; + } /*********************************************************************** * @@ -727,6 +842,41 @@ namespace OpenSim.Framework.Data.MySQL } return prim; } + + + /// + /// Build a prim inventory item from the persisted data. + /// + /// + /// + private TaskInventoryItem buildItem(DataRow row) + { + TaskInventoryItem taskItem = new TaskInventoryItem(); + + taskItem.item_id = new LLUUID((String)row["itemID"]); + taskItem.ParentPartID = new LLUUID((String)row["primID"]); + taskItem.asset_id = new LLUUID((String)row["assetID"]); + taskItem.parent_id = new LLUUID((String)row["parentFolderID"]); + + taskItem.inv_type = Convert.ToInt32(row["invType"]); + taskItem.type = Convert.ToInt32(row["assetType"]); + + taskItem.name = (String)row["name"]; + taskItem.desc = (String)row["description"]; + taskItem.creation_date = Convert.ToUInt32(row["creationDate"]); + taskItem.creator_id = new LLUUID((String)row["creatorID"]); + taskItem.owner_id = new LLUUID((String)row["ownerID"]); + taskItem.last_owner_id = new LLUUID((String)row["lastOwnerID"]); + taskItem.group_id = new LLUUID((String)row["groupID"]); + + taskItem.next_owner_mask = Convert.ToUInt32(row["nextPermissions"]); + taskItem.owner_mask = Convert.ToUInt32(row["currentPermissions"]); + taskItem.base_mask = Convert.ToUInt32(row["basePermissions"]); + taskItem.everyone_mask = Convert.ToUInt32(row["everyonePermissions"]); + taskItem.group_mask = Convert.ToUInt32(row["groupPermissions"]); + + return taskItem; + } private LandData buildLandData(DataRow row) { @@ -870,6 +1020,30 @@ namespace OpenSim.Framework.Data.MySQL } } } + + private void fillItemRow(DataRow row, TaskInventoryItem taskItem) + { + row["itemID"] = taskItem.item_id; + row["primID"] = taskItem.ParentPartID; + row["assetID"] = taskItem.asset_id; + row["parentFolderID"] = taskItem.parent_id; + + row["invType"] = taskItem.inv_type; + row["assetType"] = taskItem.type; + + row["name"] = taskItem.name; + row["description"] = taskItem.desc; + row["creationDate"] = taskItem.creation_date; + row["creatorID"] = taskItem.creator_id; + row["ownerID"] = taskItem.owner_id; + row["lastOwnerID"] = taskItem.last_owner_id; + row["groupID"] = taskItem.group_id; + row["nextPermissions"] = taskItem.next_owner_mask; + row["currentPermissions"] = taskItem.owner_mask; + row["basePermissions"] = taskItem.base_mask; + row["everyonePermissions"] = taskItem.everyone_mask; + row["groupPermissions"] = taskItem.group_mask; + } private void fillLandRow(DataRow row, LandData land, LLUUID regionUUID) { @@ -1017,7 +1191,79 @@ namespace OpenSim.Framework.Data.MySQL { fillShapeRow(shapeRow, prim); } + + if (persistPrimInventories) + { + addPrimInventory(prim.UUID, prim.TaskInventory); + } } + + /// + /// Persist prim inventory. Deletes, updates and inserts rows. + /// + /// + /// + /// + private void addPrimInventory(LLUUID primID, IDictionary items) + { + MainLog.Instance.Verbose("DATASTORE", "Entered addPrimInventory with prim ID {0}", primID); + + // Find all existing inventory rows for this prim + DataTable dbItems = m_itemsTable; + + String sql = String.Format("primID = '{0}'", primID); + DataRow[] dbItemRows = dbItems.Select(sql); + + // Build structures for manipulation purposes + IDictionary dbItemsToRemove = new Dictionary(); + ICollection itemsToAdd + = new List(); + + foreach (DataRow row in dbItemRows) + { + dbItemsToRemove.Add((String)row["itemID"], row); + } + + // Eliminate rows from the deletion set which already exist for this prim's inventory + // TODO Very temporary, need to take account of simple metadata changes soon + foreach (LLUUID itemId in items.Keys) + { + String rawItemId = itemId.ToString(); + + if (dbItemsToRemove.ContainsKey(rawItemId)) + { + dbItemsToRemove.Remove(rawItemId); + } + else + { + itemsToAdd.Add(items[itemId]); + } + } + + // Delete excess rows + foreach (DataRow row in dbItemsToRemove.Values) + { + MainLog.Instance.Verbose( + "DATASTORE", + "Removing item {0}, {1} from prim ID {2}", + row["name"], row["itemID"], row["primID"]); + + row.Delete(); + } + + // Insert items not already present + foreach (TaskInventoryItem newItem in itemsToAdd) + { + MainLog.Instance.Verbose( + "DATASTORE", + "Adding item {0}, {1} to prim ID {1}", + newItem.name, newItem.item_id, newItem.ParentPartID); + + DataRow newItemRow = dbItems.NewRow(); + fillItemRow(newItemRow, newItem); + dbItems.Rows.Add(newItemRow); + } + } /*********************************************************************** * @@ -1111,6 +1357,9 @@ namespace OpenSim.Framework.Data.MySQL } sql += subsql; sql += ")"; + + //MainLog.Instance.Verbose("DATASTORE", "defineTable() sql {0}", sql); + return sql; } @@ -1166,6 +1415,20 @@ namespace OpenSim.Framework.Data.MySQL delete.Connection = conn; da.DeleteCommand = delete; } + + private void SetupItemsCommands(MySqlDataAdapter da, MySqlConnection conn) + { + da.InsertCommand = createInsertCommand("primitems", m_itemsTable); + da.InsertCommand.Connection = conn; + + da.UpdateCommand = createUpdateCommand("primitems", "itemID = :itemID", m_itemsTable); + da.UpdateCommand.Connection = conn; + + MySqlCommand delete = new MySqlCommand("delete from primitems where itemID = :itemID"); + delete.Parameters.Add(createMySqlParameter("itemID", typeof (String))); + delete.Connection = conn; + da.DeleteCommand = delete; + } private void SetupTerrainCommands(MySqlDataAdapter da, MySqlConnection conn) { @@ -1206,12 +1469,14 @@ namespace OpenSim.Framework.Data.MySQL { string createPrims = defineTable(createPrimTable()); string createShapes = defineTable(createShapeTable()); + string createItems = defineTable(createItemsTable()); string createTerrain = defineTable(createTerrainTable()); string createLand = defineTable(createLandTable()); string createLandAccessList = defineTable(createLandAccessListTable()); MySqlCommand pcmd = new MySqlCommand(createPrims, conn); MySqlCommand scmd = new MySqlCommand(createShapes, conn); + MySqlCommand icmd = new MySqlCommand(createItems, conn); MySqlCommand tcmd = new MySqlCommand(createTerrain, conn); MySqlCommand lcmd = new MySqlCommand(createLand, conn); MySqlCommand lalcmd = new MySqlCommand(createLandAccessList, conn); @@ -1225,45 +1490,54 @@ namespace OpenSim.Framework.Data.MySQL { pcmd.ExecuteNonQuery(); } - catch (MySqlException) + catch (MySqlException e) { - MainLog.Instance.Warn("MySql", "Primitives Table Already Exists"); + MainLog.Instance.Warn("MySql", "Primitives Table Already Exists: {0}", e); } try { scmd.ExecuteNonQuery(); } - catch (MySqlException) + catch (MySqlException e) { - MainLog.Instance.Warn("MySql", "Shapes Table Already Exists"); + MainLog.Instance.Warn("MySql", "Shapes Table Already Exists: {0}", e); } + + try + { + icmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + MainLog.Instance.Warn("MySql", "Items Table Already Exists: {0}", e); + } try { tcmd.ExecuteNonQuery(); } - catch (MySqlException) + catch (MySqlException e) { - MainLog.Instance.Warn("MySql", "Terrain Table Already Exists"); + MainLog.Instance.Warn("MySql", "Terrain Table Already Exists: {0}", e); } try { lcmd.ExecuteNonQuery(); } - catch (MySqlException) + catch (MySqlException e) { - MainLog.Instance.Warn("MySql", "Land Table Already Exists"); + MainLog.Instance.Warn("MySql", "Land Table Already Exists: {0}", e); } try { lalcmd.ExecuteNonQuery(); } - catch (MySqlException) + catch (MySqlException e) { - MainLog.Instance.Warn("MySql", "LandAccessList Table Already Exists"); + MainLog.Instance.Warn("MySql", "LandAccessList Table Already Exists: {0}", e); } conn.Close(); } @@ -1274,6 +1548,8 @@ namespace OpenSim.Framework.Data.MySQL MySqlDataAdapter pDa = new MySqlDataAdapter(primSelectCmd); MySqlCommand shapeSelectCmd = new MySqlCommand(m_shapeSelect, conn); MySqlDataAdapter sDa = new MySqlDataAdapter(shapeSelectCmd); + MySqlCommand itemsSelectCmd = new MySqlCommand(m_itemsSelect, conn); + MySqlDataAdapter iDa = new MySqlDataAdapter(itemsSelectCmd); MySqlCommand terrainSelectCmd = new MySqlCommand(m_terrainSelect, conn); MySqlDataAdapter tDa = new MySqlDataAdapter(terrainSelectCmd); MySqlCommand landSelectCmd = new MySqlCommand(m_landSelect, conn); @@ -1286,6 +1562,10 @@ namespace OpenSim.Framework.Data.MySQL { pDa.Fill(tmpDS, "prims"); sDa.Fill(tmpDS, "primshapes"); + + if (persistPrimInventories) + iDa.Fill(tmpDS, "primitems"); + tDa.Fill(tmpDS, "terrain"); lDa.Fill(tmpDS, "land"); lalDa.Fill(tmpDS, "landaccesslist"); @@ -1298,6 +1578,10 @@ namespace OpenSim.Framework.Data.MySQL pDa.Fill(tmpDS, "prims"); sDa.Fill(tmpDS, "primshapes"); + + if (persistPrimInventories) + iDa.Fill(tmpDS, "primitems"); + tDa.Fill(tmpDS, "terrain"); lDa.Fill(tmpDS, "land"); lalDa.Fill(tmpDS, "landaccesslist"); @@ -1310,6 +1594,7 @@ namespace OpenSim.Framework.Data.MySQL return false; } } + foreach (DataColumn col in createShapeTable().Columns) { if (!tmpDS.Tables["primshapes"].Columns.Contains(col.ColumnName)) @@ -1318,6 +1603,9 @@ namespace OpenSim.Framework.Data.MySQL return false; } } + + // XXX primitems should probably go here eventually + foreach (DataColumn col in createTerrainTable().Columns) { if (!tmpDS.Tables["terrain"].Columns.Contains(col.ColumnName)) @@ -1326,6 +1614,7 @@ namespace OpenSim.Framework.Data.MySQL return false; } } + foreach (DataColumn col in createLandTable().Columns) { if (!tmpDS.Tables["land"].Columns.Contains(col.ColumnName)) @@ -1334,6 +1623,7 @@ namespace OpenSim.Framework.Data.MySQL return false; } } + foreach (DataColumn col in createLandAccessListTable().Columns) { if (!tmpDS.Tables["landaccesslist"].Columns.Contains(col.ColumnName)) @@ -1342,6 +1632,7 @@ namespace OpenSim.Framework.Data.MySQL return false; } } + return true; } @@ -1395,6 +1686,10 @@ namespace OpenSim.Framework.Data.MySQL { return "integer"; } + else if (type == typeof (Int64)) + { + return "bigint"; + } else if (type == typeof (Double)) { return "float"; @@ -1409,4 +1704,4 @@ namespace OpenSim.Framework.Data.MySQL } } } -} \ No newline at end of file +} diff --git a/OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs b/OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs index 96dd5ac..dfd4535 100644 --- a/OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs +++ b/OpenSim/Framework/Data.SQLite/SQLiteRegionData.cs @@ -61,6 +61,7 @@ namespace OpenSim.Framework.Data.SQLite private String m_connectionString; + // Temporary attribute while this is experimental private bool persistPrimInventories; /*********************************************************************** @@ -208,6 +209,7 @@ namespace OpenSim.Framework.Data.SQLite { DataTable prims = ds.Tables["prims"]; DataTable shapes = ds.Tables["primshapes"]; + DataTable items = ds.Tables["primitems"]; string selectExp = "SceneGroupID = '" + Util.ToRawUuidString(obj) + "'"; lock (ds) @@ -215,13 +217,28 @@ namespace OpenSim.Framework.Data.SQLite DataRow[] primRows = prims.Select(selectExp); foreach (DataRow row in primRows) { + // Remove shape rows LLUUID uuid = new LLUUID((string) row["UUID"]); DataRow shapeRow = shapes.Rows.Find(Util.ToRawUuidString(uuid)); if (shapeRow != null) { shapeRow.Delete(); } - row.Delete(); + + if (persistPrimInventories) + { + // Remove items rows + String sql = String.Format("primID = '{0}'", uuid); + DataRow[] itemRows = items.Select(sql); + + foreach (DataRow itemsRow in itemRows) + { + itemsRow.Delete(); + } + } + + // Remove prim row + row.Delete(); } } @@ -1444,6 +1461,20 @@ namespace OpenSim.Framework.Data.SQLite param.SourceColumn = name; param.SourceVersion = DataRowVersion.Current; return param; + } + + private void setupPrimCommands(SqliteDataAdapter da, SqliteConnection conn) + { + da.InsertCommand = createInsertCommand("prims", ds.Tables["prims"]); + da.InsertCommand.Connection = conn; + + da.UpdateCommand = createUpdateCommand("prims", "UUID=:UUID", ds.Tables["prims"]); + da.UpdateCommand.Connection = conn; + + SqliteCommand delete = new SqliteCommand("delete from prims where UUID = :UUID"); + delete.Parameters.Add(createSqliteParameter("UUID", typeof (String))); + delete.Connection = conn; + da.DeleteCommand = delete; } private void setupItemsCommands(SqliteDataAdapter da, SqliteConnection conn) @@ -1460,20 +1491,6 @@ namespace OpenSim.Framework.Data.SQLite da.DeleteCommand = delete; } - private void setupPrimCommands(SqliteDataAdapter da, SqliteConnection conn) - { - da.InsertCommand = createInsertCommand("prims", ds.Tables["prims"]); - da.InsertCommand.Connection = conn; - - da.UpdateCommand = createUpdateCommand("prims", "UUID=:UUID", ds.Tables["prims"]); - da.UpdateCommand.Connection = conn; - - SqliteCommand delete = new SqliteCommand("delete from prims where UUID = :UUID"); - delete.Parameters.Add(createSqliteParameter("UUID", typeof (String))); - delete.Connection = conn; - da.DeleteCommand = delete; - } - private void setupTerrainCommands(SqliteDataAdapter da, SqliteConnection conn) { da.InsertCommand = createInsertCommand("terrain", ds.Tables["terrain"]); @@ -1656,8 +1673,8 @@ namespace OpenSim.Framework.Data.SQLite return false; } } - - // TODO Not restoring prim inventories quite yet + + // XXX primitems should probably go here eventually foreach (DataColumn col in createTerrainTable().Columns) { diff --git a/OpenSim/Region/Environment/Scenes/SceneObjectGroup.Inventory.cs b/OpenSim/Region/Environment/Scenes/SceneObjectGroup.Inventory.cs index b188fdd..d694f67 100644 --- a/OpenSim/Region/Environment/Scenes/SceneObjectGroup.Inventory.cs +++ b/OpenSim/Region/Environment/Scenes/SceneObjectGroup.Inventory.cs @@ -235,6 +235,13 @@ namespace OpenSim.Region.Environment.Scenes if (part != null) { part.UpdateInventoryItem(item); + + // It might seem somewhat crude to update the whole group for a single prim inventory change, + // but it's possible that other prim inventory changes will take place before the region + // persistence thread visits this object. In the future, changes can be signalled at a more + // granular level, or we could let the datastore worry about whether prims have really + // changed since they were last persisted. + HasChanged = true; return true; } -- cgit v1.1