mysimulation/server/tso.common/Utils/Cache/FileSystemCache.cs

341 lines
9.9 KiB
C#
Raw Permalink Normal View History

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace FSO.Common.Utils.Cache
{
public class FileSystemCache : ICache
{
private const int DIGEST_DELAY = 10000;
private string _Directory { get; set; }
private Queue<FileSystemCacheMutation> _Mutations;
private Thread _DigestThread;
private bool _Active;
private long _CacheSize;
private long _MaxCacheSize;
private Thread _MainThread;
private LinkedList<FileSystemCacheEntry> _Cache;
private Dictionary<CacheKey, FileSystemCacheEntry> _Index;
public FileSystemCache(string directory, long maxSize)
{
_MainThread = Thread.CurrentThread;
_Directory = directory;
_Mutations = new Queue<FileSystemCacheMutation>();
_CacheSize = 0;
_MaxCacheSize = maxSize;
_Cache = new LinkedList<FileSystemCacheEntry>();
_Index = new Dictionary<CacheKey, FileSystemCacheEntry>();
}
public void Init()
{
var tempList = new List<FileSystemCacheEntry>();
_ScanDirectory(CacheKey.Root, tempList);
foreach(var item in tempList.OrderByDescending(x => x.LastRead)){
_Cache.AddLast(item);
_Index.Add(item.Key, item);
_CacheSize += item.Length;
}
_Active = true;
_DigestThread = new Thread(DigestLoop);
_DigestThread.Priority = ThreadPriority.BelowNormal;
_DigestThread.Start();
}
private void DigestLoop()
{
while (_Active && _MainThread?.IsAlive != false)
{
Digest();
Thread.Sleep(DIGEST_DELAY);
}
}
private void Digest()
{
lock (_Mutations)
{
/**
* We can avoid some work & disk hits by removing tasks for keys modified in later tasks
*/
List<FileSystemCacheMutation> tasks = new List<FileSystemCacheMutation>();
Dictionary<CacheKey, FileSystemCacheMutation> taskIndex = new Dictionary<CacheKey, FileSystemCacheMutation>();
FileSystemCacheMutation mutation;
while (_Mutations.Count > 0 && (mutation = _Mutations.Dequeue()) != null)
{
tasks.Add(mutation);
FileSystemCacheMutation previousTask = null;
taskIndex.TryGetValue(mutation.Key, out previousTask);
if (previousTask == null) {
taskIndex[mutation.Key] = mutation;
continue;
}
tasks.Remove(previousTask);
taskIndex[mutation.Key] = mutation;
}
int bytesRequired = 0;
foreach(var task in tasks){
bytesRequired += task.GetBytesRequired(this);
}
if(bytesRequired > 0 && (_CacheSize + bytesRequired) >= _MaxCacheSize)
{
//We need to evict some entries to make room
while((_CacheSize + bytesRequired) >= _MaxCacheSize && _Cache.Count > 0)
{
var last = _Cache.Last;
try {
new FileSystemCacheRemoveMutation() {
Key = last.Value.Key
}.Execute(this);
}catch(Exception ex){
}
CalculateCacheSize();
}
}
foreach (var task in tasks)
{
try
{
task.Execute(this);
}
catch (Exception ex)
{
}
}
//Recalculate cache size if we made changes
if(tasks.Count > 0){
CalculateCacheSize();
}
}
}
private void CalculateCacheSize()
{
long size = 0;
foreach(var item in _Cache){
size += item.Length;
}
_CacheSize = size;
}
private void _ScanDirectory(CacheKey parent, List<FileSystemCacheEntry> tempList)
{
var dir = GetFilePath(parent);
if (!Directory.Exists(dir)) { return; }
var info = new DirectoryInfo(dir);
foreach(FileInfo file in info.GetFiles())
{
var key = CacheKey.Combine(parent, file.Name);
tempList.Add(new FileSystemCacheEntry {
Key = key,
LastRead = file.LastAccessTime,
LastWrite = file.LastWriteTime,
Length = file.Length
});
}
foreach(var subDir in info.GetDirectories())
{
var key = CacheKey.Combine(parent, subDir.Name);
_ScanDirectory(key, tempList);
}
}
public bool ContainsKey(CacheKey key)
{
return _Index.ContainsKey(key);
}
public void Add(CacheKey key, byte[] bytes)
{
var clone = new byte[bytes.Length];
Buffer.BlockCopy(bytes, 0, clone, 0, bytes.Length);
lock (_Mutations)
{
_Mutations.Enqueue(new FileSystemCacheAddMutation
{
Key = key,
Data = clone
});
}
}
public void Remove(CacheKey key)
{
lock (_Mutations)
{
_Mutations.Enqueue(new FileSystemCacheRemoveMutation
{
Key = key
});
}
}
public Task<T> Get<T>(CacheKey key)
{
if(typeof(T) == typeof(byte[]))
{
return Task.Factory.StartNew(() =>
{
var file = GetFilePath(key);
byte[] result = null;
if (File.Exists(file)){
result = File.ReadAllBytes(file);
}else{
throw new Exception("File not found");
}
TouchEntry(key);
return (T)(object)result;
});
}
throw new Exception("Not implemented yet");
}
public void Dispose()
{
_Active = false;
}
internal string GetFilePath(CacheKey key)
{
return Path.Combine(_Directory, Path.Combine(key.Components));
}
internal FileSystemCacheEntry GetEntry(CacheKey key)
{
FileSystemCacheEntry entry = null;
_Index.TryGetValue(key, out entry);
return entry;
}
internal void AddEntry(FileSystemCacheEntry entry)
{
var existing = GetEntry(entry.Key);
if(existing != null){
_Cache.Remove(existing);
}
_Cache.AddLast(entry);
_Index[entry.Key] = entry;
}
internal void RemoveEntry(CacheKey key)
{
var existing = GetEntry(key);
if (existing != null)
{
_Cache.Remove(existing);
_Index.Remove(key);
}
}
internal void TouchEntry(CacheKey key)
{
var existing = GetEntry(key);
if (existing != null)
{
_Cache.AddFirst(existing);
}
}
}
public interface FileSystemCacheMutation
{
CacheKey Key { get; }
int GetBytesRequired(FileSystemCache cache);
void Execute(FileSystemCache cache);
}
public class FileSystemCacheAddMutation : FileSystemCacheMutation
{
public CacheKey Key { get; set; }
public byte[] Data { get; set; }
public void Execute(FileSystemCache cache)
{
var path = cache.GetFilePath(Key);
var finalPart = path.LastIndexOf('/');
Directory.CreateDirectory((finalPart == -1)?path:path.Substring(0, finalPart));
File.WriteAllBytes(path, Data);
var entry = cache.GetEntry(Key);
if (entry == null)
{
entry = new FileSystemCacheEntry();
entry.Key = Key;
entry.LastRead = DateTime.MinValue;
entry.LastWrite = DateTime.Now;
entry.Length = Data.Length;
cache.AddEntry(entry);
}
else
{
entry.Length = Data.Length;
}
}
public int GetBytesRequired(FileSystemCache cache)
{
var existingFile = cache.GetEntry(Key);
if(existingFile != null)
{
return Data.Length - (int)existingFile.Length;
}
return Data.Length;
}
}
public class FileSystemCacheRemoveMutation : FileSystemCacheMutation
{
public CacheKey Key { get; set; }
public void Execute(FileSystemCache cache)
{
File.Delete(cache.GetFilePath(Key));
cache.RemoveEntry(Key);
}
public int GetBytesRequired(FileSystemCache cache)
{
var existingFile = cache.GetEntry(Key);
if (existingFile != null)
{
return -((int)existingFile.Length);
}
return 0;
}
}
public class FileSystemCacheEntry
{
public CacheKey Key;
public DateTime LastWrite;
public DateTime LastRead;
public long Length;
}
}