FlotsamAssetCache.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  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. // Uncomment to make asset Get requests for existing
  28. // #define WAIT_ON_INPROGRESS_REQUESTS
  29. using System;
  30. using System.IO;
  31. using System.Collections.Generic;
  32. using System.Reflection;
  33. using System.Runtime.Serialization;
  34. using System.Runtime.Serialization.Formatters.Binary;
  35. using System.Threading;
  36. using System.Timers;
  37. using log4net;
  38. using Nini.Config;
  39. using Mono.Addins;
  40. using OpenMetaverse;
  41. using OpenSim.Framework;
  42. using OpenSim.Framework.Console;
  43. using OpenSim.Region.Framework.Interfaces;
  44. using OpenSim.Region.Framework.Scenes;
  45. using OpenSim.Services.Interfaces;
  46. [assembly: Addin("FlotsamAssetCache", "1.1")]
  47. [assembly: AddinDependency("OpenSim", "0.5")]
  48. namespace Flotsam.RegionModules.AssetCache
  49. {
  50. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")]
  51. public class FlotsamAssetCache : ISharedRegionModule, IImprovedAssetCache, IAssetService
  52. {
  53. private static readonly ILog m_log =
  54. LogManager.GetLogger(
  55. MethodBase.GetCurrentMethod().DeclaringType);
  56. private bool m_Enabled;
  57. private const string m_ModuleName = "FlotsamAssetCache";
  58. private const string m_DefaultCacheDirectory = m_ModuleName;
  59. private string m_CacheDirectory = m_DefaultCacheDirectory;
  60. private readonly List<char> m_InvalidChars = new List<char>();
  61. private int m_LogLevel = 1;
  62. private ulong m_HitRateDisplay = 1; // How often to display hit statistics, given in requests
  63. private static ulong m_Requests;
  64. private static ulong m_RequestsForInprogress;
  65. private static ulong m_DiskHits;
  66. private static ulong m_MemoryHits;
  67. private static double m_HitRateMemory;
  68. private static double m_HitRateFile;
  69. #if WAIT_ON_INPROGRESS_REQUESTS
  70. private Dictionary<string, ManualResetEvent> m_CurrentlyWriting = new Dictionary<string, ManualResetEvent>();
  71. private int m_WaitOnInprogressTimeout = 3000;
  72. #else
  73. private List<string> m_CurrentlyWriting = new List<string>();
  74. #endif
  75. private ExpiringCache<string, AssetBase> m_MemoryCache;
  76. private bool m_MemoryCacheEnabled = true;
  77. // Expiration is expressed in hours.
  78. private const double m_DefaultMemoryExpiration = 1.0;
  79. private const double m_DefaultFileExpiration = 48;
  80. private TimeSpan m_MemoryExpiration = TimeSpan.Zero;
  81. private TimeSpan m_FileExpiration = TimeSpan.Zero;
  82. private TimeSpan m_FileExpirationCleanupTimer = TimeSpan.Zero;
  83. private static int m_CacheDirectoryTiers = 1;
  84. private static int m_CacheDirectoryTierLen = 3;
  85. private static int m_CacheWarnAt = 30000;
  86. private System.Timers.Timer m_CacheCleanTimer;
  87. private IAssetService m_AssetService;
  88. private List<Scene> m_Scenes = new List<Scene>();
  89. private bool m_DeepScanBeforePurge;
  90. public FlotsamAssetCache()
  91. {
  92. m_InvalidChars.AddRange(Path.GetInvalidPathChars());
  93. m_InvalidChars.AddRange(Path.GetInvalidFileNameChars());
  94. }
  95. public Type ReplaceableInterface
  96. {
  97. get { return null; }
  98. }
  99. public string Name
  100. {
  101. get { return m_ModuleName; }
  102. }
  103. public void Initialise(IConfigSource source)
  104. {
  105. IConfig moduleConfig = source.Configs["Modules"];
  106. if (moduleConfig != null)
  107. {
  108. string name = moduleConfig.GetString("AssetCaching", String.Empty);
  109. if (name == Name)
  110. {
  111. m_MemoryCache = new ExpiringCache<string, AssetBase>();
  112. m_Enabled = true;
  113. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: {0} enabled", this.Name);
  114. IConfig assetConfig = source.Configs["AssetCache"];
  115. if (assetConfig == null)
  116. {
  117. m_log.Warn("[FLOTSAM ASSET CACHE]: AssetCache missing from OpenSim.ini, using defaults.");
  118. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Cache Directory", m_DefaultCacheDirectory);
  119. return;
  120. }
  121. m_CacheDirectory = assetConfig.GetString("CacheDirectory", m_DefaultCacheDirectory);
  122. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Cache Directory", m_DefaultCacheDirectory);
  123. m_MemoryCacheEnabled = assetConfig.GetBoolean("MemoryCacheEnabled", false);
  124. m_MemoryExpiration = TimeSpan.FromHours(assetConfig.GetDouble("MemoryCacheTimeout", m_DefaultMemoryExpiration));
  125. #if WAIT_ON_INPROGRESS_REQUESTS
  126. m_WaitOnInprogressTimeout = assetConfig.GetInt("WaitOnInprogressTimeout", 3000);
  127. #endif
  128. m_LogLevel = assetConfig.GetInt("LogLevel", 1);
  129. m_HitRateDisplay = (ulong)assetConfig.GetInt("HitRateDisplay", 1000);
  130. m_FileExpiration = TimeSpan.FromHours(assetConfig.GetDouble("FileCacheTimeout", m_DefaultFileExpiration));
  131. m_FileExpirationCleanupTimer = TimeSpan.FromHours(assetConfig.GetDouble("FileCleanupTimer", m_DefaultFileExpiration));
  132. if ((m_FileExpiration > TimeSpan.Zero) && (m_FileExpirationCleanupTimer > TimeSpan.Zero))
  133. {
  134. m_CacheCleanTimer = new System.Timers.Timer(m_FileExpirationCleanupTimer.TotalMilliseconds);
  135. m_CacheCleanTimer.AutoReset = true;
  136. m_CacheCleanTimer.Elapsed += CleanupExpiredFiles;
  137. lock (m_CacheCleanTimer)
  138. m_CacheCleanTimer.Start();
  139. }
  140. m_CacheDirectoryTiers = assetConfig.GetInt("CacheDirectoryTiers", 1);
  141. if (m_CacheDirectoryTiers < 1)
  142. {
  143. m_CacheDirectoryTiers = 1;
  144. }
  145. else if (m_CacheDirectoryTiers > 3)
  146. {
  147. m_CacheDirectoryTiers = 3;
  148. }
  149. m_CacheDirectoryTierLen = assetConfig.GetInt("CacheDirectoryTierLength", 3);
  150. if (m_CacheDirectoryTierLen < 1)
  151. {
  152. m_CacheDirectoryTierLen = 1;
  153. }
  154. else if (m_CacheDirectoryTierLen > 4)
  155. {
  156. m_CacheDirectoryTierLen = 4;
  157. }
  158. m_CacheWarnAt = assetConfig.GetInt("CacheWarnAt", 30000);
  159. m_DeepScanBeforePurge = assetConfig.GetBoolean("DeepScanBeforePurge", false);
  160. MainConsole.Instance.Commands.AddCommand(this.Name, true, "fcache status", "fcache status", "Display cache status", HandleConsoleCommand);
  161. MainConsole.Instance.Commands.AddCommand(this.Name, true, "fcache clear", "fcache clear [file] [memory]", "Remove all assets in the file and/or memory cache", HandleConsoleCommand);
  162. MainConsole.Instance.Commands.AddCommand(this.Name, true, "fcache assets", "fcache assets", "Attempt a deep scan and cache of all assets in all scenes", HandleConsoleCommand);
  163. MainConsole.Instance.Commands.AddCommand(this.Name, true, "fcache expire", "fcache expire <datetime>", "Purge cached assets older then the specified date/time", HandleConsoleCommand);
  164. }
  165. }
  166. }
  167. public void PostInitialise()
  168. {
  169. }
  170. public void Close()
  171. {
  172. }
  173. public void AddRegion(Scene scene)
  174. {
  175. if (m_Enabled)
  176. {
  177. scene.RegisterModuleInterface<IImprovedAssetCache>(this);
  178. m_Scenes.Add(scene);
  179. if (m_AssetService == null)
  180. {
  181. m_AssetService = scene.RequestModuleInterface<IAssetService>();
  182. }
  183. }
  184. }
  185. public void RemoveRegion(Scene scene)
  186. {
  187. if (m_Enabled)
  188. {
  189. scene.UnregisterModuleInterface<IImprovedAssetCache>(this);
  190. m_Scenes.Remove(scene);
  191. }
  192. }
  193. public void RegionLoaded(Scene scene)
  194. {
  195. }
  196. ////////////////////////////////////////////////////////////
  197. // IImprovedAssetCache
  198. //
  199. private void UpdateMemoryCache(string key, AssetBase asset)
  200. {
  201. if (m_MemoryCacheEnabled)
  202. {
  203. if (m_MemoryExpiration > TimeSpan.Zero)
  204. {
  205. m_MemoryCache.AddOrUpdate(key, asset, m_MemoryExpiration);
  206. }
  207. else
  208. {
  209. m_MemoryCache.AddOrUpdate(key, asset, DateTime.MaxValue);
  210. }
  211. }
  212. }
  213. public void Cache(AssetBase asset)
  214. {
  215. // TODO: Spawn this off to some seperate thread to do the actual writing
  216. if (asset != null)
  217. {
  218. UpdateMemoryCache(asset.ID, asset);
  219. string filename = GetFileName(asset.ID);
  220. try
  221. {
  222. // If the file is already cached, don't cache it, just touch it so access time is updated
  223. if (File.Exists(filename))
  224. {
  225. File.SetLastAccessTime(filename, DateTime.Now);
  226. } else {
  227. // Once we start writing, make sure we flag that we're writing
  228. // that object to the cache so that we don't try to write the
  229. // same file multiple times.
  230. lock (m_CurrentlyWriting)
  231. {
  232. #if WAIT_ON_INPROGRESS_REQUESTS
  233. if (m_CurrentlyWriting.ContainsKey(filename))
  234. {
  235. return;
  236. }
  237. else
  238. {
  239. m_CurrentlyWriting.Add(filename, new ManualResetEvent(false));
  240. }
  241. #else
  242. if (m_CurrentlyWriting.Contains(filename))
  243. {
  244. return;
  245. }
  246. else
  247. {
  248. m_CurrentlyWriting.Add(filename);
  249. }
  250. #endif
  251. }
  252. Util.FireAndForget(
  253. delegate { WriteFileCache(filename, asset); });
  254. }
  255. }
  256. catch (Exception e)
  257. {
  258. LogException(e);
  259. }
  260. }
  261. }
  262. public AssetBase Get(string id)
  263. {
  264. m_Requests++;
  265. AssetBase asset = null;
  266. if (m_MemoryCacheEnabled && m_MemoryCache.TryGetValue(id, out asset))
  267. {
  268. m_MemoryHits++;
  269. }
  270. else
  271. {
  272. string filename = GetFileName(id);
  273. if (File.Exists(filename))
  274. {
  275. FileStream stream = null;
  276. try
  277. {
  278. stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
  279. BinaryFormatter bformatter = new BinaryFormatter();
  280. asset = (AssetBase)bformatter.Deserialize(stream);
  281. UpdateMemoryCache(id, asset);
  282. m_DiskHits++;
  283. }
  284. catch (System.Runtime.Serialization.SerializationException e)
  285. {
  286. LogException(e);
  287. // If there was a problem deserializing the asset, the asset may
  288. // either be corrupted OR was serialized under an old format
  289. // {different version of AssetBase} -- we should attempt to
  290. // delete it and re-cache
  291. File.Delete(filename);
  292. }
  293. catch (Exception e)
  294. {
  295. LogException(e);
  296. }
  297. finally
  298. {
  299. if (stream != null)
  300. stream.Close();
  301. }
  302. }
  303. #if WAIT_ON_INPROGRESS_REQUESTS
  304. // Check if we're already downloading this asset. If so, try to wait for it to
  305. // download.
  306. if (m_WaitOnInprogressTimeout > 0)
  307. {
  308. m_RequestsForInprogress++;
  309. ManualResetEvent waitEvent;
  310. if (m_CurrentlyWriting.TryGetValue(filename, out waitEvent))
  311. {
  312. waitEvent.WaitOne(m_WaitOnInprogressTimeout);
  313. return Get(id);
  314. }
  315. }
  316. #else
  317. // Track how often we have the problem that an asset is requested while
  318. // it is still being downloaded by a previous request.
  319. if (m_CurrentlyWriting.Contains(filename))
  320. {
  321. m_RequestsForInprogress++;
  322. }
  323. #endif
  324. }
  325. if (((m_LogLevel >= 1)) && (m_HitRateDisplay != 0) && (m_Requests % m_HitRateDisplay == 0))
  326. {
  327. m_HitRateFile = (double)m_DiskHits / m_Requests * 100.0;
  328. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Cache Get :: {0} :: {1}", id, asset == null ? "Miss" : "Hit");
  329. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: File Hit Rate {0}% for {1} requests", m_HitRateFile.ToString("0.00"), m_Requests);
  330. if (m_MemoryCacheEnabled)
  331. {
  332. m_HitRateMemory = (double)m_MemoryHits / m_Requests * 100.0;
  333. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Memory Hit Rate {0}% for {1} requests", m_HitRateMemory.ToString("0.00"), m_Requests);
  334. }
  335. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: {0} unnessesary requests due to requests for assets that are currently downloading.", m_RequestsForInprogress);
  336. }
  337. return asset;
  338. }
  339. public void Expire(string id)
  340. {
  341. if (m_LogLevel >= 2)
  342. m_log.DebugFormat("[FLOTSAM ASSET CACHE]: Expiring Asset {0}.", id);
  343. try
  344. {
  345. string filename = GetFileName(id);
  346. if (File.Exists(filename))
  347. {
  348. File.Delete(filename);
  349. }
  350. if (m_MemoryCacheEnabled)
  351. m_MemoryCache.Remove(id);
  352. }
  353. catch (Exception e)
  354. {
  355. LogException(e);
  356. }
  357. }
  358. public void Clear()
  359. {
  360. if (m_LogLevel >= 2)
  361. m_log.Debug("[FLOTSAM ASSET CACHE]: Clearing Cache.");
  362. foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
  363. {
  364. Directory.Delete(dir);
  365. }
  366. if (m_MemoryCacheEnabled)
  367. m_MemoryCache.Clear();
  368. }
  369. private void CleanupExpiredFiles(object source, ElapsedEventArgs e)
  370. {
  371. if (m_LogLevel >= 2)
  372. m_log.DebugFormat("[FLOTSAM ASSET CACHE]: Checking for expired files older then {0}.", m_FileExpiration.ToString());
  373. // Purge all files last accessed prior to this point
  374. DateTime purgeLine = DateTime.Now - m_FileExpiration;
  375. // An optional deep scan at this point will ensure assets present in scenes,
  376. // or referenced by objects in the scene, but not recently accessed
  377. // are not purged.
  378. if (m_DeepScanBeforePurge)
  379. {
  380. CacheScenes();
  381. }
  382. foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
  383. {
  384. CleanExpiredFiles(dir, purgeLine);
  385. }
  386. }
  387. /// <summary>
  388. /// Recurses through specified directory checking for asset files last
  389. /// accessed prior to the specified purge line and deletes them. Also
  390. /// removes empty tier directories.
  391. /// </summary>
  392. /// <param name="dir"></param>
  393. private void CleanExpiredFiles(string dir, DateTime purgeLine)
  394. {
  395. foreach (string file in Directory.GetFiles(dir))
  396. {
  397. if (File.GetLastAccessTime(file) < purgeLine)
  398. {
  399. File.Delete(file);
  400. }
  401. }
  402. // Recurse into lower tiers
  403. foreach (string subdir in Directory.GetDirectories(dir))
  404. {
  405. CleanExpiredFiles(subdir, purgeLine);
  406. }
  407. // Check if a tier directory is empty, if so, delete it
  408. int dirSize = Directory.GetFiles(dir).Length + Directory.GetDirectories(dir).Length;
  409. if (dirSize == 0)
  410. {
  411. Directory.Delete(dir);
  412. }
  413. else if (dirSize >= m_CacheWarnAt)
  414. {
  415. m_log.WarnFormat("[FLOTSAM ASSET CACHE]: Cache folder exceeded CacheWarnAt limit {0} {1}. Suggest increasing tiers, tier length, or reducing cache expiration", dir, dirSize);
  416. }
  417. }
  418. /// <summary>
  419. /// Determines the filename for an AssetID stored in the file cache
  420. /// </summary>
  421. /// <param name="id"></param>
  422. /// <returns></returns>
  423. private string GetFileName(string id)
  424. {
  425. // Would it be faster to just hash the darn thing?
  426. foreach (char c in m_InvalidChars)
  427. {
  428. id = id.Replace(c, '_');
  429. }
  430. string path = m_CacheDirectory;
  431. for (int p = 1; p <= m_CacheDirectoryTiers; p++)
  432. {
  433. string pathPart = id.Substring((p - 1) * m_CacheDirectoryTierLen, m_CacheDirectoryTierLen);
  434. path = Path.Combine(path, pathPart);
  435. }
  436. return Path.Combine(path, id);
  437. }
  438. /// <summary>
  439. /// Writes a file to the file cache, creating any nessesary
  440. /// tier directories along the way
  441. /// </summary>
  442. /// <param name="filename"></param>
  443. /// <param name="asset"></param>
  444. private void WriteFileCache(string filename, AssetBase asset)
  445. {
  446. Stream stream = null;
  447. // Make sure the target cache directory exists
  448. string directory = Path.GetDirectoryName(filename);
  449. // Write file first to a temp name, so that it doesn't look
  450. // like it's already cached while it's still writing.
  451. string tempname = Path.Combine(directory, Path.GetRandomFileName());
  452. try
  453. {
  454. if (!Directory.Exists(directory))
  455. {
  456. Directory.CreateDirectory(directory);
  457. }
  458. stream = File.Open(tempname, FileMode.Create);
  459. BinaryFormatter bformatter = new BinaryFormatter();
  460. bformatter.Serialize(stream, asset);
  461. stream.Close();
  462. // Now that it's written, rename it so that it can be found.
  463. File.Move(tempname, filename);
  464. if (m_LogLevel >= 2)
  465. m_log.DebugFormat("[FLOTSAM ASSET CACHE]: Cache Stored :: {0}", asset.ID);
  466. }
  467. catch (Exception e)
  468. {
  469. LogException(e);
  470. }
  471. finally
  472. {
  473. if (stream != null)
  474. stream.Close();
  475. // Even if the write fails with an exception, we need to make sure
  476. // that we release the lock on that file, otherwise it'll never get
  477. // cached
  478. lock (m_CurrentlyWriting)
  479. {
  480. #if WAIT_ON_INPROGRESS_REQUESTS
  481. ManualResetEvent waitEvent;
  482. if (m_CurrentlyWriting.TryGetValue(filename, out waitEvent))
  483. {
  484. m_CurrentlyWriting.Remove(filename);
  485. waitEvent.Set();
  486. }
  487. #else
  488. if (m_CurrentlyWriting.Contains(filename))
  489. {
  490. m_CurrentlyWriting.Remove(filename);
  491. }
  492. #endif
  493. }
  494. }
  495. }
  496. private static void LogException(Exception e)
  497. {
  498. string[] text = e.ToString().Split(new char[] { '\n' });
  499. foreach (string t in text)
  500. {
  501. m_log.ErrorFormat("[FLOTSAM ASSET CACHE]: {0} ", t);
  502. }
  503. }
  504. /// <summary>
  505. /// Scan through the file cache, and return number of assets currently cached.
  506. /// </summary>
  507. /// <param name="dir"></param>
  508. /// <returns></returns>
  509. private int GetFileCacheCount(string dir)
  510. {
  511. int count = Directory.GetFiles(dir).Length;
  512. foreach (string subdir in Directory.GetDirectories(dir))
  513. {
  514. count += GetFileCacheCount(subdir);
  515. }
  516. return count;
  517. }
  518. /// <summary>
  519. /// This notes the last time the Region had a deep asset scan performed on it.
  520. /// </summary>
  521. /// <param name="RegionID"></param>
  522. private void StampRegionStatusFile(UUID RegionID)
  523. {
  524. string RegionCacheStatusFile = Path.Combine(m_CacheDirectory, "RegionStatus_" + RegionID.ToString() + ".fac");
  525. if (File.Exists(RegionCacheStatusFile))
  526. {
  527. File.SetLastWriteTime(RegionCacheStatusFile, DateTime.Now);
  528. }
  529. else
  530. {
  531. File.WriteAllText(RegionCacheStatusFile, "Please do not delete this file unless you are manually clearing your Flotsam Asset Cache.");
  532. }
  533. }
  534. /// <summary>
  535. /// Iterates through all Scenes, doing a deep scan through assets
  536. /// to cache all assets present in the scene or referenced by assets
  537. /// in the scene
  538. /// </summary>
  539. /// <returns></returns>
  540. private int CacheScenes()
  541. {
  542. UuidGatherer gatherer = new UuidGatherer(m_AssetService);
  543. Dictionary<UUID, int> assets = new Dictionary<UUID, int>();
  544. foreach (Scene s in m_Scenes)
  545. {
  546. StampRegionStatusFile(s.RegionInfo.RegionID);
  547. s.ForEachSOG(delegate(SceneObjectGroup e)
  548. {
  549. gatherer.GatherAssetUuids(e, assets);
  550. }
  551. );
  552. }
  553. foreach (UUID assetID in assets.Keys)
  554. {
  555. string filename = GetFileName(assetID.ToString());
  556. if (File.Exists(filename))
  557. {
  558. File.SetLastAccessTime(filename, DateTime.Now);
  559. }
  560. else
  561. {
  562. m_AssetService.Get(assetID.ToString());
  563. }
  564. }
  565. return assets.Keys.Count;
  566. }
  567. /// <summary>
  568. /// Deletes all cache contents
  569. /// </summary>
  570. private void ClearFileCache()
  571. {
  572. foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
  573. {
  574. try
  575. {
  576. Directory.Delete(dir, true);
  577. }
  578. catch (Exception e)
  579. {
  580. LogException(e);
  581. }
  582. }
  583. foreach (string file in Directory.GetFiles(m_CacheDirectory))
  584. {
  585. try
  586. {
  587. File.Delete(file);
  588. }
  589. catch (Exception e)
  590. {
  591. LogException(e);
  592. }
  593. }
  594. }
  595. #region Console Commands
  596. private void HandleConsoleCommand(string module, string[] cmdparams)
  597. {
  598. if (cmdparams.Length >= 2)
  599. {
  600. string cmd = cmdparams[1];
  601. switch (cmd)
  602. {
  603. case "status":
  604. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Memory Cache : {0} assets", m_MemoryCache.Count);
  605. int fileCount = GetFileCacheCount(m_CacheDirectory);
  606. m_log.InfoFormat("[FLOTSAM ASSET CACHE] File Cache : {0} assets", fileCount);
  607. foreach (string s in Directory.GetFiles(m_CacheDirectory, "*.fac"))
  608. {
  609. m_log.Info("[FLOTSAM ASSET CACHE] Deep Scans were performed on the following regions:");
  610. string RegionID = s.Remove(0,s.IndexOf("_")).Replace(".fac","");
  611. DateTime RegionDeepScanTMStamp = File.GetLastWriteTime(s);
  612. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Region: {0}, {1}", RegionID, RegionDeepScanTMStamp.ToString("MM/dd/yyyy hh:mm:ss"));
  613. }
  614. break;
  615. case "clear":
  616. if (cmdparams.Length < 3)
  617. {
  618. m_log.Warn("[FLOTSAM ASSET CACHE] Please specify memory and/or file cache.");
  619. break;
  620. }
  621. foreach (string s in cmdparams)
  622. {
  623. if (s.ToLower() == "memory")
  624. {
  625. m_MemoryCache.Clear();
  626. m_log.Info("[FLOTSAM ASSET CACHE] Memory cache cleared.");
  627. }
  628. else if (s.ToLower() == "file")
  629. {
  630. ClearFileCache();
  631. m_log.Info("[FLOTSAM ASSET CACHE] File cache cleared.");
  632. }
  633. }
  634. break;
  635. case "assets":
  636. m_log.Info("[FLOTSAM ASSET CACHE] Caching all assets, in all scenes.");
  637. Util.FireAndForget(delegate {
  638. int assetsCached = CacheScenes();
  639. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Completed Scene Caching, {0} assets found.", assetsCached);
  640. });
  641. break;
  642. case "expire":
  643. if (cmdparams.Length >= 3)
  644. {
  645. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Invalid parameters for Expire, please specify a valid date & time", cmd);
  646. break;
  647. }
  648. string s_expirationDate = "";
  649. DateTime expirationDate;
  650. if (cmdparams.Length > 3)
  651. {
  652. s_expirationDate = string.Join(" ", cmdparams, 2, cmdparams.Length - 2);
  653. }
  654. else
  655. {
  656. s_expirationDate = cmdparams[2];
  657. }
  658. if (!DateTime.TryParse(s_expirationDate, out expirationDate))
  659. {
  660. m_log.InfoFormat("[FLOTSAM ASSET CACHE] {0} is not a valid date & time", cmd);
  661. break;
  662. }
  663. CleanExpiredFiles(m_CacheDirectory, expirationDate);
  664. break;
  665. default:
  666. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Unknown command {0}", cmd);
  667. break;
  668. }
  669. }
  670. else if (cmdparams.Length == 1)
  671. {
  672. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache status - Display cache status");
  673. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache clearmem - Remove all assets cached in memory");
  674. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache clearfile - Remove all assets cached on disk");
  675. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache cachescenes - Attempt a deep cache of all assets in all scenes");
  676. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache <datetime> - Purge assets older then the specified date & time");
  677. }
  678. }
  679. #endregion
  680. #region IAssetService Members
  681. public AssetMetadata GetMetadata(string id)
  682. {
  683. AssetBase asset = Get(id);
  684. return asset.Metadata;
  685. }
  686. public byte[] GetData(string id)
  687. {
  688. AssetBase asset = Get(id);
  689. return asset.Data;
  690. }
  691. public bool Get(string id, object sender, AssetRetrieved handler)
  692. {
  693. AssetBase asset = Get(id);
  694. handler(id, sender, asset);
  695. return true;
  696. }
  697. public string Store(AssetBase asset)
  698. {
  699. if (asset.FullID == UUID.Zero)
  700. {
  701. asset.FullID = UUID.Random();
  702. }
  703. Cache(asset);
  704. return asset.ID;
  705. }
  706. public bool UpdateContent(string id, byte[] data)
  707. {
  708. AssetBase asset = Get(id);
  709. asset.Data = data;
  710. Cache(asset);
  711. return true;
  712. }
  713. public bool Delete(string id)
  714. {
  715. Expire(id);
  716. return true;
  717. }
  718. #endregion
  719. }
  720. }