/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSim Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Data; using System.Reflection; using libsecondlife; using Mono.Data.SqliteClient; using OpenSim.Framework.Console; namespace OpenSim.Framework.Data.SQLite { public class SQLiteInventoryStore : SQLiteBase, IInventoryData { private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private const string invItemsSelect = "select * from inventoryitems"; private const string invFoldersSelect = "select * from inventoryfolders"; private DataSet ds; private SqliteDataAdapter invItemsDa; private SqliteDataAdapter invFoldersDa; /// /// Initialises the interface /// public void Initialise() { Initialise("inventoryStore.db", "inventoryDatabase"); } public void Initialise(string dbfile, string dbname) { string connectionString = "URI=file:" + dbfile + ",version=3"; m_log.Info("[Inventory]: Sqlite - connecting: " + dbfile); SqliteConnection conn = new SqliteConnection(connectionString); conn.Open(); TestTables(conn); SqliteCommand itemsSelectCmd = new SqliteCommand(invItemsSelect, conn); invItemsDa = new SqliteDataAdapter(itemsSelectCmd); // SqliteCommandBuilder primCb = new SqliteCommandBuilder(primDa); SqliteCommand foldersSelectCmd = new SqliteCommand(invFoldersSelect, conn); invFoldersDa = new SqliteDataAdapter(foldersSelectCmd); ds = new DataSet(); ds.Tables.Add(createInventoryFoldersTable()); invFoldersDa.Fill(ds.Tables["inventoryfolders"]); setupFoldersCommands(invFoldersDa, conn); m_log.Info("[DATASTORE]: Populated Intentory Folders Definitions"); ds.Tables.Add(createInventoryItemsTable()); invItemsDa.Fill(ds.Tables["inventoryitems"]); setupItemsCommands(invItemsDa, conn); m_log.Info("[DATASTORE]: Populated Intentory Items Definitions"); ds.AcceptChanges(); } public InventoryItemBase buildItem(DataRow row) { InventoryItemBase item = new InventoryItemBase(); item.inventoryID = new LLUUID((string) row["UUID"]); item.assetID = new LLUUID((string) row["assetID"]); item.assetType = Convert.ToInt32(row["assetType"]); item.invType = Convert.ToInt32(row["invType"]); item.parentFolderID = new LLUUID((string) row["parentFolderID"]); item.avatarID = new LLUUID((string) row["avatarID"]); item.creatorsID = new LLUUID((string) row["creatorsID"]); item.inventoryName = (string) row["inventoryName"]; item.inventoryDescription = (string) row["inventoryDescription"]; item.inventoryNextPermissions = Convert.ToUInt32(row["inventoryNextPermissions"]); item.inventoryCurrentPermissions = Convert.ToUInt32(row["inventoryCurrentPermissions"]); item.inventoryBasePermissions = Convert.ToUInt32(row["inventoryBasePermissions"]); item.inventoryEveryOnePermissions = Convert.ToUInt32(row["inventoryEveryOnePermissions"]); return item; } private void fillItemRow(DataRow row, InventoryItemBase item) { row["UUID"] = Util.ToRawUuidString(item.inventoryID); row["assetID"] = Util.ToRawUuidString(item.assetID); row["assetType"] = item.assetType; row["invType"] = item.invType; row["parentFolderID"] = Util.ToRawUuidString(item.parentFolderID); row["avatarID"] = Util.ToRawUuidString(item.avatarID); row["creatorsID"] = Util.ToRawUuidString(item.creatorsID); row["inventoryName"] = item.inventoryName; row["inventoryDescription"] = item.inventoryDescription; row["inventoryNextPermissions"] = item.inventoryNextPermissions; row["inventoryCurrentPermissions"] = item.inventoryCurrentPermissions; row["inventoryBasePermissions"] = item.inventoryBasePermissions; row["inventoryEveryOnePermissions"] = item.inventoryEveryOnePermissions; } private void addFolder(InventoryFolderBase folder) { lock (ds) { DataTable inventoryFolderTable = ds.Tables["inventoryfolders"]; DataRow inventoryRow = inventoryFolderTable.Rows.Find(Util.ToRawUuidString(folder.folderID)); if (inventoryRow == null) { inventoryRow = inventoryFolderTable.NewRow(); fillFolderRow(inventoryRow, folder); inventoryFolderTable.Rows.Add(inventoryRow); } else { fillFolderRow(inventoryRow, folder); } invFoldersDa.Update(ds, "inventoryfolders"); } } private void moveFolder(InventoryFolderBase folder) { lock (ds) { DataTable inventoryFolderTable = ds.Tables["inventoryfolders"]; DataRow inventoryRow = inventoryFolderTable.Rows.Find(Util.ToRawUuidString(folder.folderID)); if (inventoryRow == null) { inventoryRow = inventoryFolderTable.NewRow(); fillFolderRow(inventoryRow, folder); inventoryFolderTable.Rows.Add(inventoryRow); } else { moveFolderRow(inventoryRow, folder); } invFoldersDa.Update(ds, "inventoryfolders"); } } private void addItem(InventoryItemBase item) { lock (ds) { DataTable inventoryItemTable = ds.Tables["inventoryitems"]; DataRow inventoryRow = inventoryItemTable.Rows.Find(Util.ToRawUuidString(item.inventoryID)); if (inventoryRow == null) { inventoryRow = inventoryItemTable.NewRow(); fillItemRow(inventoryRow, item); inventoryItemTable.Rows.Add(inventoryRow); } else { fillItemRow(inventoryRow, item); } invItemsDa.Update(ds, "inventoryitems"); } } public void Shutdown() { // TODO: DataSet commit } /// /// Closes the interface /// public void Close() { } /// /// The plugin being loaded /// /// A string containing the plugin name public string getName() { return "SQLite Inventory Data Interface"; } /// /// The plugins version /// /// A string containing the plugin version public string getVersion() { Module module = GetType().Module; string dllName = module.Assembly.ManifestModule.Name; Version dllVersion = module.Assembly.GetName().Version; return string.Format("{0}.{1}.{2}.{3}", dllVersion.Major, dllVersion.Minor, dllVersion.Build, dllVersion.Revision); } /// /// Returns a list of inventory items contained within the specified folder /// /// The UUID of the target folder /// A List of InventoryItemBase items public List getInventoryInFolder(LLUUID folderID) { lock (ds) { List retval = new List(); DataTable inventoryItemTable = ds.Tables["inventoryitems"]; string selectExp = "parentFolderID = '" + Util.ToRawUuidString(folderID) + "'"; DataRow[] rows = inventoryItemTable.Select(selectExp); foreach (DataRow row in rows) { retval.Add(buildItem(row)); } return retval; } } /// /// Returns a list of the root folders within a users inventory /// /// The user whos inventory is to be searched /// A list of folder objects public List getUserRootFolders(LLUUID user) { return new List(); } // see InventoryItemBase.getUserRootFolder public InventoryFolderBase getUserRootFolder(LLUUID user) { lock (ds) { List folders = new List(); DataTable inventoryFolderTable = ds.Tables["inventoryfolders"]; string selectExp = "agentID = '" + Util.ToRawUuidString(user) + "' AND parentID = '" + Util.ToRawUuidString(LLUUID.Zero) + "'"; DataRow[] rows = inventoryFolderTable.Select(selectExp); foreach (DataRow row in rows) { folders.Add(buildFolder(row)); } // There should only ever be one root folder for a user. However, if there's more // than one we'll simply use the first one rather than failing. It would be even // nicer to print some message to this effect, but this feels like it's too low a // to put such a message out, and it's too minor right now to spare the time to // suitably refactor. if (folders.Count > 0) { return folders[0]; } return null; } } /// /// Append a list of all the child folders of a parent folder /// /// list where folders will be appended /// ID of parent protected void getInventoryFolders(ref List folders, LLUUID parentID) { lock (ds) { DataTable inventoryFolderTable = ds.Tables["inventoryfolders"]; string selectExp = "parentID = '" + Util.ToRawUuidString(parentID) + "'"; DataRow[] rows = inventoryFolderTable.Select(selectExp); foreach (DataRow row in rows) { folders.Add(buildFolder(row)); } } } /// /// Returns a list of inventory folders contained in the folder 'parentID' /// /// The folder to get subfolders for /// A list of inventory folders public List getInventoryFolders(LLUUID parentID) { List folders = new List(); getInventoryFolders(ref folders, Util.ToRawUuidString(parentID)); return folders; } /// /// Returns all child folders in the hierarchy from the parent folder and down /// /// The folder to get subfolders for /// A list of inventory folders protected List getFolderHierarchy(LLUUID parentID) { List folders = new List(); getInventoryFolders(ref folders, Util.ToRawUuidString(parentID)); for (int i = 0; i < folders.Count; i++) getInventoryFolders(ref folders, Util.ToRawUuidString(folders[i].folderID)); return folders; } /// /// Returns an inventory item by its UUID /// /// The UUID of the item to be returned /// A class containing item information public InventoryItemBase getInventoryItem(LLUUID item) { lock (ds) { DataRow row = ds.Tables["inventoryitems"].Rows.Find(Util.ToRawUuidString(item)); if (row != null) { return buildItem(row); } else { return null; } } } /// /// Returns a specified inventory folder by its UUID /// /// The UUID of the folder to be returned /// A class containing folder information public InventoryFolderBase getInventoryFolder(LLUUID folder) { // TODO: Deep voodoo here. If you enable this code then // multi region breaks. No idea why, but I figured it was // better to leave multi region at this point. It does mean // that you don't get to see system textures why creating // clothes and the like. :( lock (ds) { DataRow row = ds.Tables["inventoryfolders"].Rows.Find(Util.ToRawUuidString(folder)); if (row != null) { return buildFolder(row); } else { return null; } } } /// /// Creates a new inventory item based on item /// /// The item to be created public void addInventoryItem(InventoryItemBase item) { addItem(item); } /// /// Updates an inventory item with item (updates based on ID) /// /// The updated item public void updateInventoryItem(InventoryItemBase item) { addItem(item); } /// /// /// /// public void deleteInventoryItem(LLUUID itemID) { lock (ds) { DataTable inventoryItemTable = ds.Tables["inventoryitems"]; DataRow inventoryRow = inventoryItemTable.Rows.Find(Util.ToRawUuidString(itemID)); if (inventoryRow != null) { inventoryRow.Delete(); } invItemsDa.Update(ds, "inventoryitems"); } } /// /// Delete all items in the specified folder /// /// id of the folder, whose item content should be deleted //!TODO, this is horribly inefficient, but I don't want to ruin the overall structure of this implementation private void deleteItemsInFolder(LLUUID folderId) { List items = getInventoryInFolder(Util.ToRawUuidString(folderId)); foreach (InventoryItemBase i in items) deleteInventoryItem(Util.ToRawUuidString(i.inventoryID)); } /// /// Adds a new folder specified by folder /// /// The inventory folder public void addInventoryFolder(InventoryFolderBase folder) { addFolder(folder); } /// /// Updates a folder based on its ID with folder /// /// The inventory folder public void updateInventoryFolder(InventoryFolderBase folder) { addFolder(folder); } /// /// Moves a folder based on its ID with folder /// /// The inventory folder public void moveInventoryFolder(InventoryFolderBase folder) { moveFolder(folder); } /// /// Delete a folder /// /// /// This will clean-up any child folders and child items as well /// /// public void deleteInventoryFolder(LLUUID folderID) { lock (ds) { List subFolders = getFolderHierarchy(Util.ToRawUuidString(folderID)); DataTable inventoryFolderTable = ds.Tables["inventoryfolders"]; DataRow inventoryRow; //Delete all sub-folders foreach (InventoryFolderBase f in subFolders) { inventoryRow = inventoryFolderTable.Rows.Find(Util.ToRawUuidString(f.folderID)); if (inventoryRow != null) { deleteItemsInFolder(Util.ToRawUuidString(f.folderID)); inventoryRow.Delete(); } } //Delete the actual row inventoryRow = inventoryFolderTable.Rows.Find(Util.ToRawUuidString(folderID)); if (inventoryRow != null) { deleteItemsInFolder(Util.ToRawUuidString(folderID)); inventoryRow.Delete(); } invFoldersDa.Update(ds, "inventoryfolders"); } } /*********************************************************************** * * Data Table definitions * **********************************************************************/ private static DataTable createInventoryItemsTable() { DataTable inv = new DataTable("inventoryitems"); createCol(inv, "UUID", typeof (String)); //inventoryID createCol(inv, "assetID", typeof (String)); createCol(inv, "assetType", typeof (Int32)); createCol(inv, "invType", typeof (Int32)); createCol(inv, "parentFolderID", typeof (String)); createCol(inv, "avatarID", typeof (String)); createCol(inv, "creatorsID", typeof (String)); createCol(inv, "inventoryName", typeof (String)); createCol(inv, "inventoryDescription", typeof (String)); // permissions createCol(inv, "inventoryNextPermissions", typeof (Int32)); createCol(inv, "inventoryCurrentPermissions", typeof (Int32)); createCol(inv, "inventoryBasePermissions", typeof (Int32)); createCol(inv, "inventoryEveryOnePermissions", typeof (Int32)); inv.PrimaryKey = new DataColumn[] {inv.Columns["UUID"]}; return inv; } private DataTable createInventoryFoldersTable() { DataTable fol = new DataTable("inventoryfolders"); createCol(fol, "UUID", typeof (String)); //folderID createCol(fol, "name", typeof (String)); createCol(fol, "agentID", typeof (String)); createCol(fol, "parentID", typeof (String)); createCol(fol, "type", typeof (Int32)); createCol(fol, "version", typeof (Int32)); fol.PrimaryKey = new DataColumn[] {fol.Columns["UUID"]}; return fol; } private void setupItemsCommands(SqliteDataAdapter da, SqliteConnection conn) { lock (ds) { da.InsertCommand = createInsertCommand("inventoryitems", ds.Tables["inventoryitems"]); da.InsertCommand.Connection = conn; da.UpdateCommand = createUpdateCommand("inventoryitems", "UUID=:UUID", ds.Tables["inventoryitems"]); da.UpdateCommand.Connection = conn; SqliteCommand delete = new SqliteCommand("delete from inventoryitems where UUID = :UUID"); delete.Parameters.Add(createSqliteParameter("UUID", typeof(String))); delete.Connection = conn; da.DeleteCommand = delete; } } private void setupFoldersCommands(SqliteDataAdapter da, SqliteConnection conn) { lock (ds) { da.InsertCommand = createInsertCommand("inventoryfolders", ds.Tables["inventoryfolders"]); da.InsertCommand.Connection = conn; da.UpdateCommand = createUpdateCommand("inventoryfolders", "UUID=:UUID", ds.Tables["inventoryfolders"]); da.UpdateCommand.Connection = conn; SqliteCommand delete = new SqliteCommand("delete from inventoryfolders where UUID = :UUID"); delete.Parameters.Add(createSqliteParameter("UUID", typeof(String))); delete.Connection = conn; da.DeleteCommand = delete; } } private InventoryFolderBase buildFolder(DataRow row) { InventoryFolderBase folder = new InventoryFolderBase(); folder.folderID = new LLUUID((string) row["UUID"]); folder.name = (string) row["name"]; folder.agentID = new LLUUID((string) row["agentID"]); folder.parentID = new LLUUID((string) row["parentID"]); folder.type = Convert.ToInt16(row["type"]); folder.version = Convert.ToUInt16(row["version"]); return folder; } private void fillFolderRow(DataRow row, InventoryFolderBase folder) { row["UUID"] = Util.ToRawUuidString(folder.folderID); row["name"] = folder.name; row["agentID"] = Util.ToRawUuidString(folder.agentID); row["parentID"] = Util.ToRawUuidString(folder.parentID); row["type"] = folder.type; row["version"] = folder.version; } private void moveFolderRow(DataRow row, InventoryFolderBase folder) { row["UUID"] = Util.ToRawUuidString(folder.folderID); row["parentID"] = Util.ToRawUuidString(folder.parentID); } /*********************************************************************** * * Test and Initialization code * **********************************************************************/ private void InitDB(SqliteConnection conn) { string createInventoryItems = defineTable(createInventoryItemsTable()); string createInventoryFolders = defineTable(createInventoryFoldersTable()); SqliteCommand pcmd = new SqliteCommand(createInventoryItems, conn); SqliteCommand scmd = new SqliteCommand(createInventoryFolders, conn); pcmd.ExecuteNonQuery(); scmd.ExecuteNonQuery(); } private bool TestTables(SqliteConnection conn) { SqliteCommand invItemsSelectCmd = new SqliteCommand(invItemsSelect, conn); SqliteDataAdapter pDa = new SqliteDataAdapter(invItemsSelectCmd); SqliteCommand invFoldersSelectCmd = new SqliteCommand(invFoldersSelect, conn); SqliteDataAdapter sDa = new SqliteDataAdapter(invFoldersSelectCmd); DataSet tmpDS = new DataSet(); try { pDa.Fill(tmpDS, "inventoryitems"); sDa.Fill(tmpDS, "inventoryfolders"); } catch (SqliteSyntaxException) { m_log.Info("[DATASTORE]: SQLite Database doesn't exist... creating"); InitDB(conn); } pDa.Fill(tmpDS, "inventoryitems"); sDa.Fill(tmpDS, "inventoryfolders"); foreach (DataColumn col in createInventoryItemsTable().Columns) { if (! tmpDS.Tables["inventoryitems"].Columns.Contains(col.ColumnName)) { m_log.Info("[DATASTORE]: Missing required column:" + col.ColumnName); return false; } } foreach (DataColumn col in createInventoryFoldersTable().Columns) { if (! tmpDS.Tables["inventoryfolders"].Columns.Contains(col.ColumnName)) { m_log.Info("[DATASTORE]: Missing required column:" + col.ColumnName); return false; } } return true; } } }