aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules/Asset
diff options
context:
space:
mode:
authorJeff Ames2009-06-09 17:48:22 +0000
committerJeff Ames2009-06-09 17:48:22 +0000
commit135ff63c3dd2a2c68351669afbd2f57c2fe64d2d (patch)
tree4bb44c0e0a61289d6405de30eb140b8a7b1f6e42 /OpenSim/Region/CoreModules/Asset
parentARGH!!!! note to self: ALWAYS use String.IsNullOrEmpty(...)! (diff)
downloadopensim-SC_OLD-135ff63c3dd2a2c68351669afbd2f57c2fe64d2d.zip
opensim-SC_OLD-135ff63c3dd2a2c68351669afbd2f57c2fe64d2d.tar.gz
opensim-SC_OLD-135ff63c3dd2a2c68351669afbd2f57c2fe64d2d.tar.bz2
opensim-SC_OLD-135ff63c3dd2a2c68351669afbd2f57c2fe64d2d.tar.xz
Update svn properties.
Diffstat (limited to 'OpenSim/Region/CoreModules/Asset')
-rw-r--r--OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs1046
1 files changed, 523 insertions, 523 deletions
diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs
index 99282a3..9540bd4 100644
--- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs
+++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs
@@ -1,523 +1,523 @@
1/* 1/*
2Copyright (c) Contributors, http://osflotsam.org/ 2Copyright (c) Contributors, http://osflotsam.org/
3See CONTRIBUTORS.TXT for a full list of copyright holders. 3See CONTRIBUTORS.TXT for a full list of copyright holders.
4 4
5Redistribution and use in source and binary forms, with or without 5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met: 6modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright 7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer. 8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright 9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the 10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution. 11 documentation and/or other materials provided with the distribution.
12 * Neither the name of the Flotsam Project nor the 12 * Neither the name of the Flotsam Project nor the
13 names of its contributors may be used to endorse or promote products 13 names of its contributors may be used to endorse or promote products
14 derived from this software without specific prior written permission. 14 derived from this software without specific prior written permission.
15 15
16THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR 16THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
17IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY 19DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 21DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
22GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 23INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
24IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 26ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
27 27
28// Uncomment to make asset Get requests for existing 28// Uncomment to make asset Get requests for existing
29// #define WAIT_ON_INPROGRESS_REQUESTS 29// #define WAIT_ON_INPROGRESS_REQUESTS
30 30
31using System; 31using System;
32using System.IO; 32using System.IO;
33using System.Collections.Generic; 33using System.Collections.Generic;
34using System.Reflection; 34using System.Reflection;
35using System.Runtime.Serialization; 35using System.Runtime.Serialization;
36using System.Runtime.Serialization.Formatters.Binary; 36using System.Runtime.Serialization.Formatters.Binary;
37using System.Threading; 37using System.Threading;
38using System.Timers; 38using System.Timers;
39 39
40using log4net; 40using log4net;
41using Nini.Config; 41using Nini.Config;
42using Mono.Addins; 42using Mono.Addins;
43using OpenMetaverse; 43using OpenMetaverse;
44 44
45using OpenSim.Framework; 45using OpenSim.Framework;
46using OpenSim.Region.Framework.Interfaces; 46using OpenSim.Region.Framework.Interfaces;
47using OpenSim.Region.Framework.Scenes; 47using OpenSim.Region.Framework.Scenes;
48using OpenSim.Services.Interfaces; 48using OpenSim.Services.Interfaces;
49 49
50 50
51[assembly: Addin("FlotsamAssetCache", "1.1")] 51[assembly: Addin("FlotsamAssetCache", "1.1")]
52[assembly: AddinDependency("OpenSim", "0.5")] 52[assembly: AddinDependency("OpenSim", "0.5")]
53 53
54namespace Flotsam.RegionModules.AssetCache 54namespace Flotsam.RegionModules.AssetCache
55{ 55{
56 /// <summary> 56 /// <summary>
57 /// OpenSim.ini Options: 57 /// OpenSim.ini Options:
58 /// ------- 58 /// -------
59 /// [Modules] 59 /// [Modules]
60 /// AssetCaching = "FlotsamAssetCache" 60 /// AssetCaching = "FlotsamAssetCache"
61 /// 61 ///
62 /// [AssetCache] 62 /// [AssetCache]
63 /// ; cache directory can be shared by multiple instances 63 /// ; cache directory can be shared by multiple instances
64 /// CacheDirectory = /directory/writable/by/OpenSim/instance 64 /// CacheDirectory = /directory/writable/by/OpenSim/instance
65 /// 65 ///
66 /// ; Log level 66 /// ; Log level
67 /// ; 0 - (Error) Errors only 67 /// ; 0 - (Error) Errors only
68 /// ; 1 - (Info) Hit Rate Stats + Level 0 68 /// ; 1 - (Info) Hit Rate Stats + Level 0
69 /// ; 2 - (Debug) Cache Activity (Reads/Writes) + Level 1 69 /// ; 2 - (Debug) Cache Activity (Reads/Writes) + Level 1
70 /// ; 70 /// ;
71 /// LogLevel = 1 71 /// LogLevel = 1
72 /// 72 ///
73 /// ; How often should hit rates be displayed (given in AssetRequests) 73 /// ; How often should hit rates be displayed (given in AssetRequests)
74 /// ; 0 to disable 74 /// ; 0 to disable
75 /// HitRateDisplay = 100 75 /// HitRateDisplay = 100
76 /// 76 ///
77 /// ; Set to false for disk cache only. 77 /// ; Set to false for disk cache only.
78 /// MemoryCacheEnabled = true 78 /// MemoryCacheEnabled = true
79 /// 79 ///
80 /// ; How long {in hours} to keep assets cached in memory, .5 == 30 minutes 80 /// ; How long {in hours} to keep assets cached in memory, .5 == 30 minutes
81 /// MemoryCacheTimeout = 2 81 /// MemoryCacheTimeout = 2
82 /// 82 ///
83 /// ; How long {in hours} to keep assets cached on disk, .5 == 30 minutes 83 /// ; How long {in hours} to keep assets cached on disk, .5 == 30 minutes
84 /// ; Specify 0 if you do not want your disk cache to expire 84 /// ; Specify 0 if you do not want your disk cache to expire
85 /// FileCacheTimeout = 0 85 /// FileCacheTimeout = 0
86 /// 86 ///
87 /// ; How often {in hours} should the disk be checked for expired filed 87 /// ; How often {in hours} should the disk be checked for expired filed
88 /// ; Specify 0 to disable expiration checking 88 /// ; Specify 0 to disable expiration checking
89 /// FileCleanupTimer = .166 ;roughly every 10 minutes 89 /// FileCleanupTimer = .166 ;roughly every 10 minutes
90 /// 90 ///
91 /// ; If WAIT_ON_INPROGRESS_REQUESTS has been defined then this specifies how 91 /// ; If WAIT_ON_INPROGRESS_REQUESTS has been defined then this specifies how
92 /// ; long (in miliseconds) to block a request thread while trying to complete 92 /// ; long (in miliseconds) to block a request thread while trying to complete
93 /// ; writing to disk. 93 /// ; writing to disk.
94 /// WaitOnInprogressTimeout = 3000 94 /// WaitOnInprogressTimeout = 3000
95 /// ------- 95 /// -------
96 /// </summary> 96 /// </summary>
97 97
98 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")] 98 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")]
99 public class FlotsamAssetCache : ISharedRegionModule, IImprovedAssetCache 99 public class FlotsamAssetCache : ISharedRegionModule, IImprovedAssetCache
100 { 100 {
101 private static readonly ILog m_log = 101 private static readonly ILog m_log =
102 LogManager.GetLogger( 102 LogManager.GetLogger(
103 MethodBase.GetCurrentMethod().DeclaringType); 103 MethodBase.GetCurrentMethod().DeclaringType);
104 104
105 private bool m_Enabled = false; 105 private bool m_Enabled = false;
106 106
107 private const string m_ModuleName = "FlotsamAssetCache"; 107 private const string m_ModuleName = "FlotsamAssetCache";
108 private const string m_DefaultCacheDirectory = m_ModuleName; 108 private const string m_DefaultCacheDirectory = m_ModuleName;
109 private string m_CacheDirectory = m_DefaultCacheDirectory; 109 private string m_CacheDirectory = m_DefaultCacheDirectory;
110 110
111 111
112 private List<char> m_InvalidChars = new List<char>(); 112 private List<char> m_InvalidChars = new List<char>();
113 113
114 private int m_LogLevel = 1; 114 private int m_LogLevel = 1;
115 private ulong m_HitRateDisplay = 1; // How often to display hit statistics, given in requests 115 private ulong m_HitRateDisplay = 1; // How often to display hit statistics, given in requests
116 116
117 private static ulong m_Requests = 0; 117 private static ulong m_Requests = 0;
118 private static ulong m_RequestsForInprogress = 0; 118 private static ulong m_RequestsForInprogress = 0;
119 private static ulong m_DiskHits = 0; 119 private static ulong m_DiskHits = 0;
120 private static ulong m_MemoryHits = 0; 120 private static ulong m_MemoryHits = 0;
121 private static double m_HitRateMemory = 0.0; 121 private static double m_HitRateMemory = 0.0;
122 private static double m_HitRateFile = 0.0; 122 private static double m_HitRateFile = 0.0;
123 123
124#if WAIT_ON_INPROGRESS_REQUESTS 124#if WAIT_ON_INPROGRESS_REQUESTS
125 private Dictionary<string, ManualResetEvent> m_CurrentlyWriting = new Dictionary<string, ManualResetEvent>(); 125 private Dictionary<string, ManualResetEvent> m_CurrentlyWriting = new Dictionary<string, ManualResetEvent>();
126 private int m_WaitOnInprogressTimeout = 3000; 126 private int m_WaitOnInprogressTimeout = 3000;
127#else 127#else
128 private List<string> m_CurrentlyWriting = new List<string>(); 128 private List<string> m_CurrentlyWriting = new List<string>();
129#endif 129#endif
130 130
131 private ExpiringCache<string, AssetBase> m_MemoryCache = new ExpiringCache<string, AssetBase>(); 131 private ExpiringCache<string, AssetBase> m_MemoryCache = new ExpiringCache<string, AssetBase>();
132 private bool m_MemoryCacheEnabled = true; 132 private bool m_MemoryCacheEnabled = true;
133 133
134 // Expiration is expressed in hours. 134 // Expiration is expressed in hours.
135 private const double m_DefaultMemoryExpiration = 1.0; 135 private const double m_DefaultMemoryExpiration = 1.0;
136 private const double m_DefaultFileExpiration = 48; 136 private const double m_DefaultFileExpiration = 48;
137 private TimeSpan m_MemoryExpiration = TimeSpan.Zero; 137 private TimeSpan m_MemoryExpiration = TimeSpan.Zero;
138 private TimeSpan m_FileExpiration = TimeSpan.Zero; 138 private TimeSpan m_FileExpiration = TimeSpan.Zero;
139 private TimeSpan m_FileExpirationCleanupTimer = TimeSpan.Zero; 139 private TimeSpan m_FileExpirationCleanupTimer = TimeSpan.Zero;
140 140
141 private System.Timers.Timer m_CachCleanTimer = new System.Timers.Timer(); 141 private System.Timers.Timer m_CachCleanTimer = new System.Timers.Timer();
142 142
143 public FlotsamAssetCache() 143 public FlotsamAssetCache()
144 { 144 {
145 m_InvalidChars.AddRange(Path.GetInvalidPathChars()); 145 m_InvalidChars.AddRange(Path.GetInvalidPathChars());
146 m_InvalidChars.AddRange(Path.GetInvalidFileNameChars()); 146 m_InvalidChars.AddRange(Path.GetInvalidFileNameChars());
147 } 147 }
148 148
149 public string Name 149 public string Name
150 { 150 {
151 get { return m_ModuleName; } 151 get { return m_ModuleName; }
152 } 152 }
153 153
154 public void Initialise(IConfigSource source) 154 public void Initialise(IConfigSource source)
155 { 155 {
156 IConfig moduleConfig = source.Configs["Modules"]; 156 IConfig moduleConfig = source.Configs["Modules"];
157 157
158 if (moduleConfig != null) 158 if (moduleConfig != null)
159 { 159 {
160 string name = moduleConfig.GetString("AssetCaching", this.Name); 160 string name = moduleConfig.GetString("AssetCaching", this.Name);
161 161
162 if (name == Name) 162 if (name == Name)
163 { 163 {
164 IConfig assetConfig = source.Configs["AssetCache"]; 164 IConfig assetConfig = source.Configs["AssetCache"];
165 if (assetConfig == null) 165 if (assetConfig == null)
166 { 166 {
167 m_log.Error("[ASSET CACHE]: AssetCache missing from OpenSim.ini"); 167 m_log.Error("[ASSET CACHE]: AssetCache missing from OpenSim.ini");
168 return; 168 return;
169 } 169 }
170 170
171 m_Enabled = true; 171 m_Enabled = true;
172 172
173 m_log.InfoFormat("[ASSET CACHE]: {0} enabled", this.Name); 173 m_log.InfoFormat("[ASSET CACHE]: {0} enabled", this.Name);
174 174
175 m_CacheDirectory = assetConfig.GetString("CacheDirectory", m_DefaultCacheDirectory); 175 m_CacheDirectory = assetConfig.GetString("CacheDirectory", m_DefaultCacheDirectory);
176 m_log.InfoFormat("[ASSET CACHE]: Cache Directory", m_DefaultCacheDirectory); 176 m_log.InfoFormat("[ASSET CACHE]: Cache Directory", m_DefaultCacheDirectory);
177 177
178 m_MemoryCacheEnabled = assetConfig.GetBoolean("MemoryCacheEnabled", true); 178 m_MemoryCacheEnabled = assetConfig.GetBoolean("MemoryCacheEnabled", true);
179 m_MemoryExpiration = TimeSpan.FromHours(assetConfig.GetDouble("MemoryCacheTimeout", m_DefaultMemoryExpiration)); 179 m_MemoryExpiration = TimeSpan.FromHours(assetConfig.GetDouble("MemoryCacheTimeout", m_DefaultMemoryExpiration));
180 180
181#if WAIT_ON_INPROGRESS_REQUESTS 181#if WAIT_ON_INPROGRESS_REQUESTS
182 m_WaitOnInprogressTimeout = assetConfig.GetInt("WaitOnInprogressTimeout", 3000); 182 m_WaitOnInprogressTimeout = assetConfig.GetInt("WaitOnInprogressTimeout", 3000);
183#endif 183#endif
184 184
185 m_HitRateDisplay = (ulong)assetConfig.GetInt("HitRateDisplay", 1); 185 m_HitRateDisplay = (ulong)assetConfig.GetInt("HitRateDisplay", 1);
186 186
187 m_FileExpiration = TimeSpan.FromHours(assetConfig.GetDouble("FileCacheTimeout", m_DefaultFileExpiration)); 187 m_FileExpiration = TimeSpan.FromHours(assetConfig.GetDouble("FileCacheTimeout", m_DefaultFileExpiration));
188 m_FileExpirationCleanupTimer = TimeSpan.FromHours(assetConfig.GetDouble("FileCleanupTimer", m_DefaultFileExpiration)); 188 m_FileExpirationCleanupTimer = TimeSpan.FromHours(assetConfig.GetDouble("FileCleanupTimer", m_DefaultFileExpiration));
189 if ((m_FileExpiration > TimeSpan.Zero) && (m_FileExpirationCleanupTimer > TimeSpan.Zero)) 189 if ((m_FileExpiration > TimeSpan.Zero) && (m_FileExpirationCleanupTimer > TimeSpan.Zero))
190 { 190 {
191 m_CachCleanTimer.Interval = m_FileExpirationCleanupTimer.TotalMilliseconds; 191 m_CachCleanTimer.Interval = m_FileExpirationCleanupTimer.TotalMilliseconds;
192 m_CachCleanTimer.AutoReset = true; 192 m_CachCleanTimer.AutoReset = true;
193 m_CachCleanTimer.Elapsed += CleanupExpiredFiles; 193 m_CachCleanTimer.Elapsed += CleanupExpiredFiles;
194 m_CachCleanTimer.Enabled = true; 194 m_CachCleanTimer.Enabled = true;
195 m_CachCleanTimer.Start(); 195 m_CachCleanTimer.Start();
196 } 196 }
197 else 197 else
198 { 198 {
199 m_CachCleanTimer.Enabled = false; 199 m_CachCleanTimer.Enabled = false;
200 } 200 }
201 } 201 }
202 } 202 }
203 } 203 }
204 204
205 public void PostInitialise() 205 public void PostInitialise()
206 { 206 {
207 } 207 }
208 208
209 public void Close() 209 public void Close()
210 { 210 {
211 } 211 }
212 212
213 public void AddRegion(Scene scene) 213 public void AddRegion(Scene scene)
214 { 214 {
215 if (m_Enabled) 215 if (m_Enabled)
216 scene.RegisterModuleInterface<IImprovedAssetCache>(this); 216 scene.RegisterModuleInterface<IImprovedAssetCache>(this);
217 } 217 }
218 218
219 public void RemoveRegion(Scene scene) 219 public void RemoveRegion(Scene scene)
220 { 220 {
221 } 221 }
222 222
223 public void RegionLoaded(Scene scene) 223 public void RegionLoaded(Scene scene)
224 { 224 {
225 } 225 }
226 226
227 //////////////////////////////////////////////////////////// 227 ////////////////////////////////////////////////////////////
228 // IImprovedAssetCache 228 // IImprovedAssetCache
229 // 229 //
230 230
231 private void UpdateMemoryCache(string key, AssetBase asset) 231 private void UpdateMemoryCache(string key, AssetBase asset)
232 { 232 {
233 if( m_MemoryCacheEnabled ) 233 if( m_MemoryCacheEnabled )
234 { 234 {
235 if (m_MemoryExpiration > TimeSpan.Zero) 235 if (m_MemoryExpiration > TimeSpan.Zero)
236 { 236 {
237 m_MemoryCache.AddOrUpdate(key, asset, m_MemoryExpiration); 237 m_MemoryCache.AddOrUpdate(key, asset, m_MemoryExpiration);
238 } 238 }
239 else 239 else
240 { 240 {
241 m_MemoryCache.AddOrUpdate(key, asset, DateTime.MaxValue); 241 m_MemoryCache.AddOrUpdate(key, asset, DateTime.MaxValue);
242 } 242 }
243 } 243 }
244 } 244 }
245 245
246 public void Cache(AssetBase asset) 246 public void Cache(AssetBase asset)
247 { 247 {
248 // TODO: Spawn this off to some seperate thread to do the actual writing 248 // TODO: Spawn this off to some seperate thread to do the actual writing
249 if (asset != null) 249 if (asset != null)
250 { 250 {
251 UpdateMemoryCache(asset.ID, asset); 251 UpdateMemoryCache(asset.ID, asset);
252 252
253 string filename = GetFileName(asset.ID); 253 string filename = GetFileName(asset.ID);
254 254
255 try 255 try
256 { 256 {
257 // If the file is already cached, don't cache it, just touch it so access time is updated 257 // If the file is already cached, don't cache it, just touch it so access time is updated
258 if (File.Exists(filename)) 258 if (File.Exists(filename))
259 { 259 {
260 File.SetLastAccessTime(filename, DateTime.Now); 260 File.SetLastAccessTime(filename, DateTime.Now);
261 } else { 261 } else {
262 262
263 // Once we start writing, make sure we flag that we're writing 263 // Once we start writing, make sure we flag that we're writing
264 // that object to the cache so that we don't try to write the 264 // that object to the cache so that we don't try to write the
265 // same file multiple times. 265 // same file multiple times.
266 lock (m_CurrentlyWriting) 266 lock (m_CurrentlyWriting)
267 { 267 {
268#if WAIT_ON_INPROGRESS_REQUESTS 268#if WAIT_ON_INPROGRESS_REQUESTS
269 if (m_CurrentlyWriting.ContainsKey(filename)) 269 if (m_CurrentlyWriting.ContainsKey(filename))
270 { 270 {
271 return; 271 return;
272 } 272 }
273 else 273 else
274 { 274 {
275 m_CurrentlyWriting.Add(filename, new ManualResetEvent(false)); 275 m_CurrentlyWriting.Add(filename, new ManualResetEvent(false));
276 } 276 }
277 277
278#else 278#else
279 if (m_CurrentlyWriting.Contains(filename)) 279 if (m_CurrentlyWriting.Contains(filename))
280 { 280 {
281 return; 281 return;
282 } 282 }
283 else 283 else
284 { 284 {
285 m_CurrentlyWriting.Add(filename); 285 m_CurrentlyWriting.Add(filename);
286 } 286 }
287#endif 287#endif
288 288
289 } 289 }
290 290
291 ThreadPool.QueueUserWorkItem( 291 ThreadPool.QueueUserWorkItem(
292 delegate 292 delegate
293 { 293 {
294 WriteFileCache(filename, asset); 294 WriteFileCache(filename, asset);
295 } 295 }
296 ); 296 );
297 } 297 }
298 } 298 }
299 catch (Exception e) 299 catch (Exception e)
300 { 300 {
301 LogException(e); 301 LogException(e);
302 } 302 }
303 } 303 }
304 } 304 }
305 305
306 public AssetBase Get(string id) 306 public AssetBase Get(string id)
307 { 307 {
308 m_Requests++; 308 m_Requests++;
309 309
310 AssetBase asset = null; 310 AssetBase asset = null;
311 311
312 if (m_MemoryCacheEnabled && m_MemoryCache.TryGetValue(id, out asset)) 312 if (m_MemoryCacheEnabled && m_MemoryCache.TryGetValue(id, out asset))
313 { 313 {
314 m_MemoryHits++; 314 m_MemoryHits++;
315 } 315 }
316 else 316 else
317 { 317 {
318 string filename = GetFileName(id); 318 string filename = GetFileName(id);
319 if (File.Exists(filename)) 319 if (File.Exists(filename))
320 { 320 {
321 try 321 try
322 { 322 {
323 FileStream stream = File.Open(filename, FileMode.Open); 323 FileStream stream = File.Open(filename, FileMode.Open);
324 BinaryFormatter bformatter = new BinaryFormatter(); 324 BinaryFormatter bformatter = new BinaryFormatter();
325 325
326 asset = (AssetBase)bformatter.Deserialize(stream); 326 asset = (AssetBase)bformatter.Deserialize(stream);
327 stream.Close(); 327 stream.Close();
328 328
329 UpdateMemoryCache(id, asset); 329 UpdateMemoryCache(id, asset);
330 330
331 m_DiskHits++; 331 m_DiskHits++;
332 } 332 }
333 catch (System.Runtime.Serialization.SerializationException e) 333 catch (System.Runtime.Serialization.SerializationException e)
334 { 334 {
335 LogException(e); 335 LogException(e);
336 336
337 // If there was a problem deserializing the asset, the asset may 337 // If there was a problem deserializing the asset, the asset may
338 // either be corrupted OR was serialized under an old format 338 // either be corrupted OR was serialized under an old format
339 // {different version of AssetBase} -- we should attempt to 339 // {different version of AssetBase} -- we should attempt to
340 // delete it and re-cache 340 // delete it and re-cache
341 File.Delete(filename); 341 File.Delete(filename);
342 } 342 }
343 catch (Exception e) 343 catch (Exception e)
344 { 344 {
345 LogException(e); 345 LogException(e);
346 } 346 }
347 } 347 }
348 348
349 349
350#if WAIT_ON_INPROGRESS_REQUESTS 350#if WAIT_ON_INPROGRESS_REQUESTS
351 // Check if we're already downloading this asset. If so, try to wait for it to 351 // Check if we're already downloading this asset. If so, try to wait for it to
352 // download. 352 // download.
353 if (m_WaitOnInprogressTimeout > 0) 353 if (m_WaitOnInprogressTimeout > 0)
354 { 354 {
355 m_RequestsForInprogress++; 355 m_RequestsForInprogress++;
356 356
357 ManualResetEvent waitEvent; 357 ManualResetEvent waitEvent;
358 if (m_CurrentlyWriting.TryGetValue(filename, out waitEvent)) 358 if (m_CurrentlyWriting.TryGetValue(filename, out waitEvent))
359 { 359 {
360 waitEvent.WaitOne(m_WaitOnInprogressTimeout); 360 waitEvent.WaitOne(m_WaitOnInprogressTimeout);
361 return Get(id); 361 return Get(id);
362 } 362 }
363 } 363 }
364#else 364#else
365 // Track how often we have the problem that an asset is requested while 365 // Track how often we have the problem that an asset is requested while
366 // it is still being downloaded by a previous request. 366 // it is still being downloaded by a previous request.
367 if (m_CurrentlyWriting.Contains(filename)) 367 if (m_CurrentlyWriting.Contains(filename))
368 { 368 {
369 m_RequestsForInprogress++; 369 m_RequestsForInprogress++;
370 } 370 }
371#endif 371#endif
372 } 372 }
373 373
374 if (((m_LogLevel >= 1)) && (m_HitRateDisplay != 0) && (m_Requests % m_HitRateDisplay == 0)) 374 if (((m_LogLevel >= 1)) && (m_HitRateDisplay != 0) && (m_Requests % m_HitRateDisplay == 0))
375 { 375 {
376 m_HitRateFile = (double)m_DiskHits / m_Requests * 100.0; 376 m_HitRateFile = (double)m_DiskHits / m_Requests * 100.0;
377 377
378 m_log.InfoFormat("[ASSET CACHE]: Cache Get :: {0} :: {1}", id, asset == null ? "Miss" : "Hit"); 378 m_log.InfoFormat("[ASSET CACHE]: Cache Get :: {0} :: {1}", id, asset == null ? "Miss" : "Hit");
379 m_log.InfoFormat("[ASSET CACHE]: File Hit Rate {0}% for {1} requests", m_HitRateFile.ToString("0.00"), m_Requests); 379 m_log.InfoFormat("[ASSET CACHE]: File Hit Rate {0}% for {1} requests", m_HitRateFile.ToString("0.00"), m_Requests);
380 380
381 if (m_MemoryCacheEnabled) 381 if (m_MemoryCacheEnabled)
382 { 382 {
383 m_HitRateMemory = (double)m_MemoryHits / m_Requests * 100.0; 383 m_HitRateMemory = (double)m_MemoryHits / m_Requests * 100.0;
384 m_log.InfoFormat("[ASSET CACHE]: Memory Hit Rate {0}% for {1} requests", m_HitRateMemory.ToString("0.00"), m_Requests); 384 m_log.InfoFormat("[ASSET CACHE]: Memory Hit Rate {0}% for {1} requests", m_HitRateMemory.ToString("0.00"), m_Requests);
385 } 385 }
386 386
387 m_log.InfoFormat("[ASSET CACHE]: {0} unnessesary requests due to requests for assets that are currently downloading.", m_RequestsForInprogress); 387 m_log.InfoFormat("[ASSET CACHE]: {0} unnessesary requests due to requests for assets that are currently downloading.", m_RequestsForInprogress);
388 388
389 } 389 }
390 390
391 return asset; 391 return asset;
392 } 392 }
393 393
394 public void Expire(string id) 394 public void Expire(string id)
395 { 395 {
396 if (m_LogLevel >= 2) 396 if (m_LogLevel >= 2)
397 m_log.DebugFormat("[ASSET CACHE]: Expiring Asset {0}.", id); 397 m_log.DebugFormat("[ASSET CACHE]: Expiring Asset {0}.", id);
398 398
399 try 399 try
400 { 400 {
401 string filename = GetFileName(id); 401 string filename = GetFileName(id);
402 if (File.Exists(filename)) 402 if (File.Exists(filename))
403 { 403 {
404 File.Delete(filename); 404 File.Delete(filename);
405 } 405 }
406 406
407 if( m_MemoryCacheEnabled ) 407 if( m_MemoryCacheEnabled )
408 m_MemoryCache.Remove(id); 408 m_MemoryCache.Remove(id);
409 } 409 }
410 catch (Exception e) 410 catch (Exception e)
411 { 411 {
412 LogException(e); 412 LogException(e);
413 } 413 }
414 } 414 }
415 415
416 public void Clear() 416 public void Clear()
417 { 417 {
418 if (m_LogLevel >= 2) 418 if (m_LogLevel >= 2)
419 m_log.Debug("[ASSET CACHE]: Clearing Cache."); 419 m_log.Debug("[ASSET CACHE]: Clearing Cache.");
420 420
421 foreach (string dir in Directory.GetDirectories(m_CacheDirectory)) 421 foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
422 { 422 {
423 Directory.Delete(dir); 423 Directory.Delete(dir);
424 } 424 }
425 425
426 if( m_MemoryCacheEnabled ) 426 if( m_MemoryCacheEnabled )
427 m_MemoryCache.Clear(); 427 m_MemoryCache.Clear();
428 } 428 }
429 429
430 private void CleanupExpiredFiles(object source, ElapsedEventArgs e) 430 private void CleanupExpiredFiles(object source, ElapsedEventArgs e)
431 { 431 {
432 if (m_LogLevel >= 2) 432 if (m_LogLevel >= 2)
433 m_log.DebugFormat("[ASSET CACHE]: Checking for expired files older then {0}.", m_FileExpiration.ToString()); 433 m_log.DebugFormat("[ASSET CACHE]: Checking for expired files older then {0}.", m_FileExpiration.ToString());
434 434
435 foreach (string dir in Directory.GetDirectories(m_CacheDirectory)) 435 foreach (string dir in Directory.GetDirectories(m_CacheDirectory))
436 { 436 {
437 foreach (string file in Directory.GetFiles(dir)) 437 foreach (string file in Directory.GetFiles(dir))
438 { 438 {
439 if (DateTime.Now - File.GetLastAccessTime(file) > m_FileExpiration) 439 if (DateTime.Now - File.GetLastAccessTime(file) > m_FileExpiration)
440 { 440 {
441 File.Delete(file); 441 File.Delete(file);
442 } 442 }
443 } 443 }
444 } 444 }
445 } 445 }
446 446
447 private string GetFileName(string id) 447 private string GetFileName(string id)
448 { 448 {
449 // Would it be faster to just hash the darn thing? 449 // Would it be faster to just hash the darn thing?
450 foreach (char c in m_InvalidChars) 450 foreach (char c in m_InvalidChars)
451 { 451 {
452 id = id.Replace(c, '_'); 452 id = id.Replace(c, '_');
453 } 453 }
454 454
455 string p = id.Substring(id.Length - 4); 455 string p = id.Substring(id.Length - 4);
456 p = Path.Combine(p, id); 456 p = Path.Combine(p, id);
457 return Path.Combine(m_CacheDirectory, p); 457 return Path.Combine(m_CacheDirectory, p);
458 } 458 }
459 459
460 private void WriteFileCache(string filename, AssetBase asset) 460 private void WriteFileCache(string filename, AssetBase asset)
461 { 461 {
462 try 462 try
463 { 463 {
464 // Make sure the target cache directory exists 464 // Make sure the target cache directory exists
465 string directory = Path.GetDirectoryName(filename); 465 string directory = Path.GetDirectoryName(filename);
466 if (!Directory.Exists(directory)) 466 if (!Directory.Exists(directory))
467 { 467 {
468 Directory.CreateDirectory(directory); 468 Directory.CreateDirectory(directory);
469 } 469 }
470 470
471 // Write file first to a temp name, so that it doesn't look 471 // Write file first to a temp name, so that it doesn't look
472 // like it's already cached while it's still writing. 472 // like it's already cached while it's still writing.
473 string tempname = Path.Combine(directory, Path.GetRandomFileName()); 473 string tempname = Path.Combine(directory, Path.GetRandomFileName());
474 Stream stream = File.Open(tempname, FileMode.Create); 474 Stream stream = File.Open(tempname, FileMode.Create);
475 BinaryFormatter bformatter = new BinaryFormatter(); 475 BinaryFormatter bformatter = new BinaryFormatter();
476 bformatter.Serialize(stream, asset); 476 bformatter.Serialize(stream, asset);
477 stream.Close(); 477 stream.Close();
478 478
479 // Now that it's written, rename it so that it can be found. 479 // Now that it's written, rename it so that it can be found.
480 File.Move(tempname, filename); 480 File.Move(tempname, filename);
481 481
482 if (m_LogLevel >= 2) 482 if (m_LogLevel >= 2)
483 m_log.DebugFormat("[ASSET CACHE]: Cache Stored :: {0}", asset.ID); 483 m_log.DebugFormat("[ASSET CACHE]: Cache Stored :: {0}", asset.ID);
484 } 484 }
485 catch (Exception e) 485 catch (Exception e)
486 { 486 {
487 LogException(e); 487 LogException(e);
488 } 488 }
489 finally 489 finally
490 { 490 {
491 // Even if the write fails with an exception, we need to make sure 491 // Even if the write fails with an exception, we need to make sure
492 // that we release the lock on that file, otherwise it'll never get 492 // that we release the lock on that file, otherwise it'll never get
493 // cached 493 // cached
494 lock (m_CurrentlyWriting) 494 lock (m_CurrentlyWriting)
495 { 495 {
496#if WAIT_ON_INPROGRESS_REQUESTS 496#if WAIT_ON_INPROGRESS_REQUESTS
497 ManualResetEvent waitEvent; 497 ManualResetEvent waitEvent;
498 if (m_CurrentlyWriting.TryGetValue(filename, out waitEvent)) 498 if (m_CurrentlyWriting.TryGetValue(filename, out waitEvent))
499 { 499 {
500 m_CurrentlyWriting.Remove(filename); 500 m_CurrentlyWriting.Remove(filename);
501 waitEvent.Set(); 501 waitEvent.Set();
502 } 502 }
503#else 503#else
504 if (m_CurrentlyWriting.Contains(filename)) 504 if (m_CurrentlyWriting.Contains(filename))
505 { 505 {
506 m_CurrentlyWriting.Remove(filename); 506 m_CurrentlyWriting.Remove(filename);
507 } 507 }
508#endif 508#endif
509 } 509 }
510 510
511 } 511 }
512 } 512 }
513 513
514 private static void LogException(Exception e) 514 private static void LogException(Exception e)
515 { 515 {
516 string[] text = e.ToString().Split(new char[] { '\n' }); 516 string[] text = e.ToString().Split(new char[] { '\n' });
517 foreach (string t in text) 517 foreach (string t in text)
518 { 518 {
519 m_log.ErrorFormat("[ASSET CACHE]: {0} ", t); 519 m_log.ErrorFormat("[ASSET CACHE]: {0} ", t);
520 } 520 }
521 } 521 }
522 } 522 }
523} 523}