FlotsamAssetCache.cs 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594
  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.IO;
  29. using System.Collections.Generic;
  30. using System.Collections.Concurrent;
  31. using System.Reflection;
  32. using System.Runtime.Serialization.Formatters.Binary;
  33. using System.Text;
  34. using System.Threading;
  35. using System.Threading.Tasks;
  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.Monitoring;
  43. using OpenSim.Region.Framework.Interfaces;
  44. using OpenSim.Region.Framework.Scenes;
  45. using OpenSim.Server.Base;
  46. using OpenSim.Services.Interfaces;
  47. namespace OpenSim.Region.CoreModules.Asset
  48. {
  49. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "FlotsamAssetCache")]
  50. public class FlotsamAssetCache : ISharedRegionModule, IAssetCache, IAssetService
  51. {
  52. private struct WriteAssetInfo
  53. {
  54. public string filename;
  55. public AssetBase asset;
  56. }
  57. private static readonly ILog m_log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType);
  58. private bool m_Enabled;
  59. private bool m_timerRunning;
  60. private bool m_cleanupRunning;
  61. private const string m_ModuleName = "FlotsamAssetCache";
  62. private string m_CacheDirectory = "assetcache";
  63. private string m_assetLoader;
  64. private string m_assetLoaderArgs;
  65. private readonly char[] m_InvalidChars;
  66. private int m_LogLevel = 0;
  67. private ulong m_HitRateDisplay = 100; // How often to display hit statistics, given in requests
  68. private ulong m_Requests;
  69. private ulong m_RequestsForInprogress;
  70. private ulong m_DiskHits;
  71. private ulong m_MemoryHits;
  72. private ulong m_weakRefHits;
  73. private static HashSet<string> m_CurrentlyWriting = new HashSet<string>();
  74. private static BlockingCollection<WriteAssetInfo> m_assetFileWriteQueue = null;
  75. private static CancellationTokenSource m_cancelSource;
  76. private static HashSet<string> m_defaultAssets = new HashSet<string>();
  77. private bool m_FileCacheEnabled = true;
  78. private ExpiringCacheOS<string, AssetBase> m_MemoryCache;
  79. private bool m_MemoryCacheEnabled = false;
  80. private ExpiringKey<string> m_negativeCache;
  81. private bool m_negativeCacheEnabled = true;
  82. // Expiration is expressed in hours.
  83. private double m_MemoryExpiration = 0.016;
  84. private const double m_DefaultFileExpiration = 48;
  85. // Negative cache is in seconds
  86. private int m_negativeExpiration = 120;
  87. private TimeSpan m_FileExpiration = TimeSpan.FromHours(m_DefaultFileExpiration);
  88. private TimeSpan m_FileExpirationCleanupTimer = TimeSpan.FromHours(1.0);
  89. private static int m_CacheDirectoryTiers = 1;
  90. private static int m_CacheDirectoryTierLen = 3;
  91. private static int m_CacheWarnAt = 30000;
  92. private System.Timers.Timer m_CacheCleanTimer;
  93. private IAssetService m_AssetService;
  94. private List<Scene> m_Scenes = new List<Scene>();
  95. private readonly object timerLock = new object();
  96. private Dictionary<string,WeakReference> weakAssetReferences = new Dictionary<string, WeakReference>();
  97. private readonly object weakAssetReferencesLock = new object();
  98. private bool m_updateFileTimeOnCacheHit = false;
  99. public FlotsamAssetCache()
  100. {
  101. List<char> invalidChars = new List<char>();
  102. invalidChars.AddRange(Path.GetInvalidPathChars());
  103. invalidChars.AddRange(Path.GetInvalidFileNameChars());
  104. m_InvalidChars = invalidChars.ToArray();
  105. }
  106. public Type ReplaceableInterface
  107. {
  108. get { return null; }
  109. }
  110. public string Name
  111. {
  112. get { return m_ModuleName; }
  113. }
  114. public void Initialise(IConfigSource source)
  115. {
  116. IConfig moduleConfig = source.Configs["Modules"];
  117. if (moduleConfig != null)
  118. {
  119. string name = moduleConfig.GetString("AssetCaching", String.Empty);
  120. if (name == Name)
  121. {
  122. m_negativeCache = new ExpiringKey<string>(2000);
  123. m_Enabled = true;
  124. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: {0} enabled", this.Name);
  125. IConfig assetConfig = source.Configs["AssetCache"];
  126. if (assetConfig == null)
  127. {
  128. m_log.Debug(
  129. "[FLOTSAM ASSET CACHE]: AssetCache section missing from config (not copied config-include/FlotsamCache.ini.example? Using defaults.");
  130. }
  131. else
  132. {
  133. m_FileCacheEnabled = assetConfig.GetBoolean("FileCacheEnabled", m_FileCacheEnabled);
  134. m_CacheDirectory = assetConfig.GetString("CacheDirectory", m_CacheDirectory);
  135. m_CacheDirectory = Path.GetFullPath(m_CacheDirectory);
  136. m_MemoryCacheEnabled = assetConfig.GetBoolean("MemoryCacheEnabled", m_MemoryCacheEnabled);
  137. m_MemoryExpiration = assetConfig.GetDouble("MemoryCacheTimeout", m_MemoryExpiration);
  138. m_MemoryExpiration *= 3600.0; // config in hours to seconds
  139. m_negativeCacheEnabled = assetConfig.GetBoolean("NegativeCacheEnabled", m_negativeCacheEnabled);
  140. m_negativeExpiration = assetConfig.GetInt("NegativeCacheTimeout", m_negativeExpiration);
  141. m_updateFileTimeOnCacheHit = assetConfig.GetBoolean("UpdateFileTimeOnCacheHit", m_updateFileTimeOnCacheHit);
  142. m_updateFileTimeOnCacheHit &= m_FileCacheEnabled;
  143. m_LogLevel = assetConfig.GetInt("LogLevel", m_LogLevel);
  144. m_HitRateDisplay = (ulong)assetConfig.GetLong("HitRateDisplay", (long)m_HitRateDisplay);
  145. m_FileExpiration = TimeSpan.FromHours(assetConfig.GetDouble("FileCacheTimeout", m_DefaultFileExpiration));
  146. m_FileExpirationCleanupTimer = TimeSpan.FromHours(
  147. assetConfig.GetDouble("FileCleanupTimer", m_FileExpirationCleanupTimer.TotalHours));
  148. m_CacheDirectoryTiers = assetConfig.GetInt("CacheDirectoryTiers", m_CacheDirectoryTiers);
  149. m_CacheDirectoryTierLen = assetConfig.GetInt("CacheDirectoryTierLength", m_CacheDirectoryTierLen);
  150. m_CacheWarnAt = assetConfig.GetInt("CacheWarnAt", m_CacheWarnAt);
  151. }
  152. if(m_MemoryCacheEnabled)
  153. m_MemoryCache = new ExpiringCacheOS<string, AssetBase>((int)m_MemoryExpiration * 500);
  154. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Cache Directory {0}", m_CacheDirectory);
  155. if (m_CacheDirectoryTiers < 1)
  156. m_CacheDirectoryTiers = 1;
  157. else if (m_CacheDirectoryTiers > 3)
  158. m_CacheDirectoryTiers = 3;
  159. if (m_CacheDirectoryTierLen < 1)
  160. m_CacheDirectoryTierLen = 1;
  161. else if (m_CacheDirectoryTierLen > 4)
  162. m_CacheDirectoryTierLen = 4;
  163. m_negativeExpiration *= 1000;
  164. assetConfig = source.Configs["AssetService"];
  165. if(assetConfig != null)
  166. {
  167. m_assetLoader = assetConfig.GetString("DefaultAssetLoader", String.Empty);
  168. m_assetLoaderArgs = assetConfig.GetString("AssetLoaderArgs", string.Empty);
  169. if (string.IsNullOrWhiteSpace(m_assetLoaderArgs))
  170. m_assetLoader = string.Empty;
  171. }
  172. MainConsole.Instance.Commands.AddCommand("Assets", true, "fcache status", "fcache status", "Display cache status", HandleConsoleCommand);
  173. MainConsole.Instance.Commands.AddCommand("Assets", true, "fcache clear", "fcache clear [file] [memory]", "Remove all assets in the cache. If file or memory is specified then only this cache is cleared.", HandleConsoleCommand);
  174. MainConsole.Instance.Commands.AddCommand("Assets", true, "fcache assets", "fcache assets", "Attempt a deep scan and cache of all assets in all scenes", HandleConsoleCommand);
  175. MainConsole.Instance.Commands.AddCommand("Assets", true, "fcache expire", "fcache expire <datetime(mm/dd/YYYY)>", "Purge cached assets older then the specified date/time", HandleConsoleCommand);
  176. if (!string.IsNullOrWhiteSpace(m_assetLoader))
  177. {
  178. IAssetLoader assetLoader = ServerUtils.LoadPlugin<IAssetLoader>(m_assetLoader, new object[] { });
  179. if (assetLoader != null)
  180. {
  181. HashSet<string> ids = new HashSet<string>();
  182. assetLoader.ForEachDefaultXmlAsset(
  183. m_assetLoaderArgs,
  184. delegate (AssetBase a)
  185. {
  186. ids.Add(a.ID);
  187. });
  188. m_defaultAssets = ids;
  189. }
  190. MainConsole.Instance.Commands.AddCommand("Assets", true, "fcache cachedefaultassets", "fcache cachedefaultassets", "loads local default assets to cache. This may override grid ones. use with care", HandleConsoleCommand);
  191. MainConsole.Instance.Commands.AddCommand("Assets", true, "fcache deletedefaultassets", "fcache deletedefaultassets", "deletes default local assets from cache so they can be refreshed from grid. use with care", HandleConsoleCommand);
  192. }
  193. }
  194. }
  195. }
  196. public void PostInitialise()
  197. {
  198. }
  199. public void Close()
  200. {
  201. if(m_Scenes.Count <= 0)
  202. {
  203. if (m_assetFileWriteQueue != null)
  204. {
  205. m_assetFileWriteQueue.Dispose();
  206. m_assetFileWriteQueue = null;
  207. }
  208. if(m_cancelSource != null)
  209. {
  210. m_cancelSource.Dispose();
  211. m_cancelSource = null;
  212. }
  213. }
  214. }
  215. public void AddRegion(Scene scene)
  216. {
  217. if (m_Enabled)
  218. {
  219. scene.RegisterModuleInterface<IAssetCache>(this);
  220. m_Scenes.Add(scene);
  221. }
  222. }
  223. public void RemoveRegion(Scene scene)
  224. {
  225. if (m_Enabled)
  226. {
  227. scene.UnregisterModuleInterface<IAssetCache>(this);
  228. m_Scenes.Remove(scene);
  229. lock(timerLock)
  230. {
  231. if(m_Scenes.Count <= 0)
  232. {
  233. m_cleanupRunning = false;
  234. if (m_timerRunning)
  235. {
  236. m_timerRunning = false;
  237. m_CacheCleanTimer.Stop();
  238. m_CacheCleanTimer.Close();
  239. }
  240. if (m_FileCacheEnabled && m_assetFileWriteQueue != null)
  241. m_cancelSource.Cancel();
  242. }
  243. }
  244. }
  245. }
  246. public void RegionLoaded(Scene scene)
  247. {
  248. if (m_Enabled)
  249. {
  250. if(m_AssetService == null)
  251. m_AssetService = scene.RequestModuleInterface<IAssetService>();
  252. lock(timerLock)
  253. {
  254. if(!m_timerRunning)
  255. {
  256. if (m_FileCacheEnabled && (m_FileExpiration > TimeSpan.Zero) && (m_FileExpirationCleanupTimer > TimeSpan.Zero))
  257. {
  258. m_CacheCleanTimer = new System.Timers.Timer(m_FileExpirationCleanupTimer.TotalMilliseconds)
  259. {
  260. AutoReset = false
  261. };
  262. m_CacheCleanTimer.Elapsed += CleanupExpiredFiles;
  263. m_CacheCleanTimer.Start();
  264. m_timerRunning = true;
  265. }
  266. }
  267. if (m_FileCacheEnabled && m_assetFileWriteQueue == null)
  268. {
  269. m_assetFileWriteQueue = new BlockingCollection<WriteAssetInfo>();
  270. m_cancelSource = new CancellationTokenSource();
  271. WorkManager.RunInThreadPool(ProcessWrites, null, "FloatsamCacheWriter", false);
  272. }
  273. }
  274. }
  275. }
  276. private void ProcessWrites(object o)
  277. {
  278. try
  279. {
  280. while(true)
  281. {
  282. if(m_assetFileWriteQueue.TryTake(out WriteAssetInfo wai,-1, m_cancelSource.Token))
  283. {
  284. WriteFileCache(wai.filename,wai.asset,false);
  285. wai.asset = null;
  286. Thread.Sleep(20);
  287. }
  288. }
  289. }
  290. catch{ }
  291. }
  292. ////////////////////////////////////////////////////////////
  293. // IAssetCache
  294. //
  295. private void UpdateWeakReference(string key, AssetBase asset)
  296. {
  297. lock(weakAssetReferencesLock)
  298. {
  299. if(weakAssetReferences.TryGetValue(key , out WeakReference aref))
  300. aref.Target = asset;
  301. else
  302. weakAssetReferences[key] = new WeakReference(asset);
  303. }
  304. }
  305. private void UpdateMemoryCache(string key, AssetBase asset)
  306. {
  307. m_MemoryCache.AddOrUpdate(key, asset, m_MemoryExpiration);
  308. }
  309. private void UpdateFileCache(string key, AssetBase asset, bool replace = false)
  310. {
  311. if(m_assetFileWriteQueue == null)
  312. return;
  313. string filename = GetFileName(key);
  314. try
  315. {
  316. // If the file is already cached, don't cache it, just touch it so access time is updated
  317. if (!replace && File.Exists(filename))
  318. {
  319. UpdateFileLastAccessTime(filename);
  320. return;
  321. }
  322. // Once we start writing, make sure we flag that we're writing
  323. // that object to the cache so that we don't try to write the
  324. // same file multiple times.
  325. lock (m_CurrentlyWriting)
  326. {
  327. if (m_CurrentlyWriting.Contains(filename))
  328. return;
  329. else
  330. m_CurrentlyWriting.Add(filename);
  331. }
  332. WriteAssetInfo wai = new WriteAssetInfo()
  333. {
  334. filename = filename,
  335. asset = asset
  336. };
  337. if (m_assetFileWriteQueue != null)
  338. m_assetFileWriteQueue.Add(wai);
  339. }
  340. catch (Exception e)
  341. {
  342. m_log.ErrorFormat(
  343. "[FLOTSAM ASSET CACHE]: Failed to update cache for asset {0}. Exception {1} {2}",
  344. asset.ID, e.Message, e.StackTrace);
  345. }
  346. }
  347. public void Cache(AssetBase asset, bool replace = false)
  348. {
  349. // TODO: Spawn this off to some seperate thread to do the actual writing
  350. if (asset != null)
  351. {
  352. //m_log.DebugFormat("[FLOTSAM ASSET CACHE]: Caching asset with id {0}", asset.ID);
  353. UpdateWeakReference(asset.ID, asset);
  354. if (m_MemoryCacheEnabled)
  355. UpdateMemoryCache(asset.ID, asset);
  356. if (m_FileCacheEnabled)
  357. UpdateFileCache(asset.ID, asset, replace);
  358. }
  359. }
  360. public void CacheNegative(string id)
  361. {
  362. if (m_negativeCacheEnabled)
  363. {
  364. m_negativeCache.Add(id, m_negativeExpiration);
  365. }
  366. }
  367. /// <summary>
  368. /// Updates the cached file with the current time.
  369. /// </summary>
  370. /// <param name="filename">Filename.</param>
  371. /// <returns><c>true</c>, if the update was successful, false otherwise.</returns>
  372. private bool UpdateFileLastAccessTime(string filename)
  373. {
  374. try
  375. {
  376. File.SetLastAccessTime(filename, DateTime.Now);
  377. return true;
  378. }
  379. catch (FileNotFoundException)
  380. {
  381. return false;
  382. }
  383. catch
  384. {
  385. return true; // ignore other errors
  386. }
  387. }
  388. private AssetBase GetFromWeakReference(string id)
  389. {
  390. AssetBase asset = null;
  391. lock(weakAssetReferencesLock)
  392. {
  393. if (weakAssetReferences.TryGetValue(id, out WeakReference aref))
  394. {
  395. asset = aref.Target as AssetBase;
  396. if(asset == null)
  397. weakAssetReferences.Remove(id);
  398. else
  399. m_weakRefHits++;
  400. }
  401. }
  402. return asset;
  403. }
  404. /// <summary>
  405. /// Try to get an asset from the in-memory cache.
  406. /// </summary>
  407. /// <param name="id"></param>
  408. /// <returns></returns>
  409. private AssetBase GetFromMemoryCache(string id)
  410. {
  411. if (m_MemoryCache.TryGetValue(id, out AssetBase asset))
  412. {
  413. m_MemoryHits++;
  414. return asset;
  415. }
  416. return null;
  417. }
  418. private bool CheckFromMemoryCache(string id)
  419. {
  420. return m_MemoryCache.Contains(id);
  421. }
  422. /// <summary>
  423. /// Try to get an asset from the file cache.
  424. /// </summary>
  425. /// <param name="id"></param>
  426. /// <returns>An asset retrieved from the file cache. null if there was a problem retrieving an asset.</returns>
  427. private AssetBase GetFromFileCache(string id)
  428. {
  429. string filename = GetFileName(id);
  430. // Track how often we have the problem that an asset is requested while
  431. // it is still being downloaded by a previous request.
  432. if (m_CurrentlyWriting.Contains(filename))
  433. {
  434. m_RequestsForInprogress++;
  435. return null;
  436. }
  437. AssetBase asset = null;
  438. try
  439. {
  440. using (FileStream stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
  441. {
  442. if (stream.Length == 0) // Empty file will trigger exception below
  443. return null;
  444. BinaryFormatter bformatter = new BinaryFormatter();
  445. asset = (AssetBase)bformatter.Deserialize(stream);
  446. m_DiskHits++;
  447. }
  448. }
  449. catch (FileNotFoundException)
  450. {
  451. }
  452. catch (DirectoryNotFoundException)
  453. {
  454. }
  455. catch (System.Runtime.Serialization.SerializationException e)
  456. {
  457. m_log.WarnFormat(
  458. "[FLOTSAM ASSET CACHE]: Failed to get file {0} for asset {1}. Exception {2} {3}",
  459. filename, id, e.Message, e.StackTrace);
  460. // If there was a problem deserializing the asset, the asset may
  461. // either be corrupted OR was serialized under an old format
  462. // {different version of AssetBase} -- we should attempt to
  463. // delete it and re-cache
  464. File.Delete(filename);
  465. }
  466. catch (Exception e)
  467. {
  468. m_log.WarnFormat(
  469. "[FLOTSAM ASSET CACHE]: Failed to get file {0} for asset {1}. Exception {2} {3}",
  470. filename, id, e.Message, e.StackTrace);
  471. }
  472. return asset;
  473. }
  474. private bool CheckFromFileCache(string id)
  475. {
  476. try
  477. {
  478. return File.Exists(GetFileName(id));
  479. }
  480. catch
  481. {
  482. }
  483. return false;
  484. }
  485. // For IAssetService
  486. public AssetBase Get(string id)
  487. {
  488. Get(id, out AssetBase asset);
  489. return asset;
  490. }
  491. public bool Get(string id, out AssetBase asset)
  492. {
  493. asset = null;
  494. m_Requests++;
  495. if (m_negativeCache.ContainsKey(id))
  496. return false;
  497. asset = GetFromWeakReference(id);
  498. if (asset != null)
  499. {
  500. if(m_updateFileTimeOnCacheHit)
  501. {
  502. string filename = GetFileName(id);
  503. UpdateFileLastAccessTime(filename);
  504. }
  505. if (m_MemoryCacheEnabled)
  506. UpdateMemoryCache(id, asset);
  507. return true;
  508. }
  509. if (m_MemoryCacheEnabled)
  510. {
  511. asset = GetFromMemoryCache(id);
  512. if(asset != null)
  513. {
  514. UpdateWeakReference(id,asset);
  515. if (m_updateFileTimeOnCacheHit)
  516. {
  517. string filename = GetFileName(id);
  518. UpdateFileLastAccessTime(filename);
  519. }
  520. return true;
  521. }
  522. }
  523. if (m_FileCacheEnabled)
  524. {
  525. asset = GetFromFileCache(id);
  526. if(asset != null)
  527. {
  528. UpdateWeakReference(id,asset);
  529. if (m_MemoryCacheEnabled)
  530. UpdateMemoryCache(id, asset);
  531. }
  532. }
  533. return true;
  534. }
  535. public bool Check(string id)
  536. {
  537. if(GetFromWeakReference(id) != null)
  538. return true;
  539. if (m_MemoryCacheEnabled && CheckFromMemoryCache(id))
  540. return true;
  541. if (m_FileCacheEnabled && CheckFromFileCache(id))
  542. return true;
  543. return false;
  544. }
  545. public AssetBase GetCached(string id)
  546. {
  547. Get(id, out AssetBase asset);
  548. return asset;
  549. }
  550. public void Expire(string id)
  551. {
  552. if (m_LogLevel >= 2)
  553. m_log.DebugFormat("[FLOTSAM ASSET CACHE]: Expiring Asset {0}", id);
  554. try
  555. {
  556. lock (weakAssetReferencesLock)
  557. weakAssetReferences.Remove(id);
  558. if (m_MemoryCacheEnabled)
  559. m_MemoryCache.Remove(id);
  560. if (m_FileCacheEnabled)
  561. {
  562. string filename = GetFileName(id);
  563. File.Delete(filename);
  564. }
  565. }
  566. catch (Exception e)
  567. {
  568. if (m_LogLevel >= 2)
  569. m_log.WarnFormat("[FLOTSAM ASSET CACHE]: Failed to expire cached file {0}. Exception {1} {2}",
  570. id, e.Message, e.StackTrace);
  571. }
  572. }
  573. public void Clear()
  574. {
  575. if (m_LogLevel >= 2)
  576. m_log.Debug("[FLOTSAM ASSET CACHE]: Clearing caches.");
  577. if (m_FileCacheEnabled && Directory.Exists(m_CacheDirectory))
  578. {
  579. foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
  580. {
  581. try
  582. {
  583. Directory.Delete(dir, true);
  584. }
  585. catch { }
  586. }
  587. }
  588. if (m_MemoryCacheEnabled)
  589. {
  590. m_MemoryCache.Dispose();
  591. m_MemoryCache = new ExpiringCacheOS<string, AssetBase>((int)m_MemoryExpiration * 500);
  592. }
  593. if (m_negativeCacheEnabled)
  594. {
  595. m_negativeCache.Dispose();
  596. m_negativeCache = new ExpiringKey<string>(2000);
  597. }
  598. lock (weakAssetReferencesLock)
  599. weakAssetReferences = new Dictionary<string, WeakReference>();
  600. }
  601. private async void CleanupExpiredFiles(object source, ElapsedEventArgs e)
  602. {
  603. lock (timerLock)
  604. {
  605. if (!m_timerRunning || m_cleanupRunning || !Directory.Exists(m_CacheDirectory))
  606. return;
  607. m_cleanupRunning = true;
  608. }
  609. // Purge all files last accessed prior to this point
  610. await DoCleanExpiredFiles(DateTime.Now - m_FileExpiration).ConfigureAwait(false);
  611. }
  612. private async Task DoCleanExpiredFiles(DateTime purgeLine)
  613. {
  614. long heap = 0;
  615. //if (m_LogLevel >= 2)
  616. {
  617. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Start background expiring files older than {0}.", purgeLine);
  618. heap = GC.GetTotalMemory(false);
  619. }
  620. // An asset cache may contain local non-temporary assets that are not in the asset service. Therefore,
  621. // before cleaning up expired files we must scan the objects in the scene to make sure that we retain
  622. // such local assets if they have not been recently accessed.
  623. Dictionary<UUID,sbyte> gids = await gatherSceneAssets().ConfigureAwait(false);
  624. int cooldown = 0;
  625. m_log.Info("[FLOTSAM ASSET CACHE] start asset files expire");
  626. foreach (string subdir in Directory.GetDirectories(m_CacheDirectory))
  627. {
  628. if(!m_cleanupRunning)
  629. break;
  630. cooldown = await CleanExpiredFiles(subdir, gids, purgeLine, cooldown);
  631. if (++cooldown >= 10)
  632. {
  633. await Task.Delay(100).ConfigureAwait(false);
  634. cooldown = 0;
  635. }
  636. }
  637. gids = null;
  638. lock (timerLock)
  639. {
  640. if (m_timerRunning)
  641. m_CacheCleanTimer.Start();
  642. m_cleanupRunning = false;
  643. }
  644. //if (m_LogLevel >= 2)
  645. {
  646. heap = GC.GetTotalMemory(false) - heap;
  647. double fheap = Math.Round((double)(heap / (1024 * 1024)), 3);
  648. m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Finished expiring files, heap delta: {0}MB.", fheap);
  649. }
  650. }
  651. /// <summary>
  652. /// Recurses through specified directory checking for asset files last
  653. /// accessed prior to the specified purge line and deletes them. Also
  654. /// removes empty tier directories.
  655. /// </summary>
  656. /// <param name="dir"></param>
  657. /// <param name="purgeLine"></param>
  658. private async Task<int> CleanExpiredFiles(string dir, Dictionary<UUID, sbyte> gids, DateTime purgeLine, int cooldown)
  659. {
  660. try
  661. {
  662. if (!m_cleanupRunning)
  663. return cooldown;
  664. int dirSize = 0;
  665. // Recurse into lower tiers
  666. foreach (string subdir in Directory.GetDirectories(dir))
  667. {
  668. if (!m_cleanupRunning)
  669. return cooldown;
  670. ++dirSize;
  671. cooldown = await CleanExpiredFiles(subdir, gids, purgeLine, cooldown);
  672. if (++cooldown > 10)
  673. {
  674. await Task.Delay(100).ConfigureAwait(false);
  675. cooldown = 0;
  676. }
  677. }
  678. foreach (string file in Directory.GetFiles(dir))
  679. {
  680. if (!m_cleanupRunning)
  681. return cooldown;
  682. ++dirSize;
  683. string id = Path.GetFileName(file);
  684. if (String.IsNullOrEmpty(id))
  685. continue; //??
  686. if (m_defaultAssets.Contains(id) ||(UUID.TryParse(id, out UUID uid) && gids.ContainsKey(uid)))
  687. {
  688. ++cooldown;
  689. continue;
  690. }
  691. if (File.GetLastAccessTime(file) < purgeLine)
  692. {
  693. try
  694. {
  695. File.Delete(file);
  696. lock (weakAssetReferencesLock)
  697. weakAssetReferences.Remove(id);
  698. }
  699. catch { }
  700. cooldown += 5;
  701. --dirSize;
  702. }
  703. if (++cooldown >= 20)
  704. {
  705. await Task.Delay(100).ConfigureAwait(false);
  706. cooldown = 0;
  707. }
  708. }
  709. // Check if a tier directory is empty, if so, delete it
  710. if (m_cleanupRunning && dirSize == 0)
  711. {
  712. try
  713. {
  714. Directory.Delete(dir);
  715. }
  716. catch { }
  717. cooldown += 5;
  718. if (cooldown >= 20)
  719. {
  720. await Task.Delay(100).ConfigureAwait(false);
  721. cooldown = 0;
  722. }
  723. }
  724. else if (dirSize >= m_CacheWarnAt)
  725. {
  726. m_log.WarnFormat(
  727. "[FLOTSAM ASSET CACHE]: Cache folder exceeded CacheWarnAt limit {0} {1}. Suggest increasing tiers, tier length, or reducing cache expiration",
  728. dir, dirSize);
  729. }
  730. }
  731. catch (DirectoryNotFoundException)
  732. {
  733. // If we get here, another node on the same box has
  734. // already removed the directory. Continue with next.
  735. }
  736. catch (Exception e)
  737. {
  738. m_log.WarnFormat("[FLOTSAM ASSET CACHE]: Could not complete clean of expired files in {0}, exception {1}", dir, e.Message);
  739. }
  740. return cooldown;
  741. }
  742. /// <summary>
  743. /// Determines the filename for an AssetID stored in the file cache
  744. /// </summary>
  745. /// <param name="id"></param>
  746. /// <returns></returns>
  747. private string GetFileName(string id)
  748. {
  749. StringBuilder sb = osStringBuilderCache.Acquire();
  750. int indx = id.IndexOfAny(m_InvalidChars);
  751. if (indx >= 0)
  752. {
  753. sb.Append(id);
  754. int sublen = id.Length - indx;
  755. for(int i = 0; i < m_InvalidChars.Length; ++i)
  756. {
  757. sb.Replace(m_InvalidChars[i], '_', indx, sublen);
  758. }
  759. id = sb.ToString();
  760. sb.Clear();
  761. }
  762. if(m_CacheDirectoryTiers == 1)
  763. {
  764. sb.Append(id.Substring(0, m_CacheDirectoryTierLen));
  765. sb.Append(Path.DirectorySeparatorChar);
  766. }
  767. else
  768. {
  769. for (int p = 0; p < m_CacheDirectoryTiers * m_CacheDirectoryTierLen; p += m_CacheDirectoryTierLen)
  770. {
  771. sb.Append(id.Substring(p, m_CacheDirectoryTierLen));
  772. sb.Append(Path.DirectorySeparatorChar);
  773. }
  774. }
  775. sb.Append(id);
  776. return Path.Combine(m_CacheDirectory, osStringBuilderCache.GetStringAndRelease(sb));
  777. }
  778. /// <summary>
  779. /// Writes a file to the file cache, creating any nessesary
  780. /// tier directories along the way
  781. /// </summary>
  782. /// <param name="filename"></param>
  783. /// <param name="asset"></param>
  784. private static void WriteFileCache(string filename, AssetBase asset, bool replace)
  785. {
  786. // Make sure the target cache directory exists
  787. string directory = Path.GetDirectoryName(filename);
  788. // Write file first to a temp name, so that it doesn't look
  789. // like it's already cached while it's still writing.
  790. string tempname = Path.Combine(directory, Path.GetRandomFileName());
  791. try
  792. {
  793. try
  794. {
  795. if (!Directory.Exists(directory))
  796. {
  797. Directory.CreateDirectory(directory);
  798. }
  799. using(Stream stream = File.Open(tempname, FileMode.Create))
  800. {
  801. BinaryFormatter bformatter = new BinaryFormatter();
  802. bformatter.Serialize(stream, asset);
  803. stream.Flush();
  804. }
  805. }
  806. catch (IOException e)
  807. {
  808. m_log.WarnFormat(
  809. "[FLOTSAM ASSET CACHE]: Failed to write asset {0} to temporary location {1} (final {2}) on cache in {3}. Exception {4} {5}.",
  810. asset.ID, tempname, filename, directory, e.Message, e.StackTrace);
  811. return;
  812. }
  813. catch (UnauthorizedAccessException)
  814. {
  815. }
  816. try
  817. {
  818. if(replace)
  819. File.Delete(filename);
  820. File.Move(tempname, filename);
  821. }
  822. catch (IOException)
  823. {
  824. // If we see an IOException here it's likely that some other competing thread has written the
  825. // cache file first, so ignore. Other IOException errors (e.g. filesystem full) should be
  826. // signally by the earlier temporary file writing code.
  827. }
  828. }
  829. finally
  830. {
  831. // Even if the write fails with an exception, we need to make sure
  832. // that we release the lock on that file, otherwise it'll never get
  833. // cached
  834. lock (m_CurrentlyWriting)
  835. {
  836. m_CurrentlyWriting.Remove(filename);
  837. }
  838. }
  839. }
  840. /// <summary>
  841. /// Scan through the file cache, and return number of assets currently cached.
  842. /// </summary>
  843. /// <param name="dir"></param>
  844. /// <returns></returns>
  845. private int GetFileCacheCount(string dir)
  846. {
  847. try
  848. {
  849. int count = 0;
  850. int cooldown = 0;
  851. foreach (string subdir in Directory.GetDirectories(dir))
  852. {
  853. count += GetFileCacheCount(subdir);
  854. ++cooldown;
  855. if(cooldown > 50)
  856. {
  857. Thread.Sleep(100);
  858. cooldown = 0;
  859. }
  860. }
  861. return count + Directory.GetFiles(dir).Length;
  862. }
  863. catch
  864. {
  865. return 0;
  866. }
  867. }
  868. /// <summary>
  869. /// This notes the last time the Region had a deep asset scan performed on it.
  870. /// </summary>
  871. /// <param name="regionID"></param>
  872. private void StampRegionStatusFile(UUID regionID)
  873. {
  874. string RegionCacheStatusFile = Path.Combine(m_CacheDirectory, "RegionStatus_" + regionID.ToString() + ".fac");
  875. try
  876. {
  877. if (File.Exists(RegionCacheStatusFile))
  878. {
  879. File.SetLastWriteTime(RegionCacheStatusFile, DateTime.Now);
  880. }
  881. else
  882. {
  883. File.WriteAllText(
  884. RegionCacheStatusFile,
  885. "Please do not delete this file unless you are manually clearing your Flotsam Asset Cache.");
  886. }
  887. }
  888. catch (Exception e)
  889. {
  890. m_log.WarnFormat("[FLOTSAM ASSET CACHE]: Could not stamp region status file for region {0}. Exception {1}",
  891. regionID, e. Message);
  892. }
  893. }
  894. /// <summary>
  895. /// Iterates through all Scenes, doing a deep scan through assets
  896. /// to update the access time of all assets present in the scene or referenced by assets
  897. /// in the scene.
  898. /// </summary>
  899. /// <param name="tryGetUncached">
  900. /// If true, then assets scanned which are not found in cache are added to the cache.
  901. /// </param>
  902. /// <returns>Number of distinct asset references found in the scene.</returns>
  903. private async Task<int> TouchAllSceneAssets(bool tryGetUncached)
  904. {
  905. m_log.Info("[FLOTSAM ASSET CACHE] start touch files of assets in use");
  906. Dictionary<UUID,sbyte> gatheredids = await gatherSceneAssets();
  907. int cooldown = 0;
  908. foreach(UUID id in gatheredids.Keys)
  909. {
  910. if (!m_cleanupRunning)
  911. break;
  912. string idstr = id.ToString();
  913. if (!UpdateFileLastAccessTime(GetFileName(idstr)) && tryGetUncached)
  914. {
  915. cooldown += 5;
  916. m_AssetService.Get(idstr);
  917. }
  918. if (++cooldown > 50)
  919. {
  920. Thread.Sleep(50);
  921. cooldown = 0;
  922. }
  923. }
  924. return gatheredids.Count;
  925. }
  926. private async Task<Dictionary<UUID, sbyte>> gatherSceneAssets()
  927. {
  928. m_log.Info("[FLOTSAM ASSET CACHE] gather assets in use");
  929. Dictionary<UUID, sbyte> gatheredids = new Dictionary<UUID, sbyte>();
  930. UuidGatherer gatherer = new UuidGatherer(m_AssetService, gatheredids);
  931. int cooldown = 0;
  932. foreach (Scene s in m_Scenes)
  933. {
  934. gatherer.AddGathered(s.RegionInfo.RegionSettings.TerrainTexture1, (sbyte)AssetType.Texture);
  935. gatherer.AddGathered(s.RegionInfo.RegionSettings.TerrainTexture2, (sbyte)AssetType.Texture);
  936. gatherer.AddGathered(s.RegionInfo.RegionSettings.TerrainTexture3, (sbyte)AssetType.Texture);
  937. gatherer.AddGathered(s.RegionInfo.RegionSettings.TerrainTexture4, (sbyte)AssetType.Texture);
  938. gatherer.AddGathered(s.RegionInfo.RegionSettings.TerrainImageID, (sbyte)AssetType.Texture);
  939. if (s.RegionEnvironment != null)
  940. s.RegionEnvironment.GatherAssets(gatheredids);
  941. if (s.LandChannel != null)
  942. {
  943. List<ILandObject> landObjects = s.LandChannel.AllParcels();
  944. foreach (ILandObject lo in landObjects)
  945. {
  946. if (lo.LandData != null && lo.LandData.Environment != null)
  947. lo.LandData.Environment.GatherAssets(gatheredids);
  948. }
  949. }
  950. EntityBase[] entities = s.Entities.GetEntities();
  951. for (int i = 0; i < entities.Length; ++i)
  952. {
  953. if (!m_cleanupRunning)
  954. break;
  955. EntityBase entity = entities[i];
  956. if (entity is SceneObjectGroup)
  957. {
  958. SceneObjectGroup e = entity as SceneObjectGroup;
  959. if (e.IsDeleted)
  960. continue;
  961. gatherer.AddForInspection(e);
  962. while (gatherer.GatherNext())
  963. {
  964. if (++cooldown > 50)
  965. {
  966. await Task.Delay(60).ConfigureAwait(false);
  967. cooldown = 0;
  968. }
  969. }
  970. if (++cooldown > 25)
  971. {
  972. await Task.Delay(60).ConfigureAwait(false);
  973. cooldown = 0;
  974. }
  975. }
  976. }
  977. entities = null;
  978. if (!m_cleanupRunning)
  979. break;
  980. StampRegionStatusFile(s.RegionInfo.RegionID);
  981. }
  982. gatherer.GatherAll();
  983. gatherer.FailedUUIDs.Clear();
  984. gatherer.UncertainAssetsUUIDs.Clear();
  985. gatherer = null;
  986. m_log.InfoFormat("[FLOTSAM ASSET CACHE] found {0} possible assets in use (including {1} default assets)",
  987. gatheredids.Count + m_defaultAssets.Count, m_defaultAssets.Count);
  988. return gatheredids;
  989. }
  990. /// <summary>
  991. /// Deletes all cache contents
  992. /// </summary>
  993. private void ClearFileCache()
  994. {
  995. if(!Directory.Exists(m_CacheDirectory))
  996. return;
  997. foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
  998. {
  999. try
  1000. {
  1001. Directory.Delete(dir, true);
  1002. }
  1003. catch (Exception e)
  1004. {
  1005. m_log.WarnFormat(
  1006. "[FLOTSAM ASSET CACHE]: Couldn't clear asset cache directory {0} from {1}. Exception {2} {3}",
  1007. dir, m_CacheDirectory, e.Message, e.StackTrace);
  1008. }
  1009. }
  1010. foreach (string file in Directory.GetFiles(m_CacheDirectory))
  1011. {
  1012. try
  1013. {
  1014. File.Delete(file);
  1015. }
  1016. catch (Exception e)
  1017. {
  1018. m_log.WarnFormat(
  1019. "[FLOTSAM ASSET CACHE]: Couldn't clear asset cache file {0} from {1}. Exception {1} {2}",
  1020. file, m_CacheDirectory, e.Message, e.StackTrace);
  1021. }
  1022. }
  1023. }
  1024. private List<string> GenerateCacheHitReport()
  1025. {
  1026. List<string> outputLines = new List<string>();
  1027. double invReq = 100.0 / m_Requests;
  1028. double weakHitRate = m_weakRefHits * invReq;
  1029. int weakEntriesAlive = 0;
  1030. lock(weakAssetReferencesLock)
  1031. {
  1032. foreach(WeakReference aref in weakAssetReferences.Values)
  1033. {
  1034. if (aref.IsAlive)
  1035. ++weakEntriesAlive;
  1036. }
  1037. }
  1038. int weakEntries = weakAssetReferences.Count;
  1039. double fileHitRate = m_DiskHits * invReq;
  1040. double TotalHitRate = weakHitRate + fileHitRate;
  1041. outputLines.Add(
  1042. string.Format("Total requests: {0}", m_Requests));
  1043. outputLines.Add(
  1044. string.Format("unCollected Hit Rate: {0}% ({1} entries {2} alive)", weakHitRate.ToString("0.00"),weakEntries, weakEntriesAlive));
  1045. outputLines.Add(
  1046. string.Format("File Hit Rate: {0}%", fileHitRate.ToString("0.00")));
  1047. if (m_MemoryCacheEnabled)
  1048. {
  1049. double HitRate = m_MemoryHits * invReq;
  1050. outputLines.Add(
  1051. string.Format("Memory Hit Rate: {0}%", HitRate.ToString("0.00")));
  1052. TotalHitRate += HitRate;
  1053. }
  1054. outputLines.Add(
  1055. string.Format("Total Hit Rate: {0}%", TotalHitRate.ToString("0.00")));
  1056. outputLines.Add(
  1057. string.Format(
  1058. "Requests overlap during file writing: {0}", m_RequestsForInprogress));
  1059. return outputLines;
  1060. }
  1061. #region Console Commands
  1062. private void HandleConsoleCommand(string module, string[] cmdparams)
  1063. {
  1064. ICommandConsole con = MainConsole.Instance;
  1065. if (cmdparams.Length >= 2)
  1066. {
  1067. string cmd = cmdparams[1];
  1068. switch (cmd)
  1069. {
  1070. case "status":
  1071. {
  1072. WorkManager.RunInThreadPool(delegate
  1073. {
  1074. if (m_MemoryCacheEnabled)
  1075. con.Output("[FLOTSAM ASSET CACHE] Memory Cache: {0} assets", m_MemoryCache.Count);
  1076. else
  1077. con.Output("[FLOTSAM ASSET CACHE] Memory cache disabled");
  1078. if (m_FileCacheEnabled)
  1079. {
  1080. bool doingscan;
  1081. lock (timerLock)
  1082. {
  1083. doingscan = m_cleanupRunning;
  1084. }
  1085. if(doingscan)
  1086. {
  1087. con.Output("[FLOTSAM ASSET CACHE] a deep scan is in progress, skipping file cache assets count");
  1088. }
  1089. else
  1090. {
  1091. con.Output("[FLOTSAM ASSET CACHE] counting file cache assets");
  1092. int fileCount = GetFileCacheCount(m_CacheDirectory);
  1093. con.Output("[FLOTSAM ASSET CACHE] File Cache: {0} assets", fileCount);
  1094. }
  1095. }
  1096. else
  1097. {
  1098. con.Output("[FLOTSAM ASSET CACHE] File cache disabled");
  1099. }
  1100. GenerateCacheHitReport().ForEach(l => con.Output(l));
  1101. if (m_FileCacheEnabled)
  1102. {
  1103. con.Output("[FLOTSAM ASSET CACHE] Deep scans have previously been performed on the following regions:");
  1104. foreach (string s in Directory.GetFiles(m_CacheDirectory, "*.fac"))
  1105. {
  1106. int start = s.IndexOf('_');
  1107. int end = s.IndexOf('.');
  1108. if(start > 0 && end > 0)
  1109. {
  1110. string RegionID = s.Substring(start + 1, end - start);
  1111. DateTime RegionDeepScanTMStamp = File.GetLastWriteTime(s);
  1112. con.Output("[FLOTSAM ASSET CACHE] Region: {0}, {1}", RegionID, RegionDeepScanTMStamp.ToString("MM/dd/yyyy hh:mm:ss"));
  1113. }
  1114. }
  1115. }
  1116. }, null, "CacheStatus", false);
  1117. break;
  1118. }
  1119. case "clear":
  1120. if (cmdparams.Length < 2)
  1121. {
  1122. con.Output("Usage is fcache clear [file] [memory]");
  1123. break;
  1124. }
  1125. bool clearMemory = false, clearFile = false;
  1126. if (cmdparams.Length == 2)
  1127. {
  1128. clearMemory = true;
  1129. clearFile = true;
  1130. }
  1131. foreach (string s in cmdparams)
  1132. {
  1133. if (s.ToLower() == "memory")
  1134. clearMemory = true;
  1135. else if (s.ToLower() == "file")
  1136. clearFile = true;
  1137. }
  1138. if (clearMemory)
  1139. {
  1140. if (m_MemoryCacheEnabled)
  1141. {
  1142. m_MemoryCache.Clear();
  1143. con.Output("Memory cache cleared.");
  1144. }
  1145. else
  1146. {
  1147. con.Output("Memory cache not enabled.");
  1148. }
  1149. }
  1150. if (clearFile)
  1151. {
  1152. if (m_FileCacheEnabled)
  1153. {
  1154. ClearFileCache();
  1155. con.Output("File cache cleared.");
  1156. }
  1157. else
  1158. {
  1159. con.Output("File cache not enabled.");
  1160. }
  1161. }
  1162. break;
  1163. case "assets":
  1164. lock (timerLock)
  1165. {
  1166. if (m_cleanupRunning)
  1167. {
  1168. con.Output("Flotsam assets check already running");
  1169. return;
  1170. }
  1171. m_cleanupRunning = true;
  1172. }
  1173. con.Output("Flotsam Ensuring assets are cached for all scenes.");
  1174. WorkManager.RunInThreadPool(async delegate
  1175. {
  1176. bool wasRunning= false;
  1177. lock(timerLock)
  1178. {
  1179. if(m_timerRunning)
  1180. {
  1181. m_CacheCleanTimer.Stop();
  1182. m_timerRunning = false;
  1183. wasRunning = true;
  1184. }
  1185. }
  1186. if (wasRunning)
  1187. await Task.Delay(100).ConfigureAwait(false);
  1188. int assetReferenceTotal = await TouchAllSceneAssets(true).ConfigureAwait(false);
  1189. lock(timerLock)
  1190. {
  1191. if(wasRunning)
  1192. {
  1193. m_CacheCleanTimer.Start();
  1194. m_timerRunning = true;
  1195. }
  1196. m_cleanupRunning = false;
  1197. }
  1198. con.Output("Completed check with {0} assets.", assetReferenceTotal);
  1199. }, null, "TouchAllSceneAssets", false);
  1200. break;
  1201. case "expire":
  1202. lock (timerLock)
  1203. {
  1204. if (m_cleanupRunning)
  1205. {
  1206. con.Output("Flotsam assets check already running");
  1207. return;
  1208. }
  1209. m_cleanupRunning = true;
  1210. }
  1211. if (cmdparams.Length < 3)
  1212. {
  1213. con.Output("Invalid parameters for Expire, please specify a valid date & time");
  1214. break;
  1215. }
  1216. string s_expirationDate = "";
  1217. DateTime expirationDate;
  1218. if (cmdparams.Length > 3)
  1219. {
  1220. s_expirationDate = string.Join(" ", cmdparams, 2, cmdparams.Length - 2);
  1221. }
  1222. else
  1223. {
  1224. s_expirationDate = cmdparams[2];
  1225. }
  1226. if (!DateTime.TryParse(s_expirationDate, out expirationDate))
  1227. {
  1228. con.Output("{0} is not a valid date & time", cmd);
  1229. break;
  1230. }
  1231. if (expirationDate >= DateTime.Now)
  1232. {
  1233. con.Output("{0} date & time must be in past", cmd);
  1234. break;
  1235. }
  1236. if (m_FileCacheEnabled)
  1237. {
  1238. WorkManager.RunInThreadPool(async delegate
  1239. {
  1240. bool wasRunning = false;
  1241. lock (timerLock)
  1242. {
  1243. if (m_timerRunning)
  1244. {
  1245. m_CacheCleanTimer.Stop();
  1246. m_timerRunning = false;
  1247. wasRunning = true;
  1248. }
  1249. }
  1250. if(wasRunning)
  1251. await Task.Delay(100).ConfigureAwait(false);
  1252. await DoCleanExpiredFiles(expirationDate).ConfigureAwait(false);
  1253. lock (timerLock)
  1254. {
  1255. if (wasRunning)
  1256. {
  1257. m_CacheCleanTimer.Start();
  1258. m_timerRunning = true;
  1259. }
  1260. m_cleanupRunning = false;
  1261. }
  1262. }, null, "TouchAllSceneAssets", false);
  1263. }
  1264. else
  1265. con.Output("File cache not active, not clearing.");
  1266. break;
  1267. case "cachedefaultassets":
  1268. HandleLoadDefaultAssets();
  1269. break;
  1270. case "deletedefaultassets":
  1271. HandleDeleteDefaultAssets();
  1272. break;
  1273. default:
  1274. con.Output("Unknown command {0}", cmd);
  1275. break;
  1276. }
  1277. }
  1278. else if (cmdparams.Length == 1)
  1279. {
  1280. con.Output("fcache assets - Attempt a deep cache of all assets in all scenes");
  1281. con.Output("fcache expire <datetime> - Purge assets older than the specified date & time");
  1282. con.Output("fcache clear [file] [memory] - Remove cached assets");
  1283. con.Output("fcache status - Display cache status");
  1284. con.Output("fcache cachedefaultassets - loads default assets to cache replacing existent ones, this may override grid assets. Use with care");
  1285. con.Output("fcache deletedefaultassets - deletes default local assets from cache so they can be refreshed from grid");
  1286. }
  1287. }
  1288. #endregion
  1289. #region IAssetService Members
  1290. public AssetMetadata GetMetadata(string id)
  1291. {
  1292. Get(id, out AssetBase asset);
  1293. if (asset == null)
  1294. return null;
  1295. return asset.Metadata;
  1296. }
  1297. public byte[] GetData(string id)
  1298. {
  1299. Get(id, out AssetBase asset);
  1300. if (asset == null)
  1301. return null;
  1302. return asset.Data;
  1303. }
  1304. public bool Get(string id, object sender, AssetRetrieved handler)
  1305. {
  1306. if (!Get(id, out AssetBase asset))
  1307. return false;
  1308. handler(id, sender, asset);
  1309. return true;
  1310. }
  1311. public bool[] AssetsExist(string[] ids)
  1312. {
  1313. bool[] exist = new bool[ids.Length];
  1314. for (int i = 0; i < ids.Length; i++)
  1315. {
  1316. exist[i] = Check(ids[i]);
  1317. }
  1318. return exist;
  1319. }
  1320. public string Store(AssetBase asset)
  1321. {
  1322. if (asset.FullID == UUID.Zero)
  1323. {
  1324. asset.FullID = UUID.Random();
  1325. }
  1326. Cache(asset);
  1327. return asset.ID;
  1328. }
  1329. public bool UpdateContent(string id, byte[] data)
  1330. {
  1331. if (!Get(id, out AssetBase asset))
  1332. return false;
  1333. asset.Data = data;
  1334. Cache(asset, true);
  1335. return true;
  1336. }
  1337. public bool Delete(string id)
  1338. {
  1339. Expire(id);
  1340. return true;
  1341. }
  1342. private void HandleLoadDefaultAssets()
  1343. {
  1344. if (string.IsNullOrWhiteSpace(m_assetLoader))
  1345. {
  1346. m_log.Info("[FLOTSAM ASSET CACHE] default assets loader not defined");
  1347. return;
  1348. }
  1349. IAssetLoader assetLoader = ServerUtils.LoadPlugin<IAssetLoader>(m_assetLoader, new object[] { });
  1350. if (assetLoader == null)
  1351. {
  1352. m_log.Info("[FLOTSAM ASSET CACHE] default assets loader not found");
  1353. return;
  1354. }
  1355. m_log.Info("[FLOTSAM ASSET CACHE] start loading local default assets");
  1356. int count = 0;
  1357. HashSet<string> ids = new HashSet<string>();
  1358. assetLoader.ForEachDefaultXmlAsset(
  1359. m_assetLoaderArgs,
  1360. delegate (AssetBase a)
  1361. {
  1362. Cache(a, true);
  1363. ids.Add(a.ID);
  1364. ++count;
  1365. });
  1366. m_defaultAssets = ids;
  1367. m_log.InfoFormat("[FLOTSAM ASSET CACHE] loaded {0} local default assets", count);
  1368. }
  1369. private void HandleDeleteDefaultAssets()
  1370. {
  1371. if (string.IsNullOrWhiteSpace(m_assetLoader))
  1372. {
  1373. m_log.Info("[FLOTSAM ASSET CACHE] default assets loader not defined");
  1374. return;
  1375. }
  1376. IAssetLoader assetLoader = ServerUtils.LoadPlugin<IAssetLoader>(m_assetLoader, new object[] { });
  1377. if (assetLoader == null)
  1378. {
  1379. m_log.Info("[FLOTSAM ASSET CACHE] default assets loader not found");
  1380. return;
  1381. }
  1382. m_log.Info("[FLOTSAM ASSET CACHE] started deleting local default assets");
  1383. int count = 0;
  1384. assetLoader.ForEachDefaultXmlAsset(
  1385. m_assetLoaderArgs,
  1386. delegate (AssetBase a)
  1387. {
  1388. Expire(a.ID);
  1389. ++count;
  1390. });
  1391. m_defaultAssets = new HashSet<string>();
  1392. m_log.InfoFormat("[FLOTSAM ASSET CACHE] deleted {0} local default assets", count);
  1393. }
  1394. #endregion
  1395. }
  1396. }