You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
4.3 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Concurrent;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using System.Threading;
  8. using IPA.Utilities;
  9. using IPA.Utilities.Async;
  10. using System.IO;
  11. using System.Runtime.CompilerServices;
  12. namespace IPA.Config
  13. {
  14. internal static class ConfigRuntime
  15. {
  16. private class DirInfoEqComparer : IEqualityComparer<DirectoryInfo>
  17. {
  18. public bool Equals(DirectoryInfo x, DirectoryInfo y)
  19. => x?.FullName == y?.FullName;
  20. public int GetHashCode(DirectoryInfo obj)
  21. => obj?.GetHashCode() ?? 0;
  22. }
  23. private static readonly ConcurrentBag<Config> configs = new ConcurrentBag<Config>();
  24. private static readonly AutoResetEvent configsChangedWatcher = new AutoResetEvent(false);
  25. private static readonly ConcurrentDictionary<DirectoryInfo, FileSystemWatcher> watchers
  26. = new ConcurrentDictionary<DirectoryInfo, FileSystemWatcher>(new DirInfoEqComparer());
  27. private static readonly ConditionalWeakTable<FileSystemWatcher, ConcurrentBag<Config>> watcherTrackConfigs
  28. = new ConditionalWeakTable<FileSystemWatcher, ConcurrentBag<Config>>();
  29. private static SingleThreadTaskScheduler writeScheduler = null;
  30. private static TaskFactory writeFactory = null;
  31. private static Thread readThread = null;
  32. private static void TryStartRuntime()
  33. {
  34. if (writeScheduler == null || !writeScheduler.IsRunning)
  35. {
  36. writeFactory = null;
  37. writeScheduler = new SingleThreadTaskScheduler();
  38. writeScheduler.Start();
  39. }
  40. if (writeFactory == null)
  41. writeFactory = new TaskFactory(writeScheduler);
  42. if (readThread == null || !readThread.IsAlive)
  43. {
  44. readThread = new Thread(ReadThread);
  45. readThread.Start();
  46. }
  47. }
  48. public static void RegisterConfig(Config cfg)
  49. {
  50. lock (configs)
  51. { // we only lock this segment, so that this only waits on other calls to this
  52. if (configs.ToArray().Contains(cfg))
  53. throw new InvalidOperationException("Config already registered to runtime!");
  54. configs.Add(cfg);
  55. }
  56. TryStartRuntime();
  57. AddConfigToWatchers(cfg);
  58. }
  59. private static void AddConfigToWatchers(Config config)
  60. {
  61. var dir = config.File.Directory;
  62. if (!watchers.TryGetValue(dir, out var watcher))
  63. { // create the watcher
  64. watcher = new FileSystemWatcher(dir.FullName, "");
  65. var newWatcher = watchers.GetOrAdd(dir, watcher);
  66. if (watcher != newWatcher)
  67. { // if someone else beat us to adding, delete ours and switch to that new one
  68. watcher.Dispose();
  69. watcher = newWatcher;
  70. }
  71. watcher.NotifyFilter =
  72. NotifyFilters.FileName
  73. | NotifyFilters.LastWrite;
  74. watcher.Changed += FileChangedEvent;
  75. watcher.Created += FileChangedEvent;
  76. watcher.Renamed += FileChangedEvent;
  77. watcher.Deleted += FileChangedEvent;
  78. }
  79. TryStartRuntime();
  80. watcher.EnableRaisingEvents = false; // disable while we do shit
  81. var bag = watcherTrackConfigs.GetOrCreateValue(watcher);
  82. // we don't need to check containment because this function will only be called once per config ever
  83. bag.Add(config);
  84. watcher.EnableRaisingEvents = true;
  85. }
  86. private static void FileChangedEvent(object sender, FileSystemEventArgs e)
  87. {
  88. var watcher = sender as FileSystemWatcher;
  89. if (!watcherTrackConfigs.TryGetValue(watcher, out var bag)) return;
  90. var config = bag.FirstOrDefault(c => c.File.FullName == e.FullPath);
  91. if (config != null)
  92. writeFactory.StartNew(() => WriteTask(config).Wait());
  93. }
  94. private static async Task WriteTask(Config config)
  95. {
  96. }
  97. private static void ReadThread()
  98. {
  99. }
  100. }
  101. }