FlotsamAssetCache.cs 50 KB

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