Created
June 5, 2013 03:20
-
-
Save epetrie/5711395 to your computer and use it in GitHub Desktop.
hrmmmm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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