FlotsamAssetCache.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  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 = 0;
  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", 0);
  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 AssetBase GetCached(string id)
  340. {
  341. return Get(id);
  342. }
  343. public void Expire(string id)
  344. {
  345. if (m_LogLevel >= 2)
  346. m_log.DebugFormat("[FLOTSAM ASSET CACHE]: Expiring Asset {0}.", id);
  347. try
  348. {
  349. string filename = GetFileName(id);
  350. if (File.Exists(filename))
  351. {
  352. File.Delete(filename);
  353. }
  354. if (m_MemoryCacheEnabled)
  355. m_MemoryCache.Remove(id);
  356. }
  357. catch (Exception e)
  358. {
  359. LogException(e);
  360. }
  361. }
  362. public void Clear()
  363. {
  364. if (m_LogLevel >= 2)
  365. m_log.Debug("[FLOTSAM ASSET CACHE]: Clearing Cache.");
  366. foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
  367. {
  368. Directory.Delete(dir);
  369. }
  370. if (m_MemoryCacheEnabled)
  371. m_MemoryCache.Clear();
  372. }
  373. private void CleanupExpiredFiles(object source, ElapsedEventArgs e)
  374. {
  375. if (m_LogLevel >= 2)
  376. m_log.DebugFormat("[FLOTSAM ASSET CACHE]: Checking for expired files older then {0}.", m_FileExpiration.ToString());
  377. // Purge all files last accessed prior to this point
  378. DateTime purgeLine = DateTime.Now - m_FileExpiration;
  379. // An optional deep scan at this point will ensure assets present in scenes,
  380. // or referenced by objects in the scene, but not recently accessed
  381. // are not purged.
  382. if (m_DeepScanBeforePurge)
  383. {
  384. CacheScenes();
  385. }
  386. foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
  387. {
  388. CleanExpiredFiles(dir, purgeLine);
  389. }
  390. }
  391. /// <summary>
  392. /// Recurses through specified directory checking for asset files last
  393. /// accessed prior to the specified purge line and deletes them. Also
  394. /// removes empty tier directories.
  395. /// </summary>
  396. /// <param name="dir"></param>
  397. private void CleanExpiredFiles(string dir, DateTime purgeLine)
  398. {
  399. foreach (string file in Directory.GetFiles(dir))
  400. {
  401. if (File.GetLastAccessTime(file) < purgeLine)
  402. {
  403. File.Delete(file);
  404. }
  405. }
  406. // Recurse into lower tiers
  407. foreach (string subdir in Directory.GetDirectories(dir))
  408. {
  409. CleanExpiredFiles(subdir, purgeLine);
  410. }
  411. // Check if a tier directory is empty, if so, delete it
  412. int dirSize = Directory.GetFiles(dir).Length + Directory.GetDirectories(dir).Length;
  413. if (dirSize == 0)
  414. {
  415. Directory.Delete(dir);
  416. }
  417. else if (dirSize >= m_CacheWarnAt)
  418. {
  419. m_log.WarnFormat("[FLOTSAM ASSET CACHE]: Cache folder exceeded CacheWarnAt limit {0} {1}. Suggest increasing tiers, tier length, or reducing cache expiration", dir, dirSize);
  420. }
  421. }
  422. /// <summary>
  423. /// Determines the filename for an AssetID stored in the file cache
  424. /// </summary>
  425. /// <param name="id"></param>
  426. /// <returns></returns>
  427. private string GetFileName(string id)
  428. {
  429. // Would it be faster to just hash the darn thing?
  430. foreach (char c in m_InvalidChars)
  431. {
  432. id = id.Replace(c, '_');
  433. }
  434. string path = m_CacheDirectory;
  435. for (int p = 1; p <= m_CacheDirectoryTiers; p++)
  436. {
  437. string pathPart = id.Substring((p - 1) * m_CacheDirectoryTierLen, m_CacheDirectoryTierLen);
  438. path = Path.Combine(path, pathPart);
  439. }
  440. return Path.Combine(path, id);
  441. }
  442. /// <summary>
  443. /// Writes a file to the file cache, creating any nessesary
  444. /// tier directories along the way
  445. /// </summary>
  446. /// <param name="filename"></param>
  447. /// <param name="asset"></param>
  448. private void WriteFileCache(string filename, AssetBase asset)
  449. {
  450. Stream stream = null;
  451. // Make sure the target cache directory exists
  452. string directory = Path.GetDirectoryName(filename);
  453. // Write file first to a temp name, so that it doesn't look
  454. // like it's already cached while it's still writing.
  455. string tempname = Path.Combine(directory, Path.GetRandomFileName());
  456. try
  457. {
  458. if (!Directory.Exists(directory))
  459. {
  460. Directory.CreateDirectory(directory);
  461. }
  462. stream = File.Open(tempname, FileMode.Create);
  463. BinaryFormatter bformatter = new BinaryFormatter();
  464. bformatter.Serialize(stream, asset);
  465. stream.Close();
  466. // Now that it's written, rename it so that it can be found.
  467. File.Move(tempname, filename);
  468. if (m_LogLevel >= 2)
  469. m_log.DebugFormat("[FLOTSAM ASSET CACHE]: Cache Stored :: {0}", asset.ID);
  470. }
  471. catch (Exception e)
  472. {
  473. LogException(e);
  474. }
  475. finally
  476. {
  477. if (stream != null)
  478. stream.Close();
  479. // Even if the write fails with an exception, we need to make sure
  480. // that we release the lock on that file, otherwise it'll never get
  481. // cached
  482. lock (m_CurrentlyWriting)
  483. {
  484. #if WAIT_ON_INPROGRESS_REQUESTS
  485. ManualResetEvent waitEvent;
  486. if (m_CurrentlyWriting.TryGetValue(filename, out waitEvent))
  487. {
  488. m_CurrentlyWriting.Remove(filename);
  489. waitEvent.Set();
  490. }
  491. #else
  492. if (m_CurrentlyWriting.Contains(filename))
  493. {
  494. m_CurrentlyWriting.Remove(filename);
  495. }
  496. #endif
  497. }
  498. }
  499. }
  500. private static void LogException(Exception e)
  501. {
  502. string[] text = e.ToString().Split(new char[] { '\n' });
  503. foreach (string t in text)
  504. {
  505. m_log.ErrorFormat("[FLOTSAM ASSET CACHE]: {0} ", t);
  506. }
  507. }
  508. /// <summary>
  509. /// Scan through the file cache, and return number of assets currently cached.
  510. /// </summary>
  511. /// <param name="dir"></param>
  512. /// <returns></returns>
  513. private int GetFileCacheCount(string dir)
  514. {
  515. int count = Directory.GetFiles(dir).Length;
  516. foreach (string subdir in Directory.GetDirectories(dir))
  517. {
  518. count += GetFileCacheCount(subdir);
  519. }
  520. return count;
  521. }
  522. /// <summary>
  523. /// This notes the last time the Region had a deep asset scan performed on it.
  524. /// </summary>
  525. /// <param name="RegionID"></param>
  526. private void StampRegionStatusFile(UUID RegionID)
  527. {
  528. string RegionCacheStatusFile = Path.Combine(m_CacheDirectory, "RegionStatus_" + RegionID.ToString() + ".fac");
  529. if (File.Exists(RegionCacheStatusFile))
  530. {
  531. File.SetLastWriteTime(RegionCacheStatusFile, DateTime.Now);
  532. }
  533. else
  534. {
  535. File.WriteAllText(RegionCacheStatusFile, "Please do not delete this file unless you are manually clearing your Flotsam Asset Cache.");
  536. }
  537. }
  538. /// <summary>
  539. /// Iterates through all Scenes, doing a deep scan through assets
  540. /// to cache all assets present in the scene or referenced by assets
  541. /// in the scene
  542. /// </summary>
  543. /// <returns></returns>
  544. private int CacheScenes()
  545. {
  546. UuidGatherer gatherer = new UuidGatherer(m_AssetService);
  547. Dictionary<UUID, AssetType> assets = new Dictionary<UUID, AssetType>();
  548. foreach (Scene s in m_Scenes)
  549. {
  550. StampRegionStatusFile(s.RegionInfo.RegionID);
  551. s.ForEachSOG(delegate(SceneObjectGroup e)
  552. {
  553. gatherer.GatherAssetUuids(e, assets);
  554. }
  555. );
  556. }
  557. foreach (UUID assetID in assets.Keys)
  558. {
  559. string filename = GetFileName(assetID.ToString());
  560. if (File.Exists(filename))
  561. {
  562. File.SetLastAccessTime(filename, DateTime.Now);
  563. }
  564. else
  565. {
  566. m_AssetService.Get(assetID.ToString());
  567. }
  568. }
  569. return assets.Keys.Count;
  570. }
  571. /// <summary>
  572. /// Deletes all cache contents
  573. /// </summary>
  574. private void ClearFileCache()
  575. {
  576. foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
  577. {
  578. try
  579. {
  580. Directory.Delete(dir, true);
  581. }
  582. catch (Exception e)
  583. {
  584. LogException(e);
  585. }
  586. }
  587. foreach (string file in Directory.GetFiles(m_CacheDirectory))
  588. {
  589. try
  590. {
  591. File.Delete(file);
  592. }
  593. catch (Exception e)
  594. {
  595. LogException(e);
  596. }
  597. }
  598. }
  599. #region Console Commands
  600. private void HandleConsoleCommand(string module, string[] cmdparams)
  601. {
  602. if (cmdparams.Length >= 2)
  603. {
  604. string cmd = cmdparams[1];
  605. switch (cmd)
  606. {
  607. case "status":
  608. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Memory Cache : {0} assets", m_MemoryCache.Count);
  609. int fileCount = GetFileCacheCount(m_CacheDirectory);
  610. m_log.InfoFormat("[FLOTSAM ASSET CACHE] File Cache : {0} assets", fileCount);
  611. foreach (string s in Directory.GetFiles(m_CacheDirectory, "*.fac"))
  612. {
  613. m_log.Info("[FLOTSAM ASSET CACHE] Deep Scans were performed on the following regions:");
  614. string RegionID = s.Remove(0,s.IndexOf("_")).Replace(".fac","");
  615. DateTime RegionDeepScanTMStamp = File.GetLastWriteTime(s);
  616. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Region: {0}, {1}", RegionID, RegionDeepScanTMStamp.ToString("MM/dd/yyyy hh:mm:ss"));
  617. }
  618. break;
  619. case "clear":
  620. if (cmdparams.Length < 3)
  621. {
  622. m_log.Warn("[FLOTSAM ASSET CACHE] Please specify memory and/or file cache.");
  623. break;
  624. }
  625. foreach (string s in cmdparams)
  626. {
  627. if (s.ToLower() == "memory")
  628. {
  629. m_MemoryCache.Clear();
  630. m_log.Info("[FLOTSAM ASSET CACHE] Memory cache cleared.");
  631. }
  632. else if (s.ToLower() == "file")
  633. {
  634. ClearFileCache();
  635. m_log.Info("[FLOTSAM ASSET CACHE] File cache cleared.");
  636. }
  637. }
  638. break;
  639. case "assets":
  640. m_log.Info("[FLOTSAM ASSET CACHE] Caching all assets, in all scenes.");
  641. Util.FireAndForget(delegate {
  642. int assetsCached = CacheScenes();
  643. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Completed Scene Caching, {0} assets found.", assetsCached);
  644. });
  645. break;
  646. case "expire":
  647. if (cmdparams.Length < 3)
  648. {
  649. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Invalid parameters for Expire, please specify a valid date & time", cmd);
  650. break;
  651. }
  652. string s_expirationDate = "";
  653. DateTime expirationDate;
  654. if (cmdparams.Length > 3)
  655. {
  656. s_expirationDate = string.Join(" ", cmdparams, 2, cmdparams.Length - 2);
  657. }
  658. else
  659. {
  660. s_expirationDate = cmdparams[2];
  661. }
  662. if (!DateTime.TryParse(s_expirationDate, out expirationDate))
  663. {
  664. m_log.InfoFormat("[FLOTSAM ASSET CACHE] {0} is not a valid date & time", cmd);
  665. break;
  666. }
  667. CleanExpiredFiles(m_CacheDirectory, expirationDate);
  668. break;
  669. default:
  670. m_log.InfoFormat("[FLOTSAM ASSET CACHE] Unknown command {0}", cmd);
  671. break;
  672. }
  673. }
  674. else if (cmdparams.Length == 1)
  675. {
  676. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache status - Display cache status");
  677. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache clearmem - Remove all assets cached in memory");
  678. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache clearfile - Remove all assets cached on disk");
  679. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache cachescenes - Attempt a deep cache of all assets in all scenes");
  680. m_log.InfoFormat("[FLOTSAM ASSET CACHE] flotsamcache <datetime> - Purge assets older then the specified date & time");
  681. }
  682. }
  683. #endregion
  684. #region IAssetService Members
  685. public AssetMetadata GetMetadata(string id)
  686. {
  687. AssetBase asset = Get(id);
  688. return asset.Metadata;
  689. }
  690. public byte[] GetData(string id)
  691. {
  692. AssetBase asset = Get(id);
  693. return asset.Data;
  694. }
  695. public bool Get(string id, object sender, AssetRetrieved handler)
  696. {
  697. AssetBase asset = Get(id);
  698. handler(id, sender, asset);
  699. return true;
  700. }
  701. public string Store(AssetBase asset)
  702. {
  703. if (asset.FullID == UUID.Zero)
  704. {
  705. asset.FullID = UUID.Random();
  706. }
  707. Cache(asset);
  708. return asset.ID;
  709. }
  710. public bool UpdateContent(string id, byte[] data)
  711. {
  712. AssetBase asset = Get(id);
  713. asset.Data = data;
  714. Cache(asset);
  715. return true;
  716. }
  717. public bool Delete(string id)
  718. {
  719. Expire(id);
  720. return true;
  721. }
  722. #endregion
  723. }
  724. }