Skip to content

Instantly share code, notes, and snippets.

@epetrie
Created June 5, 2013 03:20
Show Gist options
  • Save epetrie/5711395 to your computer and use it in GitHub Desktop.
Save epetrie/5711395 to your computer and use it in GitHub Desktop.
hrmmmm
public static class XmlFileManager<T> where T : class, IXmlSerializable<T>, new()
{
private static readonly Dictionary<XmlFileId<T>, XmlFileSet<T>> FileSets = new Dictionary<XmlFileId<T>, XmlFileSet<T>>();
private static readonly TimeSpan SaveCacheInterval = TimeSpan.FromSeconds(3);
private static readonly TimeoutDispatcher Dispatcher = new TimeoutDispatcher();
static XmlFileManager()
{
Dispatcher.AddRepeating(SaveAllCached, EventLogger.LogException, SaveCacheInterval);
}
private static void SaveAllCached()
{
List<XmlFileSet<T>> fileSets;
lock (FileSets)
fileSets = FileSets.Values.ToList();
foreach (var fileSet in fileSets)
if (fileSet != null && fileSet.EnableCaching)
lock (FileSets)
fileSet.ForceWriteCache();
}
public static bool IsRegistered(string file)
{
return GetRegisteredId(file) != null;
}
public static bool IsRegistered(XmlFileId<T> id)
{
lock (FileSets)
{
return FileSets.ContainsKey(id);
}
}
public static XmlFileId<T> GetRegisteredId(string file)
{
lock (FileSets)
{
var id = FileSets.Keys.FirstOrDefault(fileSet => fileSet.File == file);
return id;
}
}
public static XmlFileId<T> Register(string file, bool enableCaching = true, bool safeMode = false, bool keepBackups = true, string tempDir = null, string backupDir = null)
{
lock (FileSets)
{
var id = GetRegisteredId(file);
if (id != null)
Unregister(id);
id = new XmlFileId<T>(file);
FileSets[id] = new XmlFileSet<T>(id, file, safeMode, keepBackups, tempDir, backupDir);
FileSets[id].EnableCaching = enableCaching;
return id;
}
}
public static void Unregister(XmlFileId<T> id, bool cleanup = false)
{
lock (FileSets)
{
var fileSet = GetFileSet(id);
if (fileSet == null)
return;
if (cleanup)
fileSet.CleanUp();
FileSets.Remove(id);
}
}
public static T Load(XmlFileId<T> id, bool forceReload = false, Action<T> actionPost = null)
{
var fileSet = GetFileSet(id);
if (fileSet == null)
return null;
var item = fileSet.Load(forceReload);
if (item != null && actionPost != null)
actionPost(item);
return item;
}
public static void Save(XmlFileId<T> id, T item, bool forceSave = false, Action<T> actionPre = null)
{
if (item == null)
return;
var fileSet = GetFileSet(id);
if (fileSet == null)
return;
if (actionPre != null)
actionPre(item);
if (forceSave || !fileSet.EnableCaching)
{
fileSet.Save(item);
return;
}
fileSet.StoreCache(item);
}
public static void ClearCache(params XmlFileId<T>[] ids)
{
foreach (var id in ids)
{
lock (FileSets)
{
var fileSet = GetFileSet(id);
if (fileSet == null)
continue;
fileSet.ClearCachedItem();
}
}
}
private static XmlFileSet<T> GetFileSet(XmlFileId<T> id)
{
lock (FileSets)
{
XmlFileSet<T> fileSet;
if (!FileSets.TryGetValue(id, out fileSet))
return null;
return fileSet;
}
}
}
#region XmlFileSet
internal class XmlFileSet<T> where T : class, IXmlSerializable<T>, new()
{
#region Public Properties
public XmlFile<T> MainFile { get; private set; }
public XmlFile<T> TempFile { get; private set; }
public XmlFile<T> BackupFile { get; private set; }
public bool SafeMode { get; private set; }
public bool EnableCaching { get; set; }
public bool KeepBackups { get; private set; }
public TimeSpan ClearCacheInterval { get; set; }
#endregion
#region Private Fields
private T _cachedItem;
private DateTime _lastCacheUpdate = DateTime.MinValue;
private bool _changesPending = false;
private readonly object _syncRoot = new object();
#endregion
#region ctor / init
public XmlFileSet(XmlFileId<T> id, string file, bool safeMode = false, bool keepBackups = true, string tempDir = null,
string backupDir = null)
{
this.EnableCaching = true;
this.ClearCacheInterval = TimeSpan.FromSeconds(60);
this.SafeMode = safeMode;
this.KeepBackups = keepBackups;
InitializeXmlFiles(id, file, tempDir, backupDir);
}
public void InitializeXmlFiles(XmlFileId<T> id, string file, string tempDir, string backupDir)
{
this.MainFile = new XmlFile<T>(id, file);
if (!this.SafeMode)
return;
var temp = tempDir ?? Path.GetDirectoryName(this.MainFile.File);
var backup = backupDir ?? temp;
this.TempFile = new XmlFile<T>(id, Path.Combine(temp, this.MainFile.FileName + ".tmp"));
this.BackupFile = new XmlFile<T>(id, Path.Combine(backup, this.MainFile.FileName + ".bak"));
}
#endregion
public T Load(bool forceReload = false)
{
lock (_syncRoot)
{
if (this.EnableCaching)
{
if (forceReload)
{
if (_changesPending)
ForceWriteCache();
}
else
{
var clone = GetCachedItem();
if (clone != null)
return clone;
}
}
XmlFile<T> xmlFile = null;
try
{
var item = FindInstance(out xmlFile);
if (item == null)
return null;
if (xmlFile.Id.Id != this.MainFile.Id.Id)
{
xmlFile.Move(this.MainFile.File);
xmlFile = this.MainFile;
Trace.WriteLine(this.MainFile.File + " was missing, but we found a backup at " + xmlFile.File, "PelcoAPI");
}
if (this.EnableCaching)
SetCachedItem(item);
return item;
}
catch (Exception ex)
{
EventLogger.LogException(ex);
if (xmlFile != null)
{
Trace.WriteLine(xmlFile.File + " might be corrupted, moving to " + xmlFile.File + ".broken", "PelcoAPI");
MoveBroken(xmlFile);
}
}
return null;
}
}
public void Save(T item)
{
// make sure no other threads can modify using this instance
lock (_syncRoot)
{
try
{
var xmlFile = this.SafeMode ? this.TempFile : this.MainFile;
using (new NoShutdown())
{
xmlFile.WriteInstance(item);
if (this.SafeMode)
SafeMove();
}
if (this.EnableCaching)
SetCachedItem(item);
_changesPending = false;
Trace.WriteLine("Wrote File: " + xmlFile.FileName);
}
catch (Exception ex)
{
// probably a bad thing if we can't write our ddf and whatnot
EventLogger.LogException(ex);
Trace.WriteLine("Unable to write " + this.MainFile.File);
throw;
}
}
}
public void StoreCache(T item)
{
lock (_syncRoot)
{
if (!this.EnableCaching || item == null)
return;
SetCachedItem(item);
_changesPending = true;
}
}
public void ForceWriteCache()
{
lock (_syncRoot)
{
if (!_changesPending || !this.EnableCaching || _cachedItem == null)
return;
Save(_cachedItem);
}
}
public void CleanUp(bool removeTempDir = false)
{
if (!this.SafeMode)
return;
lock (_syncRoot)
{
if (removeTempDir)
{
if (this.TempFile.DirectoryExists)
Directory.Delete(this.TempFile.Directory);
if (this.BackupFile.DirectoryExists)
Directory.Delete(this.BackupFile.Directory);
return;
}
this.TempFile.Delete();
this.BackupFile.Delete();
}
}
private void SafeMove()
{
if (!this.SafeMode || !this.TempFile.FileExists)
return;
// make sure we always have at least one valid file at all times.
if (this.MainFile.FileExists)
this.MainFile.Move(this.BackupFile.File);
this.TempFile.Move(this.MainFile.File);
if (!this.KeepBackups)
this.BackupFile.Delete();
}
private T FindInstance(out XmlFile<T> file)
{
T item = null;
if ((item = (file = this.MainFile).ReadInstance(true)) == null)
if (this.SafeMode)
if ((item = (file = this.TempFile).ReadInstance(true)) == null)
if ((item = (file = this.BackupFile).ReadInstance(true)) == null)
return null;
return item;
}
private void MoveBroken(XmlFile<T> xmlFile)
{
var broken = xmlFile.File + ".broken";
xmlFile.Move(broken);
}
private T GetCachedItem()
{
lock (_syncRoot)
{
if (_cachedItem == null)
return null;
var now = DateTime.Now;
if ((now - _lastCacheUpdate).TotalSeconds < this.ClearCacheInterval.TotalSeconds)
return _cachedItem.Clone();
ClearCachedItem();
return null;
}
}
private void SetCachedItem(T item)
{
lock (_syncRoot)
{
_cachedItem = item != null ? item.Clone() : null;
_lastCacheUpdate = DateTime.Now;
}
}
public void ClearCachedItem()
{
lock (_syncRoot)
{
_cachedItem = null;
_lastCacheUpdate = DateTime.Now.Subtract(this.ClearCacheInterval);
}
}
}
#endregion
#region XmlFile
internal class XmlFile<T> where T : class, IXmlSerializable<T>, new()
{
public XmlFileId<T> Id { get; private set; }
public string File { get; private set; }
private readonly object _fileLock = new object();
public string FileName
{
get { return Path.GetFileName(this.File); }
}
public string Directory
{
get { return Path.GetDirectoryName(this.File); }
}
public bool FileExists
{
get { return System.IO.File.Exists(this.File); }
}
public bool DirectoryExists
{
get { return System.IO.Directory.Exists(this.Directory); }
}
public XmlFile(XmlFileId<T> id, string file)
{
this.Id = id;
this.File = file;
ValidateDirectory();
}
public void Delete(string file = null)
{
lock (_fileLock)
{
var toDelete = file ?? this.File;
if (!System.IO.File.Exists(toDelete))
return;
System.IO.File.SetAttributes(toDelete, FileAttributes.Normal);
System.IO.File.Delete(toDelete);
}
}
public void Move(string destination)
{
lock (_fileLock)
{
Copy(destination);
Delete();
}
}
public void Copy(string destination)
{
lock (_fileLock)
{
if (!this.FileExists)
return;
Delete(destination);
System.IO.File.Copy(this.File, destination);
System.IO.File.SetAttributes(destination, FileAttributes.Normal);
}
}
public void ValidateDirectory()
{
lock (_fileLock)
{
if (!this.DirectoryExists)
System.IO.Directory.CreateDirectory(this.Directory);
}
}
public string ReadRawText(bool removeOnFail = false)
{
lock (_fileLock)
{
try
{
var xml = System.IO.File.ReadAllText(this.File);
if (!ContainsXml(xml))
{
if (removeOnFail)
Delete();
return null;
}
return xml;
}
catch
{
if (removeOnFail)
Delete();
}
return null;
}
}
public T ReadInstance(bool removeOnFail = false)
{
lock (_fileLock)
{
try
{
if (this.FileExists)
System.IO.File.SetAttributes(this.File, FileAttributes.Normal);
var item = XmlSerializationHelper.FromDisk<T>(this.File);
return item;
}
catch
{
if (removeOnFail)
Delete();
}
return null;
}
}
public void WriteRawText(string xml)
{
lock (_fileLock)
{
System.IO.File.WriteAllText(this.File, xml);
System.IO.File.SetAttributes(this.File, FileAttributes.Normal);
}
}
public void WriteInstance(T item)
{
lock (_fileLock)
{
this.ValidateDirectory();
XmlSerializationHelper.ToDisk(this.File, item);
System.IO.File.SetAttributes(this.File, FileAttributes.Normal);
}
}
private readonly Regex _testXml = new Regex("<\\w+>[^<]+</\\w+>");
private bool ContainsXml(string data)
{
return !String.IsNullOrWhiteSpace(data) && _testXml.IsMatch(data);
}
}
#endregion
#region XmlFileId
public class XmlFileId<T> where T : class, IXmlSerializable<T>, new()
{
public readonly Guid Id = Guid.NewGuid();
public readonly DateTime Created = DateTime.Now;
public string File { get; private set; }
public Type Type
{
get { return typeof (T); }
}
public XmlFileId(string file)
{
this.File = file;
}
}
#endregion
#region NoShutdown
/// <summary>
/// This class will prevent a background thread from being shutdown
/// </summary>
internal class NoShutdown : IDisposable
{
private readonly Action DisposeAction;
public NoShutdown()
{
System.Threading.Thread current = System.Threading.Thread.CurrentThread;
bool wasBackgroundThread = current.IsBackground;
current.IsBackground = false;
this.DisposeAction = () => current.IsBackground = wasBackgroundThread;
}
public void Dispose()
{
this.DisposeAction();
}
}
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment