From 56d2db3a188461758dd1e4cec4536cf4f7bef684 Mon Sep 17 00:00:00 2001 From: TomTheDragon Date: Thu, 2 Nov 2017 23:53:19 -0400 Subject: [PATCH] Add a new experimental implementation of PGSQL FSAssets Signed-off-by: UbitUmarov --- OpenSim/Data/PGSQL/PGSQLFSAssetData.cs | 316 ++++++++++++++++++ .../PGSQL/Resources/FSAssetStore.migrations | 14 + 2 files changed, 330 insertions(+) create mode 100644 OpenSim/Data/PGSQL/PGSQLFSAssetData.cs create mode 100644 OpenSim/Data/PGSQL/Resources/FSAssetStore.migrations diff --git a/OpenSim/Data/PGSQL/PGSQLFSAssetData.cs b/OpenSim/Data/PGSQL/PGSQLFSAssetData.cs new file mode 100644 index 0000000000..59b857c61f --- /dev/null +++ b/OpenSim/Data/PGSQL/PGSQLFSAssetData.cs @@ -0,0 +1,316 @@ +/* + * 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 OpenMetaverse; +using Npgsql; +using NpgsqlTypes; + +namespace OpenSim.Data.PGSQL +{ + public class PGSQLFSAssetData : IFSAssetDataPlugin + { + private const string _migrationStore = "FSAssetStore"; + private static string m_Table = "fsassets"; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private long m_ticksToEpoch; + + private PGSQLManager m_database; + private string m_connectionString; + + public PGSQLFSAssetData() + { + } + + public void Initialise(string connect, string realm, int UpdateAccessTime) + { + DaysBetweenAccessTimeUpdates = UpdateAccessTime; + + m_ticksToEpoch = new System.DateTime(1970, 1, 1).Ticks; + + m_connectionString = connect; + m_database = new PGSQLManager(m_connectionString); + + //New migration to check for DB changes + m_database.CheckMigration(_migrationStore); + } + + public void Initialise() + { + throw new NotImplementedException(); + } + + /// + /// 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; } + } + + #region IPlugin Members + + public string Version { get { return "1.0.0.0"; } } + + public void Dispose() { } + + public string Name + { + get { return "PGSQL FSAsset storage engine"; } + } + + #endregion + + #region IFSAssetDataPlugin Members + + public AssetMetadata Get(string id, out string hash) + { + hash = String.Empty; + AssetMetadata meta = null; + UUID uuid = new UUID(id); + + string query = String.Format("select \"id\", \"type\", \"hash\", \"create_time\", \"access_time\", \"asset_flags\" from {0} where \"id\" = :id", m_Table); + using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString)) + using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon)) + { + dbcon.Open(); + cmd.Parameters.Add(m_database.CreateParameter("id", uuid)); + using (NpgsqlDataReader reader = cmd.ExecuteReader(CommandBehavior.Default)) + { + if (reader.Read()) + { + meta = new AssetMetadata(); + hash = reader["hash"].ToString(); + meta.ID = id; + meta.FullID = uuid; + meta.Name = String.Empty; + meta.Description = String.Empty; + 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 atime = Convert.ToInt32(reader["access_time"]); + UpdateAccessTime(atime, uuid); + } + } + } + + return meta; + } + + private void UpdateAccessTime(int AccessTime, UUID id) + { + // 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; + + string query = String.Format("UPDATE {0} SET \"access_time\" = :access_time WHERE \"id\" = :id", m_Table); + using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString)) + using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon)) + { + dbcon.Open(); + int now = (int)((System.DateTime.Now.Ticks - m_ticksToEpoch) / 10000000); + cmd.Parameters.Add(m_database.CreateParameter("id", id)); + cmd.Parameters.Add(m_database.CreateParameter("access_time", now)); + cmd.ExecuteNonQuery(); + } + } + + public bool Store(AssetMetadata meta, string hash) + { + try + { + bool found = false; + string oldhash; + AssetMetadata existingAsset = Get(meta.ID, out oldhash); + + string query = String.Format("UPDATE {0} SET \"access_time\" = :access_time WHERE \"id\" = :id", m_Table); + if (existingAsset == null) + { + query = String.Format("insert into {0} (\"id\", \"type\", \"hash\", \"asset_flags\", \"create_time\", \"access_time\") values ( :id, :type, :hash, :asset_flags, :create_time, :access_time)", m_Table); + found = true; + } + + using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString)) + using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon)) + { + dbcon.Open(); + int now = (int)((System.DateTime.Now.Ticks - m_ticksToEpoch) / 10000000); + cmd.Parameters.Add(m_database.CreateParameter("id", meta.FullID)); + cmd.Parameters.Add(m_database.CreateParameter("type", meta.Type)); + cmd.Parameters.Add(m_database.CreateParameter("hash", hash)); + cmd.Parameters.Add(m_database.CreateParameter("asset_flags", Convert.ToInt32(meta.Flags))); + cmd.Parameters.Add(m_database.CreateParameter("create_time", now)); + cmd.Parameters.Add(m_database.CreateParameter("access_time", now)); + cmd.ExecuteNonQuery(); + } + return found; + } + catch(Exception e) + { + m_log.Error("[PGSQL 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 query = string.Format("select \"id\" from {1} where id in ({0})", ids, m_Table); + using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString)) + using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon)) + { + dbcon.Open(); + using (NpgsqlDataReader reader = cmd.ExecuteReader(CommandBehavior.Default)) + { + while (reader.Read()) + { + UUID id = DBGuid.FromDB(reader["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() + { + int count = 0; + string query = String.Format("select count(*) as count from {0}", m_Table); + using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString)) + using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon)) + { + dbcon.Open(); + IDataReader reader = cmd.ExecuteReader(); + reader.Read(); + count = Convert.ToInt32(reader["count"]); + reader.Close(); + } + + return count; + } + + public bool Delete(string id) + { + string query = String.Format("delete from {0} where \"id\" = :id", m_Table); + using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString)) + using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon)) + { + dbcon.Open(); + cmd.Parameters.Add(m_database.CreateParameter("id", new UUID(id))); + cmd.ExecuteNonQuery(); + } + + return true; + } + + public void Import(string conn, string table, int start, int count, bool force, FSStoreDelegate store) + { + int imported = 0; + string limit = String.Empty; + if(count != -1) + { + limit = String.Format(" limit {0} offset {1}", start, count); + } + string query = String.Format("select * from {0}{1}", table, limit); + try + { + using (NpgsqlConnection remote = new NpgsqlConnection(conn)) + using (NpgsqlCommand cmd = new NpgsqlCommand(query, remote)) + { + remote.Open(); + MainConsole.Instance.Output("Querying database"); + MainConsole.Instance.Output("Reading data"); + using (NpgsqlDataReader reader = cmd.ExecuteReader(CommandBehavior.Default)) + { + 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 = String.Empty; + meta.Description = String.Empty; + 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++; + } + } + } + } + catch (Exception e) + { + m_log.ErrorFormat("[PGSQL FSASSETS]: Error importing assets: {0}", + e.Message.ToString()); + return; + } + + MainConsole.Instance.Output(String.Format("Import done, {0} assets imported", imported)); + } + + #endregion + } +} diff --git a/OpenSim/Data/PGSQL/Resources/FSAssetStore.migrations b/OpenSim/Data/PGSQL/Resources/FSAssetStore.migrations new file mode 100644 index 0000000000..3a072e566e --- /dev/null +++ b/OpenSim/Data/PGSQL/Resources/FSAssetStore.migrations @@ -0,0 +1,14 @@ +:VERSION 1 + +BEGIN TRANSACTION; + +CREATE TABLE fsassets ( + "id" uuid NOT NULL PRIMARY KEY, + "type" integer NOT NULL, + "hash" char(64) NOT NULL, + "create_time" integer NOT NULL DEFAULT '0', + "access_time" integer NOT NULL DEFAULT '0', + "asset_flags" integer NOT NULL DEFAULT '0' +); + +COMMIT;