PGSQLFSAssetData.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Reflection;
  29. using System.Collections.Generic;
  30. using System.Data;
  31. using OpenSim.Framework;
  32. using OpenSim.Framework.Console;
  33. using log4net;
  34. using OpenMetaverse;
  35. using Npgsql;
  36. using NpgsqlTypes;
  37. namespace OpenSim.Data.PGSQL
  38. {
  39. public class PGSQLFSAssetData : IFSAssetDataPlugin
  40. {
  41. private const string _migrationStore = "FSAssetStore";
  42. private static string m_Table = "fsassets";
  43. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  44. private long m_ticksToEpoch;
  45. private PGSQLManager m_database;
  46. private string m_connectionString;
  47. public PGSQLFSAssetData()
  48. {
  49. }
  50. public void Initialise(string connect, string realm, int UpdateAccessTime)
  51. {
  52. DaysBetweenAccessTimeUpdates = UpdateAccessTime;
  53. m_ticksToEpoch = new System.DateTime(1970, 1, 1).Ticks;
  54. m_connectionString = connect;
  55. m_database = new PGSQLManager(m_connectionString);
  56. //New migration to check for DB changes
  57. m_database.CheckMigration(_migrationStore);
  58. }
  59. public void Initialise()
  60. {
  61. throw new NotImplementedException();
  62. }
  63. /// <summary>
  64. /// Number of days that must pass before we update the access time on an asset when it has been fetched
  65. /// Config option to change this is "DaysBetweenAccessTimeUpdates"
  66. /// </summary>
  67. private int DaysBetweenAccessTimeUpdates = 0;
  68. protected virtual Assembly Assembly
  69. {
  70. get { return GetType().Assembly; }
  71. }
  72. #region IPlugin Members
  73. public string Version { get { return "1.0.0.0"; } }
  74. public void Dispose() { }
  75. public string Name
  76. {
  77. get { return "PGSQL FSAsset storage engine"; }
  78. }
  79. #endregion
  80. #region IFSAssetDataPlugin Members
  81. public AssetMetadata Get(string id, out string hash)
  82. {
  83. hash = String.Empty;
  84. AssetMetadata meta = null;
  85. UUID uuid = new UUID(id);
  86. string query = String.Format("select \"id\", \"type\", \"hash\", \"create_time\", \"access_time\", \"asset_flags\" from {0} where \"id\" = :id", m_Table);
  87. using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
  88. using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon))
  89. {
  90. dbcon.Open();
  91. cmd.Parameters.Add(m_database.CreateParameter("id", uuid));
  92. using (NpgsqlDataReader reader = cmd.ExecuteReader(CommandBehavior.Default))
  93. {
  94. if (reader.Read())
  95. {
  96. meta = new AssetMetadata();
  97. hash = reader["hash"].ToString();
  98. meta.ID = id;
  99. meta.FullID = uuid;
  100. meta.Name = String.Empty;
  101. meta.Description = String.Empty;
  102. meta.Type = (sbyte)Convert.ToInt32(reader["type"]);
  103. meta.ContentType = SLUtil.SLAssetTypeToContentType(meta.Type);
  104. meta.CreationDate = Util.ToDateTime(Convert.ToInt32(reader["create_time"]));
  105. meta.Flags = (AssetFlags)Convert.ToInt32(reader["asset_flags"]);
  106. int atime = Convert.ToInt32(reader["access_time"]);
  107. UpdateAccessTime(atime, uuid);
  108. }
  109. }
  110. }
  111. return meta;
  112. }
  113. private void UpdateAccessTime(int AccessTime, UUID id)
  114. {
  115. // Reduce DB work by only updating access time if asset hasn't recently been accessed
  116. // 0 By Default, Config option is "DaysBetweenAccessTimeUpdates"
  117. if (DaysBetweenAccessTimeUpdates > 0 && (DateTime.UtcNow - Utils.UnixTimeToDateTime(AccessTime)).TotalDays < DaysBetweenAccessTimeUpdates)
  118. return;
  119. string query = String.Format("UPDATE {0} SET \"access_time\" = :access_time WHERE \"id\" = :id", m_Table);
  120. using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
  121. using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon))
  122. {
  123. dbcon.Open();
  124. int now = (int)((System.DateTime.Now.Ticks - m_ticksToEpoch) / 10000000);
  125. cmd.Parameters.Add(m_database.CreateParameter("id", id));
  126. cmd.Parameters.Add(m_database.CreateParameter("access_time", now));
  127. cmd.ExecuteNonQuery();
  128. }
  129. }
  130. public bool Store(AssetMetadata meta, string hash)
  131. {
  132. try
  133. {
  134. bool found = false;
  135. string oldhash;
  136. AssetMetadata existingAsset = Get(meta.ID, out oldhash);
  137. string query = String.Format("UPDATE {0} SET \"access_time\" = :access_time WHERE \"id\" = :id", m_Table);
  138. if (existingAsset == null)
  139. {
  140. 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);
  141. found = true;
  142. }
  143. using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
  144. using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon))
  145. {
  146. dbcon.Open();
  147. int now = (int)((System.DateTime.Now.Ticks - m_ticksToEpoch) / 10000000);
  148. cmd.Parameters.Add(m_database.CreateParameter("id", meta.FullID));
  149. cmd.Parameters.Add(m_database.CreateParameter("type", meta.Type));
  150. cmd.Parameters.Add(m_database.CreateParameter("hash", hash));
  151. cmd.Parameters.Add(m_database.CreateParameter("asset_flags", Convert.ToInt32(meta.Flags)));
  152. cmd.Parameters.Add(m_database.CreateParameter("create_time", now));
  153. cmd.Parameters.Add(m_database.CreateParameter("access_time", now));
  154. cmd.ExecuteNonQuery();
  155. }
  156. return found;
  157. }
  158. catch(Exception e)
  159. {
  160. m_log.Error("[PGSQL FSASSETS] Failed to store asset with ID " + meta.ID);
  161. m_log.Error(e.ToString());
  162. return false;
  163. }
  164. }
  165. /// <summary>
  166. /// Check if the assets exist in the database.
  167. /// </summary>
  168. /// <param name="uuids">The asset UUID's</param>
  169. /// <returns>For each asset: true if it exists, false otherwise</returns>
  170. public bool[] AssetsExist(UUID[] uuids)
  171. {
  172. if (uuids.Length == 0)
  173. return new bool[0];
  174. HashSet<UUID> exists = new HashSet<UUID>();
  175. string ids = "'" + string.Join("','", uuids) + "'";
  176. string query = string.Format("select \"id\" from {1} where id in ({0})", ids, m_Table);
  177. using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
  178. using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon))
  179. {
  180. dbcon.Open();
  181. using (NpgsqlDataReader reader = cmd.ExecuteReader(CommandBehavior.Default))
  182. {
  183. while (reader.Read())
  184. {
  185. UUID id = DBGuid.FromDB(reader["id"]);;
  186. exists.Add(id);
  187. }
  188. }
  189. }
  190. bool[] results = new bool[uuids.Length];
  191. for (int i = 0; i < uuids.Length; i++)
  192. results[i] = exists.Contains(uuids[i]);
  193. return results;
  194. }
  195. public int Count()
  196. {
  197. int count = 0;
  198. string query = String.Format("select count(*) as count from {0}", m_Table);
  199. using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
  200. using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon))
  201. {
  202. dbcon.Open();
  203. IDataReader reader = cmd.ExecuteReader();
  204. reader.Read();
  205. count = Convert.ToInt32(reader["count"]);
  206. reader.Close();
  207. }
  208. return count;
  209. }
  210. public bool Delete(string id)
  211. {
  212. string query = String.Format("delete from {0} where \"id\" = :id", m_Table);
  213. using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
  214. using (NpgsqlCommand cmd = new NpgsqlCommand(query, dbcon))
  215. {
  216. dbcon.Open();
  217. cmd.Parameters.Add(m_database.CreateParameter("id", new UUID(id)));
  218. cmd.ExecuteNonQuery();
  219. }
  220. return true;
  221. }
  222. public void Import(string conn, string table, int start, int count, bool force, FSStoreDelegate store)
  223. {
  224. int imported = 0;
  225. string limit = String.Empty;
  226. if(count != -1)
  227. {
  228. limit = String.Format(" limit {0} offset {1}", start, count);
  229. }
  230. string query = String.Format("select * from {0}{1}", table, limit);
  231. try
  232. {
  233. using (NpgsqlConnection remote = new NpgsqlConnection(conn))
  234. using (NpgsqlCommand cmd = new NpgsqlCommand(query, remote))
  235. {
  236. remote.Open();
  237. MainConsole.Instance.Output("Querying database");
  238. MainConsole.Instance.Output("Reading data");
  239. using (NpgsqlDataReader reader = cmd.ExecuteReader(CommandBehavior.Default))
  240. {
  241. while (reader.Read())
  242. {
  243. if ((imported % 100) == 0)
  244. {
  245. MainConsole.Instance.Output(String.Format("{0} assets imported so far", imported));
  246. }
  247. AssetBase asset = new AssetBase();
  248. AssetMetadata meta = new AssetMetadata();
  249. meta.ID = reader["id"].ToString();
  250. meta.FullID = new UUID(meta.ID);
  251. meta.Name = String.Empty;
  252. meta.Description = String.Empty;
  253. meta.Type = (sbyte)Convert.ToInt32(reader["assetType"]);
  254. meta.ContentType = SLUtil.SLAssetTypeToContentType(meta.Type);
  255. meta.CreationDate = Util.ToDateTime(Convert.ToInt32(reader["create_time"]));
  256. asset.Metadata = meta;
  257. asset.Data = (byte[])reader["data"];
  258. store(asset, force);
  259. imported++;
  260. }
  261. }
  262. }
  263. }
  264. catch (Exception e)
  265. {
  266. m_log.ErrorFormat("[PGSQL FSASSETS]: Error importing assets: {0}",
  267. e.Message.ToString());
  268. return;
  269. }
  270. MainConsole.Instance.Output(String.Format("Import done, {0} assets imported", imported));
  271. }
  272. #endregion
  273. }
  274. }