|
@ -5,20 +5,119 @@ using System.Linq; |
|
|
using System.Text; |
|
|
using System.Text; |
|
|
using System.Threading.Tasks; |
|
|
using System.Threading.Tasks; |
|
|
using System.Threading; |
|
|
using System.Threading; |
|
|
|
|
|
using IPA.Utilities; |
|
|
|
|
|
using IPA.Utilities.Async; |
|
|
|
|
|
using System.IO; |
|
|
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
|
|
|
|
|
namespace IPA.Config |
|
|
namespace IPA.Config |
|
|
{ |
|
|
{ |
|
|
internal static class ConfigRuntime |
|
|
internal static class ConfigRuntime |
|
|
{ |
|
|
{ |
|
|
|
|
|
private class DirInfoEqComparer : IEqualityComparer<DirectoryInfo> |
|
|
|
|
|
{ |
|
|
|
|
|
public bool Equals(DirectoryInfo x, DirectoryInfo y) |
|
|
|
|
|
=> x?.FullName == y?.FullName; |
|
|
|
|
|
|
|
|
|
|
|
public int GetHashCode(DirectoryInfo obj) |
|
|
|
|
|
=> obj?.GetHashCode() ?? 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private static readonly ConcurrentBag<Config> configs = new ConcurrentBag<Config>(); |
|
|
private static readonly ConcurrentBag<Config> configs = new ConcurrentBag<Config>(); |
|
|
private static readonly AutoResetEvent memoryChangedWatcher = new AutoResetEvent(false); |
|
|
|
|
|
|
|
|
private static readonly AutoResetEvent configsChangedWatcher = new AutoResetEvent(false); |
|
|
|
|
|
private static readonly ConcurrentDictionary<DirectoryInfo, FileSystemWatcher> watchers |
|
|
|
|
|
= new ConcurrentDictionary<DirectoryInfo, FileSystemWatcher>(new DirInfoEqComparer()); |
|
|
|
|
|
private static readonly ConditionalWeakTable<FileSystemWatcher, ConcurrentBag<Config>> watcherTrackConfigs |
|
|
|
|
|
= new ConditionalWeakTable<FileSystemWatcher, ConcurrentBag<Config>>(); |
|
|
|
|
|
private static SingleThreadTaskScheduler writeScheduler = null; |
|
|
|
|
|
private static TaskFactory writeFactory = null; |
|
|
|
|
|
private static Thread readThread = null; |
|
|
|
|
|
|
|
|
|
|
|
private static void TryStartRuntime() |
|
|
|
|
|
{ |
|
|
|
|
|
if (writeScheduler == null || !writeScheduler.IsRunning) |
|
|
|
|
|
{ |
|
|
|
|
|
writeFactory = null; |
|
|
|
|
|
writeScheduler = new SingleThreadTaskScheduler(); |
|
|
|
|
|
writeScheduler.Start(); |
|
|
|
|
|
} |
|
|
|
|
|
if (writeFactory == null) |
|
|
|
|
|
writeFactory = new TaskFactory(writeScheduler); |
|
|
|
|
|
if (readThread == null || !readThread.IsAlive) |
|
|
|
|
|
{ |
|
|
|
|
|
readThread = new Thread(ReadThread); |
|
|
|
|
|
readThread.Start(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
public static void RegisterConfig(Config cfg) |
|
|
public static void RegisterConfig(Config cfg) |
|
|
{ |
|
|
{ |
|
|
configs.Add(cfg); |
|
|
|
|
|
|
|
|
|
|
|
// TODO: register file watcher, reset changed watcher
|
|
|
|
|
|
|
|
|
lock (configs) |
|
|
|
|
|
{ // we only lock this segment, so that this only waits on other calls to this
|
|
|
|
|
|
if (configs.ToArray().Contains(cfg)) |
|
|
|
|
|
throw new InvalidOperationException("Config already registered to runtime!"); |
|
|
|
|
|
|
|
|
|
|
|
configs.Add(cfg); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
TryStartRuntime(); |
|
|
|
|
|
|
|
|
|
|
|
AddConfigToWatchers(cfg); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void AddConfigToWatchers(Config config) |
|
|
|
|
|
{ |
|
|
|
|
|
var dir = config.File.Directory; |
|
|
|
|
|
if (!watchers.TryGetValue(dir, out var watcher)) |
|
|
|
|
|
{ // create the watcher
|
|
|
|
|
|
watcher = new FileSystemWatcher(dir.FullName, ""); |
|
|
|
|
|
var newWatcher = watchers.GetOrAdd(dir, watcher); |
|
|
|
|
|
if (watcher != newWatcher) |
|
|
|
|
|
{ // if someone else beat us to adding, delete ours and switch to that new one
|
|
|
|
|
|
watcher.Dispose(); |
|
|
|
|
|
watcher = newWatcher; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
watcher.NotifyFilter = |
|
|
|
|
|
NotifyFilters.FileName |
|
|
|
|
|
| NotifyFilters.LastWrite; |
|
|
|
|
|
|
|
|
|
|
|
watcher.Changed += FileChangedEvent; |
|
|
|
|
|
watcher.Created += FileChangedEvent; |
|
|
|
|
|
watcher.Renamed += FileChangedEvent; |
|
|
|
|
|
watcher.Deleted += FileChangedEvent; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
TryStartRuntime(); |
|
|
|
|
|
|
|
|
|
|
|
watcher.EnableRaisingEvents = false; // disable while we do shit
|
|
|
|
|
|
|
|
|
|
|
|
var bag = watcherTrackConfigs.GetOrCreateValue(watcher); |
|
|
|
|
|
// we don't need to check containment because this function will only be called once per config ever
|
|
|
|
|
|
bag.Add(config); |
|
|
|
|
|
|
|
|
|
|
|
watcher.EnableRaisingEvents = true; |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void FileChangedEvent(object sender, FileSystemEventArgs e) |
|
|
|
|
|
{ |
|
|
|
|
|
var watcher = sender as FileSystemWatcher; |
|
|
|
|
|
if (!watcherTrackConfigs.TryGetValue(watcher, out var bag)) return; |
|
|
|
|
|
|
|
|
|
|
|
var config = bag.FirstOrDefault(c => c.File.FullName == e.FullPath); |
|
|
|
|
|
if (config != null) |
|
|
|
|
|
writeFactory.StartNew(() => WriteTask(config).Wait()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static async Task WriteTask(Config config) |
|
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void ReadThread() |
|
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |