using System; using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; using IPA.Utilities; using IPA.Utilities.Async; using System.IO; using System.Runtime.CompilerServices; namespace IPA.Config { internal static class ConfigRuntime { private class DirInfoEqComparer : IEqualityComparer { public bool Equals(DirectoryInfo x, DirectoryInfo y) => x?.FullName == y?.FullName; public int GetHashCode(DirectoryInfo obj) => obj?.GetHashCode() ?? 0; } private static readonly ConcurrentBag configs = new ConcurrentBag(); private static readonly AutoResetEvent configsChangedWatcher = new AutoResetEvent(false); private static readonly ConcurrentDictionary watchers = new ConcurrentDictionary(new DirInfoEqComparer()); private static readonly ConditionalWeakTable> watcherTrackConfigs = new ConditionalWeakTable>(); 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) { 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() { } } }