/* * 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 OpenSimulator 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.Reflection; using System.Collections.Generic; using System.Data; using OpenSim.Framework; using OpenSim.Framework.Console; using log4net; using MySql.Data.MySqlClient; using OpenMetaverse; namespace OpenSim.Data.MySQL { public class MySQLFSAssetData : IFSAssetDataPlugin { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); protected MySqlConnection m_Connection = null; protected string m_ConnectionString; protected string m_Table; protected Object m_connLock = new Object(); /// /// Number of days that must pass before we update the access time on an asset when it has been fetched /// Config option to change this is "DaysBetweenAccessTimeUpdates" /// private int DaysBetweenAccessTimeUpdates = 0; protected virtual Assembly Assembly { get { return GetType().Assembly; } } public MySQLFSAssetData() { } #region IPlugin Members public string Version { get { return "1.0.0.0"; } } // Loads and initialises the MySQL storage plugin and checks for migrations public void Initialise(string connect, string realm, int UpdateAccessTime) { m_ConnectionString = connect; m_Table = realm; DaysBetweenAccessTimeUpdates = UpdateAccessTime; try { OpenDatabase(); Migration m = new Migration(m_Connection, Assembly, "FSAssetStore"); m.Update(); } catch (MySqlException e) { m_log.ErrorFormat("[FSASSETS]: Can't connect to database: {0}", e.Message.ToString()); } } public void Initialise() { throw new NotImplementedException(); } public void Dispose() { } public string Name { get { return "MySQL FSAsset storage engine"; } } #endregion private bool OpenDatabase() { try { m_Connection = new MySqlConnection(m_ConnectionString); m_Connection.Open(); } catch (MySqlException e) { m_log.ErrorFormat("[FSASSETS]: Can't connect to database: {0}", e.Message.ToString()); return false; } return true; } private IDataReader ExecuteReader(MySqlCommand c) { IDataReader r = null; MySqlConnection connection = (MySqlConnection) ((ICloneable)m_Connection).Clone(); connection.Open(); c.Connection = connection; r = c.ExecuteReader(); return r; } private void ExecuteNonQuery(MySqlCommand c) { lock (m_connLock) { bool errorSeen = false; while (true) { try { c.ExecuteNonQuery(); } catch (MySqlException) { System.Threading.Thread.Sleep(500); m_Connection.Close(); m_Connection = (MySqlConnection) ((ICloneable)m_Connection).Clone(); m_Connection.Open(); c.Connection = m_Connection; if (!errorSeen) { errorSeen = true; continue; } m_log.ErrorFormat("[FSASSETS] MySQL command: {0}", c.CommandText); throw; } break; } } } #region IFSAssetDataPlugin Members public AssetMetadata Get(string id, out string hash) { hash = String.Empty; MySqlCommand cmd = new MySqlCommand(); cmd.CommandText = String.Format("select id, name, description, type, hash, create_time, access_time, asset_flags from {0} where id = ?id", m_Table); cmd.Parameters.AddWithValue("?id", id); IDataReader reader = ExecuteReader(cmd); if (!reader.Read()) { reader.Close(); FreeCommand(cmd); return null; } AssetMetadata meta = new AssetMetadata(); hash = reader["hash"].ToString(); meta.ID = id; meta.FullID = new UUID(id); meta.Name = reader["name"].ToString(); meta.Description = reader["description"].ToString(); meta.Type = (sbyte)Convert.ToInt32(reader["type"]); meta.ContentType = SLUtil.SLAssetTypeToContentType(meta.Type); meta.CreationDate = Util.ToDateTime(Convert.ToInt32(reader["create_time"])); meta.Flags = (AssetFlags)Convert.ToInt32(reader["asset_flags"]); int AccessTime = Convert.ToInt32(reader["access_time"]); reader.Close(); UpdateAccessTime(AccessTime, cmd); FreeCommand(cmd); return meta; } private void UpdateAccessTime(int AccessTime, MySqlCommand cmd) { // Reduce DB work by only updating access time if asset hasn't recently been accessed // 0 By Default, Config option is "DaysBetweenAccessTimeUpdates" if (DaysBetweenAccessTimeUpdates > 0 && (DateTime.UtcNow - Utils.UnixTimeToDateTime(AccessTime)).TotalDays < DaysBetweenAccessTimeUpdates) return; cmd.CommandText = String.Format("UPDATE {0} SET `access_time` = UNIX_TIMESTAMP() WHERE `id` = ?id", m_Table); cmd.ExecuteNonQuery(); } protected void FreeCommand(MySqlCommand cmd) { MySqlConnection c = cmd.Connection; cmd.Dispose(); c.Close(); c.Dispose(); } public bool Store(AssetMetadata meta, string hash) { try { string oldhash; AssetMetadata existingAsset = Get(meta.ID, out oldhash); MySqlCommand cmd = m_Connection.CreateCommand(); cmd.Parameters.AddWithValue("?id", meta.ID); cmd.Parameters.AddWithValue("?name", meta.Name); cmd.Parameters.AddWithValue("?description", meta.Description); cmd.Parameters.AddWithValue("?type", meta.Type.ToString()); cmd.Parameters.AddWithValue("?hash", hash); cmd.Parameters.AddWithValue("?asset_flags", meta.Flags); if (existingAsset == null) { cmd.CommandText = String.Format("insert into {0} (id, name, description, type, hash, asset_flags, create_time, access_time) values ( ?id, ?name, ?description, ?type, ?hash, ?asset_flags, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())", m_Table); ExecuteNonQuery(cmd); cmd.Dispose(); return true; } //cmd.CommandText = String.Format("update {0} set hash = ?hash, access_time = UNIX_TIMESTAMP() where id = ?id", m_Table); //ExecuteNonQuery(cmd); cmd.Dispose(); return false; } catch(Exception e) { m_log.Error("[FSAssets] Failed to store asset with ID " + meta.ID); m_log.Error(e.ToString()); return false; } } /// /// Check if the assets exist in the database. /// /// The asset UUID's /// For each asset: true if it exists, false otherwise public bool[] AssetsExist(UUID[] uuids) { if (uuids.Length == 0) return new bool[0]; HashSet exists = new HashSet(); string ids = "'" + string.Join("','", uuids) + "'"; string sql = string.Format("select id from {1} where id in ({0})", ids, m_Table); using (MySqlCommand cmd = m_Connection.CreateCommand()) { cmd.CommandText = sql; using (MySqlDataReader dbReader = cmd.ExecuteReader()) { while (dbReader.Read()) { UUID id = DBGuid.FromDB(dbReader["ID"]); exists.Add(id); } } } bool[] results = new bool[uuids.Length]; for (int i = 0; i < uuids.Length; i++) results[i] = exists.Contains(uuids[i]); return results; } public int Count() { MySqlCommand cmd = m_Connection.CreateCommand(); cmd.CommandText = String.Format("select count(*) as count from {0}", m_Table); IDataReader reader = ExecuteReader(cmd); reader.Read(); int count = Convert.ToInt32(reader["count"]); reader.Close(); FreeCommand(cmd); return count; } public bool Delete(string id) { using (MySqlCommand cmd = m_Connection.CreateCommand()) { cmd.CommandText = String.Format("delete from {0} where id = ?id", m_Table); cmd.Parameters.AddWithValue("?id", id); ExecuteNonQuery(cmd); } return true; } public void Import(string conn, string table, int start, int count, bool force, FSStoreDelegate store) { MySqlConnection importConn; try { importConn = new MySqlConnection(conn); importConn.Open(); } catch (MySqlException e) { m_log.ErrorFormat("[FSASSETS]: Can't connect to database: {0}", e.Message.ToString()); return; } int imported = 0; MySqlCommand cmd = importConn.CreateCommand(); string limit = String.Empty; if (count != -1) { limit = String.Format(" limit {0},{1}", start, count); } cmd.CommandText = String.Format("select * from {0}{1}", table, limit); MainConsole.Instance.Output("Querying database"); IDataReader reader = cmd.ExecuteReader(); MainConsole.Instance.Output("Reading data"); while (reader.Read()) { if ((imported % 100) == 0) { MainConsole.Instance.Output(String.Format("{0} assets imported so far", imported)); } AssetBase asset = new AssetBase(); AssetMetadata meta = new AssetMetadata(); meta.ID = reader["id"].ToString(); meta.FullID = new UUID(meta.ID); meta.Name = reader["name"].ToString(); meta.Description = reader["description"].ToString(); meta.Type = (sbyte)Convert.ToInt32(reader["assetType"]); meta.ContentType = SLUtil.SLAssetTypeToContentType(meta.Type); meta.CreationDate = Util.ToDateTime(Convert.ToInt32(reader["create_time"])); asset.Metadata = meta; asset.Data = (byte[])reader["data"]; store(asset, force); imported++; } reader.Close(); cmd.Dispose(); importConn.Close(); MainConsole.Instance.Output(String.Format("Import done, {0} assets imported", imported)); } #endregion } }