29 using System.Diagnostics;
30 using System.Collections.Generic;
32 using System.IO.Compression;
34 using System.Threading;
35 using System.Reflection;
37 using OpenSim.Framework;
38 using OpenSim.Framework.Serialization.External;
39 using OpenSim.Framework.Console;
40 using OpenSim.Server.Base;
41 using OpenSim.Services.Base;
42 using OpenSim.Services.Interfaces;
46 using System.Security.Cryptography;
52 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
54 static System.Text.ASCIIEncoding enc =
new System.Text.ASCIIEncoding();
55 static SHA256CryptoServiceProvider SHA256 =
new SHA256CryptoServiceProvider();
57 static byte[] ToCString(
string s)
59 byte[] ret = enc.GetBytes(s);
60 Array.Resize(ref ret, ret.Length + 1);
61 ret[ret.Length - 1] = 0;
72 protected object m_readLock =
new object();
73 protected object m_statsLock =
new object();
74 protected int m_readCount = 0;
75 protected int m_readTicks = 0;
76 protected int m_missingAssets = 0;
77 protected int m_missingAssetsFS = 0;
79 protected bool m_useOsgridFormat =
false;
81 private static bool m_Initialized;
82 private bool m_MainInstance;
85 : this(config,
"AssetService")
94 m_MainInstance =
true;
96 MainConsole.Instance.Commands.AddCommand(
"fs",
false,
97 "show assets",
"show assets",
"Show asset stats",
99 MainConsole.Instance.Commands.AddCommand(
"fs",
false,
100 "show digest",
"show digest <ID>",
"Show asset digest",
102 MainConsole.Instance.Commands.AddCommand(
"fs",
false,
103 "delete asset",
"delete asset <ID>",
104 "Delete asset from database",
106 MainConsole.Instance.Commands.AddCommand(
"fs",
false,
107 "import",
"import <conn> <table> [<start> <count>]",
108 "Import legacy assets",
110 MainConsole.Instance.Commands.AddCommand(
"fs",
false,
111 "force import",
"force import <conn> <table> [<start> <count>]",
112 "Import legacy assets, overwriting current content",
116 IConfig assetConfig = config.Configs[configName];
118 if (assetConfig == null)
119 throw new Exception(
"No AssetService configuration");
122 string dllName = assetConfig.GetString(
"StorageProvider", string.Empty);
123 string connectionString = assetConfig.GetString(
"ConnectionString", string.Empty);
124 string realm = assetConfig.GetString(
"Realm",
"fsassets");
126 int SkipAccessTimeDays = assetConfig.GetInt(
"DaysBetweenAccessTimeUpdates", 0);
129 IConfig dbConfig = config.Configs[
"DatabaseService"];
131 if (dbConfig != null)
133 if (dllName == String.Empty)
134 dllName = dbConfig.GetString(
"StorageProvider", String.Empty);
136 if (connectionString == String.Empty)
137 connectionString = dbConfig.GetString(
"ConnectionString", String.Empty);
141 if (dllName.Equals(String.Empty))
142 throw new Exception(
"No StorageProvider configured");
144 if (connectionString.Equals(String.Empty))
145 throw new Exception(
"Missing database connection string");
148 m_DataConnector = LoadPlugin<IFSAssetDataPlugin>(dllName);
150 if (m_DataConnector == null)
151 throw new Exception(
string.Format(
"Could not find a storage interface in the module {0}", dllName));
154 m_DataConnector.Initialise(connectionString, realm, SkipAccessTimeDays);
157 string str = assetConfig.GetString(
"FallbackService", string.Empty);
159 if (str !=
string.Empty)
161 object[] args =
new object[] { config };
162 m_FallbackService = LoadPlugin<IAssetService>(str, args);
163 if (m_FallbackService != null)
165 m_log.Info(
"[FSASSETS]: Fallback service loaded");
169 m_log.Error(
"[FSASSETS]: Failed to load fallback service");
174 m_SpoolDirectory = assetConfig.GetString(
"SpoolDirectory",
"/tmp");
176 string spoolTmp = Path.Combine(m_SpoolDirectory,
"spool");
178 Directory.CreateDirectory(spoolTmp);
180 m_FSBase = assetConfig.GetString(
"BaseDirectory", String.Empty);
181 if (m_FSBase == String.Empty)
183 m_log.ErrorFormat(
"[FSASSETS]: BaseDirectory not specified");
184 throw new Exception(
"Configuration error");
187 m_useOsgridFormat = assetConfig.GetBoolean(
"UseOsgridFormat", m_useOsgridFormat);
191 string loader = assetConfig.GetString(
"DefaultAssetLoader", string.Empty);
192 if (loader !=
string.Empty)
194 m_AssetLoader = LoadPlugin<IAssetLoader>(loader);
195 string loaderArgs = assetConfig.GetString(
"AssetLoaderArgs", string.Empty);
196 m_log.InfoFormat(
"[FSASSETS]: Loading default asset set from {0}", loaderArgs);
197 m_AssetLoader.ForEachDefaultXmlAsset(loaderArgs,
204 m_WriterThread =
new Thread(Writer);
205 m_WriterThread.Start();
206 m_StatsThread =
new Thread(Stats);
207 m_StatsThread.Start();
210 m_log.Info(
"[FSASSETS]: FS asset service enabled");
223 double avg = (double)m_readTicks / (
double)m_readCount;
226 m_log.InfoFormat(
"[FSASSETS]: Read stats: {0} files, {1} ticks, avg {2:F2}, missing {3}, FS {4}", m_readCount, m_readTicks, (double)m_readTicks / (
double)m_readCount, m_missingAssets, m_missingAssetsFS);
231 m_missingAssetsFS = 0;
236 private void Writer()
238 m_log.Info(
"[ASSET]: Writer started");
242 string[] files = Directory.GetFiles(m_SpoolDirectory);
244 if (files.Length > 0)
246 int tickCount = Environment.TickCount;
247 for (
int i = 0 ; i < files.Length ; i++)
249 string hash = Path.GetFileNameWithoutExtension(files[i]);
250 string s = HashToFile(hash);
251 string diskFile = Path.Combine(m_FSBase, s);
260 Directory.CreateDirectory(Path.GetDirectoryName(diskFile));
264 catch (System.IO.IOException)
269 string d = Path.GetDirectoryName(diskFile);
275 Console.WriteLine(d);
278 Directory.CreateDirectory(Path.GetDirectoryName(d));
280 catch (System.IO.IOException)
282 d = Path.GetDirectoryName(d);
297 Console.WriteLine(d);
299 FileAttributes attr = File.GetAttributes(d);
301 if ((attr & FileAttributes.Directory) == 0)
311 m_log.ErrorFormat(
"[ASSET]: Could not resolve path creation error for {0}", diskFile);
319 byte[] data = File.ReadAllBytes(files[i]);
323 gz.Write(data, 0, data.Length);
326 File.Delete(files[i]);
330 catch(System.IO.IOException e)
332 if (e.Message.StartsWith(
"Win32 IO returned ERROR_ALREADY_EXISTS"))
333 File.Delete(files[i]);
340 int totalTicks = System.Environment.TickCount - tickCount;
343 m_log.InfoFormat(
"[ASSET]: Write cycle complete, {0} files, {1} ticks, avg {2:F2}", files.Length, totalTicks, (double)totalTicks / (
double)files.Length);
351 string GetSHA256Hash(byte[] data)
353 byte[] hash = SHA256.ComputeHash(data);
355 return BitConverter.ToString(hash).Replace(
"-",
String.Empty);
360 if (hash == null || hash.Length < 10)
363 if (m_useOsgridFormat)
368 return Path.Combine(hash.Substring(0, 3),
369 Path.Combine(hash.Substring(3, 3)));
379 return Path.Combine(hash.Substring(0, 2),
380 Path.Combine(hash.Substring(2, 2),
381 Path.Combine(hash.Substring(4, 2),
382 hash.Substring(6, 4))));
386 private bool AssetExists(
string hash)
388 string s = HashToFile(hash);
389 string diskFile = Path.Combine(m_FSBase, s);
391 if (
File.Exists(diskFile +
".gz") || File.Exists(diskFile))
399 UUID[] uuid = Array.ConvertAll(ids,
id => UUID.Parse(id));
400 return m_DataConnector.AssetsExist(uuid);
405 return Path.Combine(HashToPath(hash), hash);
412 return Get(
id, out hash);
415 private AssetBase Get(
string id, out
string sha)
417 string hash = string.Empty;
419 int startTime = System.Environment.TickCount;
424 metadata = m_DataConnector.Get(id, out hash);
429 if (metadata == null)
432 if (m_FallbackService != null)
434 asset = m_FallbackService.Get(id);
437 asset.Metadata.ContentType =
438 SLUtil.SLAssetTypeToContentType((int)asset.
Type);
439 sha = GetSHA256Hash(asset.
Data);
440 m_log.InfoFormat(
"[FSASSETS]: Added asset {0} from fallback to local store", id);
452 newAsset.Metadata = metadata;
455 newAsset.Data = GetFsData(hash);
456 if (newAsset.
Data.Length == 0)
459 if (m_FallbackService != null)
461 asset = m_FallbackService.Get(id);
464 asset.Metadata.ContentType =
465 SLUtil.SLAssetTypeToContentType((int)asset.
Type);
466 sha = GetSHA256Hash(asset.
Data);
467 m_log.InfoFormat(
"[FSASSETS]: Added asset {0} from fallback to local store", id);
478 if (asset.
Type == (
int)AssetType.Object && asset.
Data != null)
480 string xml = ExternalRepresentationUtils.SanitizeXml(Utils.BytesToString(asset.Data));
481 asset.Data = Utils.StringToBytes(xml);
489 m_readTicks += Environment.TickCount - startTime;
495 if (newAsset.
Type == (
int)AssetType.Object && newAsset.
Data != null)
497 string xml = ExternalRepresentationUtils.SanitizeXml(Utils.BytesToString(newAsset.Data));
498 newAsset.Data = Utils.StringToBytes(xml);
503 catch (Exception exception)
505 m_log.Error(exception.ToString());
515 return m_DataConnector.Get(id, out hash);
521 if (m_DataConnector.Get(
id, out hash) == null)
524 return GetFsData(hash);
531 handler(
id, sender, asset);
538 string spoolFile = Path.Combine(m_SpoolDirectory, hash +
".asset");
540 if (
File.Exists(spoolFile))
544 byte[] content = File.ReadAllBytes(spoolFile);
553 string file = HashToFile(hash);
554 string diskFile = Path.Combine(m_FSBase, file);
556 if (
File.Exists(diskFile +
".gz"))
562 using (MemoryStream ms =
new MemoryStream())
564 byte[] data =
new byte[32768];
569 bytesRead = gz.Read(data, 0, 32768);
571 ms.Write(data, 0, bytesRead);
572 }
while (bytesRead > 0);
583 else if (
File.Exists(diskFile))
587 byte[] content = File.ReadAllBytes(diskFile);
601 return Store(asset,
false);
604 private string Store(
AssetBase asset,
bool force)
606 int tickCount = Environment.TickCount;
607 string hash = GetSHA256Hash(asset.
Data);
609 if (!AssetExists(hash))
611 string tempFile = Path.Combine(Path.Combine(m_SpoolDirectory,
"spool"), hash +
".asset");
612 string finalFile = Path.Combine(m_SpoolDirectory, hash +
".asset");
614 if (!
File.Exists(finalFile))
618 if (asset.
Type == (
int)AssetType.Object && asset.Data != null)
620 string xml = ExternalRepresentationUtils.SanitizeXml(Utils.BytesToString(asset.Data));
621 asset.Data = Utils.StringToBytes(xml);
624 FileStream fs = File.Create(tempFile);
626 fs.Write(asset.Data, 0, asset.Data.Length);
630 File.Move(tempFile, finalFile);
634 if (asset.
ID ==
string.Empty)
636 if (asset.
FullID == UUID.Zero)
638 asset.FullID = UUID.Random();
640 asset.ID = asset.FullID.ToString();
644 UUID uuid = UUID.Zero;
645 if (
UUID.TryParse(asset.
ID, out uuid))
651 asset.FullID = UUID.Random();
655 if (!m_DataConnector.Store(asset.
Metadata, hash))
657 return UUID.Zero.ToString();
686 m_DataConnector.Delete(id);
691 private void HandleShowAssets(
string module,
string[] args)
693 int num = m_DataConnector.Count();
694 MainConsole.Instance.Output(string.Format(
"Total asset count: {0}", num));
697 private void HandleShowDigest(
string module,
string[] args)
701 MainConsole.Instance.Output(
"Syntax: show digest <ID>");
706 AssetBase asset = Get(args[2], out hash);
708 if (asset == null || asset.
Data.Length == 0)
710 MainConsole.Instance.Output(
"Asset not found");
716 MainConsole.Instance.Output(String.Format(
"Name: {0}", asset.Name));
717 MainConsole.Instance.Output(String.Format(
"Description: {0}", asset.Description));
718 MainConsole.Instance.Output(String.Format(
"Type: {0}", asset.Type));
719 MainConsole.Instance.Output(String.Format(
"Content-type: {0}", asset.Metadata.ContentType));
720 MainConsole.Instance.Output(String.Format(
"Flags: {0}", asset.Metadata.Flags.ToString()));
721 MainConsole.Instance.Output(String.Format(
"FS file: {0}", HashToFile(hash)));
723 for (i = 0 ; i < 5 ; i++)
726 if (asset.
Data.Length <= off)
729 if (asset.
Data.Length < off + len)
730 len = asset.Data.Length - off;
732 byte[] line =
new byte[len];
733 Array.Copy(asset.Data, off, line, 0, len);
735 string text = BitConverter.ToString(line);
736 MainConsole.Instance.Output(String.Format(
"{0:x4}: {1}", off, text));
740 private void HandleDeleteAsset(
string module,
string[] args)
744 MainConsole.Instance.Output(
"Syntax: delete asset <ID>");
750 if (asset == null || asset.
Data.Length == 0)
752 MainConsole.Instance.Output(
"Asset not found");
756 m_DataConnector.Delete(args[2]);
758 MainConsole.Instance.Output(
"Asset deleted");
761 private void HandleImportAssets(
string module,
string[] args)
764 if (args[0] ==
"force")
767 List<string> list =
new List<string>(args);
769 args = list.ToArray();
773 MainConsole.Instance.Output(
"Syntax: import <conn> <table> [<start> <count>]");
777 string conn = args[1];
778 string table = args[2];
783 start = Convert.ToInt32(args[3]);
787 count = Convert.ToInt32(args[4]);
789 m_DataConnector.Import(conn, table, start, count, force,
new FSStoreDelegate(Store));
virtual bool Delete(string id)
Delete an asset
delegate string FSStoreDelegate(AssetBase asset, bool force)
virtual string Store(AssetBase asset)
Creates a new asset
bool UpdateContent(string id, byte[] data)
Update an asset's content
virtual byte[] GetData(string id)
Get an asset's data, ignoring the metadata.
AssetBase GetCached(string id)
Synchronously fetches an asset from the local cache only.
FSAssetConnector(IConfigSource config)
virtual AssetMetadata GetMetadata(string id)
Get an asset's metadata
string HashToPath(string hash)
virtual AssetBase Get(string id)
Get an asset synchronously.
sbyte Type
(sbyte) AssetType enum
Ionic.Zlib.GZipStream GZipStream
byte[] GetFsData(string hash)
Asset class. All Assets are reference by this class or a class derived from this class ...
FSAssetConnector(IConfigSource config, string configName)
bool Get(string id, Object sender, AssetRetrieved handler)
Get an asset synchronously or asynchronously (depending on whether it is locally cached) and fire a c...
IAssetService m_FallbackService
virtual bool[] AssetsExist(string[] ids)
Check if assets exist in the database.
Interactive OpenSim region server
Ionic.Zlib.CompressionMode CompressionMode
string HashToFile(string hash)
delegate void AssetRetrieved(string id, Object sender, AssetBase asset)
string ID
Asset MetaData ID (transferring from UUID to string ID)