OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
PGSQLXAssetData.cs
Go to the documentation of this file.
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 
28 using System;
29 using System.Collections.Generic;
30 using System.Data;
31 using System.IO;
32 using System.IO.Compression;
33 using System.Reflection;
34 using System.Security.Cryptography;
35 using System.Text;
36 using log4net;
37 using OpenMetaverse;
38 using OpenSim.Framework;
39 using OpenSim.Data;
40 using Npgsql;
41 
42 namespace OpenSim.Data.PGSQL
43 {
45  {
46  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
47 
48  protected virtual Assembly Assembly
49  {
50  get { return GetType().Assembly; }
51  }
52 
56  private const int DaysBetweenAccessTimeUpdates = 30;
57 
58  private bool m_enableCompression = false;
59  private PGSQLManager m_database;
60  private string m_connectionString;
61  private object m_dbLock = new object();
62 
66  private HashAlgorithm hasher = new SHA256CryptoServiceProvider();
67 
68  #region IPlugin Members
69 
70  public string Version { get { return "1.0.0.0"; } }
71 
83  public void Initialise(string connect)
84  {
85  m_log.ErrorFormat("[PGSQL XASSETDATA]: ***********************************************************");
86  m_log.ErrorFormat("[PGSQL XASSETDATA]: ***********************************************************");
87  m_log.ErrorFormat("[PGSQL XASSETDATA]: ***********************************************************");
88  m_log.ErrorFormat("[PGSQL XASSETDATA]: THIS PLUGIN IS STRICTLY EXPERIMENTAL.");
89  m_log.ErrorFormat("[PGSQL XASSETDATA]: DO NOT USE FOR ANY DATA THAT YOU DO NOT MIND LOSING.");
90  m_log.ErrorFormat("[PGSQL XASSETDATA]: DATABASE TABLES CAN CHANGE AT ANY TIME, CAUSING EXISTING DATA TO BE LOST.");
91  m_log.ErrorFormat("[PGSQL XASSETDATA]: ***********************************************************");
92  m_log.ErrorFormat("[PGSQL XASSETDATA]: ***********************************************************");
93  m_log.ErrorFormat("[PGSQL XASSETDATA]: ***********************************************************");
94 
95  m_connectionString = connect;
96  m_database = new PGSQLManager(m_connectionString);
97 
98  using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
99  {
100  dbcon.Open();
101  Migration m = new Migration(dbcon, Assembly, "XAssetStore");
102  m.Update();
103  }
104  }
105 
106  public void Initialise()
107  {
108  throw new NotImplementedException();
109  }
110 
111  public void Dispose() { }
112 
116  public string Name
117  {
118  get { return "PGSQL XAsset storage engine"; }
119  }
120 
121  #endregion
122 
123  #region IAssetDataPlugin Members
124 
131  public AssetBase GetAsset(UUID assetID)
132  {
133 // m_log.DebugFormat("[PGSQL XASSET DATA]: Looking for asset {0}", assetID);
134 
135  AssetBase asset = null;
136  lock (m_dbLock)
137  {
138  using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
139  {
140  dbcon.Open();
141 
142  using (NpgsqlCommand cmd = new NpgsqlCommand(
143  @"SELECT name, description, access_time, ""AssetType"", local, temporary, asset_flags, creatorid, data
144  FROM XAssetsMeta
145  JOIN XAssetsData ON XAssetsMeta.hash = XAssetsData.Hash WHERE id=:ID",
146  dbcon))
147  {
148  cmd.Parameters.Add(m_database.CreateParameter("ID", assetID));
149 
150  try
151  {
152  using (NpgsqlDataReader dbReader = cmd.ExecuteReader(CommandBehavior.SingleRow))
153  {
154  if (dbReader.Read())
155  {
156  asset = new AssetBase(
157  assetID,
158  (string)dbReader["name"],
159  Convert.ToSByte(dbReader["AssetType"]),
160  dbReader["creatorid"].ToString());
161 
162  asset.Data = (byte[])dbReader["data"];
163  asset.Description = (string)dbReader["description"];
164 
165  string local = dbReader["local"].ToString();
166  if (local.Equals("1") || local.Equals("true", StringComparison.InvariantCultureIgnoreCase))
167  asset.Local = true;
168  else
169  asset.Local = false;
170 
171  asset.Temporary = Convert.ToBoolean(dbReader["temporary"]);
172  asset.Flags = (AssetFlags)Convert.ToInt32(dbReader["asset_flags"]);
173 
174  if (m_enableCompression)
175  {
176  using (GZipStream decompressionStream = new GZipStream(new MemoryStream(asset.Data), CompressionMode.Decompress))
177  {
178  MemoryStream outputStream = new MemoryStream();
179  WebUtil.CopyStream(decompressionStream, outputStream, int.MaxValue);
180  // int compressedLength = asset.Data.Length;
181  asset.Data = outputStream.ToArray();
182 
183  // m_log.DebugFormat(
184  // "[XASSET DB]: Decompressed {0} {1} to {2} bytes from {3}",
185  // asset.ID, asset.Name, asset.Data.Length, compressedLength);
186  }
187  }
188 
189  UpdateAccessTime(asset.Metadata, (int)dbReader["access_time"]);
190  }
191  }
192  }
193  catch (Exception e)
194  {
195  m_log.Error(string.Format("[PGSQL XASSET DATA]: Failure fetching asset {0}", assetID), e);
196  }
197  }
198  }
199  }
200 
201  return asset;
202  }
203 
209  public void StoreAsset(AssetBase asset)
210  {
211 // m_log.DebugFormat("[XASSETS DB]: Storing asset {0} {1}", asset.Name, asset.ID);
212 
213  lock (m_dbLock)
214  {
215  using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
216  {
217  dbcon.Open();
218 
219  using (NpgsqlTransaction transaction = dbcon.BeginTransaction())
220  {
221  string assetName = asset.Name;
222  if (asset.Name.Length > 64)
223  {
224  assetName = asset.Name.Substring(0, 64);
225  m_log.WarnFormat(
226  "[XASSET DB]: Name '{0}' for asset {1} truncated from {2} to {3} characters on add",
227  asset.Name, asset.ID, asset.Name.Length, assetName.Length);
228  }
229 
230  string assetDescription = asset.Description;
231  if (asset.Description.Length > 64)
232  {
233  assetDescription = asset.Description.Substring(0, 64);
234  m_log.WarnFormat(
235  "[XASSET DB]: Description '{0}' for asset {1} truncated from {2} to {3} characters on add",
236  asset.Description, asset.ID, asset.Description.Length, assetDescription.Length);
237  }
238 
239  if (m_enableCompression)
240  {
241  MemoryStream outputStream = new MemoryStream();
242 
243  using (GZipStream compressionStream = new GZipStream(outputStream, CompressionMode.Compress, false))
244  {
245  // Console.WriteLine(WebUtil.CopyTo(new MemoryStream(asset.Data), compressionStream, int.MaxValue));
246  // We have to close the compression stream in order to make sure it writes everything out to the underlying memory output stream.
247  compressionStream.Close();
248  byte[] compressedData = outputStream.ToArray();
249  asset.Data = compressedData;
250  }
251  }
252 
253  byte[] hash = hasher.ComputeHash(asset.Data);
254 
255  UUID asset_id;
256  UUID.TryParse(asset.ID, out asset_id);
257 
258 // m_log.DebugFormat(
259 // "[XASSET DB]: Compressed data size for {0} {1}, hash {2} is {3}",
260 // asset.ID, asset.Name, hash, compressedData.Length);
261 
262  try
263  {
264  using (NpgsqlCommand cmd =
265  new NpgsqlCommand(
266  @"insert INTO XAssetsMeta(id, hash, name, description, ""AssetType"", local, temporary, create_time, access_time, asset_flags, creatorid)
267  Select :ID, :Hash, :Name, :Description, :AssetType, :Local, :Temporary, :CreateTime, :AccessTime, :AssetFlags, :CreatorID
268  where not exists( Select id from XAssetsMeta where id = :ID);
269 
270  update XAssetsMeta
271  set id = :ID, hash = :Hash, name = :Name, description = :Description,
272  ""AssetType"" = :AssetType, local = :Local, temporary = :Temporary, create_time = :CreateTime,
273  access_time = :AccessTime, asset_flags = :AssetFlags, creatorid = :CreatorID
274  where id = :ID;
275  ",
276  dbcon))
277  {
278 
279  // create unix epoch time
280  int now = (int)Utils.DateTimeToUnixTime(DateTime.UtcNow);
281  cmd.Parameters.Add(m_database.CreateParameter("ID", asset_id));
282  cmd.Parameters.Add(m_database.CreateParameter("Hash", hash));
283  cmd.Parameters.Add(m_database.CreateParameter("Name", assetName));
284  cmd.Parameters.Add(m_database.CreateParameter("Description", assetDescription));
285  cmd.Parameters.Add(m_database.CreateParameter("AssetType", asset.Type));
286  cmd.Parameters.Add(m_database.CreateParameter("Local", asset.Local));
287  cmd.Parameters.Add(m_database.CreateParameter("Temporary", asset.Temporary));
288  cmd.Parameters.Add(m_database.CreateParameter("CreateTime", now));
289  cmd.Parameters.Add(m_database.CreateParameter("AccessTime", now));
290  cmd.Parameters.Add(m_database.CreateParameter("CreatorID", asset.Metadata.CreatorID));
291  cmd.Parameters.Add(m_database.CreateParameter("AssetFlags", (int)asset.Flags));
292 
293  cmd.ExecuteNonQuery();
294  }
295  }
296  catch (Exception e)
297  {
298  m_log.ErrorFormat("[ASSET DB]: PGSQL failure creating asset metadata {0} with name \"{1}\". Error: {2}",
299  asset.FullID, asset.Name, e.Message);
300 
301  transaction.Rollback();
302 
303  return;
304  }
305 
306  if (!ExistsData(dbcon, transaction, hash))
307  {
308  try
309  {
310  using (NpgsqlCommand cmd =
311  new NpgsqlCommand(
312  @"INSERT INTO XAssetsData(hash, data) VALUES(:Hash, :Data)",
313  dbcon))
314  {
315  cmd.Parameters.Add(m_database.CreateParameter("Hash", hash));
316  cmd.Parameters.Add(m_database.CreateParameter("Data", asset.Data));
317  cmd.ExecuteNonQuery();
318  }
319  }
320  catch (Exception e)
321  {
322  m_log.ErrorFormat("[XASSET DB]: PGSQL failure creating asset data {0} with name \"{1}\". Error: {2}",
323  asset.FullID, asset.Name, e.Message);
324 
325  transaction.Rollback();
326 
327  return;
328  }
329  }
330 
331  transaction.Commit();
332  }
333  }
334  }
335  }
336 
346  private void UpdateAccessTime(AssetMetadata assetMetadata, int accessTime)
347  {
348  DateTime now = DateTime.UtcNow;
349 
350  if ((now - Utils.UnixTimeToDateTime(accessTime)).TotalDays < DaysBetweenAccessTimeUpdates)
351  return;
352 
353  lock (m_dbLock)
354  {
355  using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
356  {
357  dbcon.Open();
358  NpgsqlCommand cmd =
359  new NpgsqlCommand(@"update XAssetsMeta set access_time=:AccessTime where id=:ID", dbcon);
360 
361  try
362  {
363  UUID asset_id;
364  UUID.TryParse(assetMetadata.ID, out asset_id);
365 
366  using (cmd)
367  {
368  // create unix epoch time
369  cmd.Parameters.Add(m_database.CreateParameter("id", asset_id));
370  cmd.Parameters.Add(m_database.CreateParameter("access_time", (int)Utils.DateTimeToUnixTime(now)));
371  cmd.ExecuteNonQuery();
372  }
373  }
374  catch (Exception e)
375  {
376  m_log.ErrorFormat(
377  "[XASSET PGSQL DB]: Failure updating access_time for asset {0} with name {1} : {2}",
378  assetMetadata.ID, assetMetadata.Name, e.Message);
379  }
380  }
381  }
382  }
383 
392  private bool ExistsData(NpgsqlConnection dbcon, NpgsqlTransaction transaction, byte[] hash)
393  {
394 // m_log.DebugFormat("[ASSETS DB]: Checking for asset {0}", uuid);
395 
396  bool exists = false;
397 
398  using (NpgsqlCommand cmd = new NpgsqlCommand(@"SELECT hash FROM XAssetsData WHERE hash=:Hash", dbcon))
399  {
400  cmd.Parameters.Add(m_database.CreateParameter("Hash", hash));
401 
402  try
403  {
404  using (NpgsqlDataReader dbReader = cmd.ExecuteReader(CommandBehavior.SingleRow))
405  {
406  if (dbReader.Read())
407  {
408 // m_log.DebugFormat("[ASSETS DB]: Found asset {0}", uuid);
409  exists = true;
410  }
411  }
412  }
413  catch (Exception e)
414  {
415  m_log.ErrorFormat(
416  "[XASSETS DB]: PGSql failure in ExistsData fetching hash {0}. Exception {1}{2}",
417  hash, e.Message, e.StackTrace);
418  }
419  }
420 
421  return exists;
422  }
423 
429  public bool[] AssetsExist(UUID[] uuids)
430  {
431  if (uuids.Length == 0)
432  return new bool[0];
433 
434  HashSet<UUID> exist = new HashSet<UUID>();
435 
436  string ids = "'" + string.Join("','", uuids) + "'";
437  string sql = string.Format(@"SELECT id FROM XAssetsMeta WHERE id IN ({0})", ids);
438 
439  using (NpgsqlConnection conn = new NpgsqlConnection(m_connectionString))
440  {
441  conn.Open();
442  using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn))
443  {
444  using (NpgsqlDataReader reader = cmd.ExecuteReader())
445  {
446  while (reader.Read())
447  {
448  UUID id = DBGuid.FromDB(reader["id"]);
449  exist.Add(id);
450  }
451  }
452  }
453  }
454 
455  bool[] results = new bool[uuids.Length];
456  for (int i = 0; i < uuids.Length; i++)
457  results[i] = exist.Contains(uuids[i]);
458  return results;
459  }
460 
466  public bool ExistsAsset(UUID uuid)
467  {
468 // m_log.DebugFormat("[ASSETS DB]: Checking for asset {0}", uuid);
469 
470  bool assetExists = false;
471 
472  lock (m_dbLock)
473  {
474  using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
475  {
476  dbcon.Open();
477  using (NpgsqlCommand cmd = new NpgsqlCommand(@"SELECT id FROM XAssetsMeta WHERE id=:ID", dbcon))
478  {
479  cmd.Parameters.Add(m_database.CreateParameter("id", uuid));
480 
481  try
482  {
483  using (NpgsqlDataReader dbReader = cmd.ExecuteReader(CommandBehavior.SingleRow))
484  {
485  if (dbReader.Read())
486  {
487 // m_log.DebugFormat("[ASSETS DB]: Found asset {0}", uuid);
488  assetExists = true;
489  }
490  }
491  }
492  catch (Exception e)
493  {
494  m_log.Error(string.Format("[XASSETS DB]: PGSql failure fetching asset {0}", uuid), e);
495  }
496  }
497  }
498  }
499 
500  return assetExists;
501  }
502 
503 
512  public List<AssetMetadata> FetchAssetMetadataSet(int start, int count)
513  {
514  List<AssetMetadata> retList = new List<AssetMetadata>(count);
515 
516  lock (m_dbLock)
517  {
518  using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
519  {
520  dbcon.Open();
521  NpgsqlCommand cmd = new NpgsqlCommand( @"SELECT name, description, access_time, ""AssetType"", temporary, id, asset_flags, creatorid
522  FROM XAssetsMeta
523  LIMIT :start, :count", dbcon);
524  cmd.Parameters.Add(m_database.CreateParameter("start", start));
525  cmd.Parameters.Add(m_database.CreateParameter("count", count));
526 
527  try
528  {
529  using (NpgsqlDataReader dbReader = cmd.ExecuteReader())
530  {
531  while (dbReader.Read())
532  {
533  AssetMetadata metadata = new AssetMetadata();
534  metadata.Name = (string)dbReader["name"];
535  metadata.Description = (string)dbReader["description"];
536  metadata.Type = Convert.ToSByte(dbReader["AssetType"]);
537  metadata.Temporary = Convert.ToBoolean(dbReader["temporary"]);
538  metadata.Flags = (AssetFlags)Convert.ToInt32(dbReader["asset_flags"]);
539  metadata.FullID = DBGuid.FromDB(dbReader["id"]);
540  metadata.CreatorID = dbReader["creatorid"].ToString();
541 
542  // We'll ignore this for now - it appears unused!
543 // metadata.SHA1 = dbReader["hash"]);
544 
545  UpdateAccessTime(metadata, (int)dbReader["access_time"]);
546 
547  retList.Add(metadata);
548  }
549  }
550  }
551  catch (Exception e)
552  {
553  m_log.Error("[XASSETS DB]: PGSql failure fetching asset set" + Environment.NewLine + e.ToString());
554  }
555  }
556  }
557 
558  return retList;
559  }
560 
561  public bool Delete(string id)
562  {
563 // m_log.DebugFormat("[XASSETS DB]: Deleting asset {0}", id);
564 
565  lock (m_dbLock)
566  {
567  using (NpgsqlConnection dbcon = new NpgsqlConnection(m_connectionString))
568  {
569  dbcon.Open();
570 
571  using (NpgsqlCommand cmd = new NpgsqlCommand(@"delete from XAssetsMeta where id=:ID", dbcon))
572  {
573  cmd.Parameters.Add(m_database.CreateParameter(id, id));
574  cmd.ExecuteNonQuery();
575  }
576 
577  // TODO: How do we deal with data from deleted assets? Probably not easily reapable unless we
578  // keep a reference count (?)
579  }
580  }
581 
582  return true;
583  }
584 
585  #endregion
586  }
587 }
bool[] AssetsExist(UUID[] uuids)
Check if the assets exist in the database.
void StoreAsset(AssetBase asset)
Create an asset in database, or update it if existing.
AssetBase GetAsset(UUID assetID)
Fetch Asset assetID from database
OpenSim.Server.Handlers.Simulation.Utils Utils
void Initialise()
Default-initialises the plugin
Ionic.Zlib.GZipStream GZipStream
Asset class. All Assets are reference by this class or a class derived from this class ...
Definition: AssetBase.cs:49
A management class for the MS SQL Storage Engine
Definition: PGSQLManager.cs:44
List< AssetMetadata > FetchAssetMetadataSet(int start, int count)
Returns a list of AssetMetadata objects. The list is a subset of the entire data set offset by start ...
This interface exists to distinguish between the normal IAssetDataPlugin and the one used by XAssetSe...
Ionic.Zlib.CompressionMode CompressionMode
bool ExistsAsset(UUID uuid)
Check if the asset exists in the database