BeefConfig.bf 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. using IDE.Util;
  2. using System;
  3. using System.Collections;
  4. using System.IO;
  5. using Beefy.utils;
  6. using System.Threading;
  7. using System.Diagnostics;
  8. namespace IDE
  9. {
  10. class BeefConfig
  11. {
  12. public class RegistryEntry
  13. {
  14. public String mProjName ~ delete _;
  15. public SemVer mVersion ~ delete _;
  16. public VerSpec mLocation ~ _.Dispose();
  17. public ConfigFile mConfigFile;
  18. public bool mParsedConfig;
  19. }
  20. public class Registry
  21. {
  22. public List<RegistryEntry> mEntries = new List<RegistryEntry>() ~ DeleteContainerAndItems!(_);
  23. public Monitor mMonitor = new .() ~ delete _;
  24. public WaitEvent mEvent = new .() ~ delete _;
  25. public Thread mThread ~ delete _;
  26. public ~this()
  27. {
  28. mEvent.WaitFor();
  29. }
  30. public void ParseConfig(RegistryEntry entry)
  31. {
  32. entry.mParsedConfig = true;
  33. if (entry.mLocation case .Path(let path))
  34. {
  35. String configPath = scope String()..AppendF("{}/BeefProj.toml", path);
  36. StructuredData sd = scope .();
  37. if (sd.Load(configPath) case .Ok)
  38. {
  39. using (sd.Open("Project"))
  40. {
  41. var projName = scope String();
  42. sd.GetString("Name", projName);
  43. if (!projName.IsEmpty)
  44. {
  45. using (mMonitor.Enter())
  46. entry.mProjName.Set(projName);
  47. }
  48. }
  49. }
  50. }
  51. }
  52. public void WaitFor()
  53. {
  54. mEvent.WaitFor();
  55. }
  56. void Resolve()
  57. {
  58. // NOTE: We allow a race condition where ParseConfig can possibly occur on multiple threads
  59. // at the same time
  60. for (var entry in mEntries)
  61. {
  62. if (!entry.mParsedConfig)
  63. ParseConfig(entry);
  64. }
  65. mEvent.Set(true);
  66. }
  67. public void StartResolve()
  68. {
  69. delete mThread;
  70. mThread = new Thread(new => Resolve);
  71. mThread.Start(false);
  72. }
  73. }
  74. public class LibDirectory
  75. {
  76. public String mPath ~ delete _;
  77. public ConfigFile mConfigFile;
  78. }
  79. public class ConfigFile
  80. {
  81. public String mFilePath ~ delete _;
  82. public String mConfigDir ~ delete _;
  83. }
  84. List<ConfigFile> mConfigFiles = new List<ConfigFile>() ~ DeleteContainerAndItems!(_);
  85. public Registry mRegistry ~ delete _;
  86. List<String> mConfigPathQueue = new List<String>() ~ DeleteContainerAndItems!(_);
  87. List<LibDirectory> mLibDirectories = new List<LibDirectory>() ~ DeleteContainerAndItems!(_);
  88. List<FileSystemWatcher> mWatchers = new .() ~ DeleteContainerAndItems!(_);
  89. public bool mLibsChanged;
  90. void LibsChanged()
  91. {
  92. mLibsChanged = true;
  93. }
  94. Result<void> Load(StringView path)
  95. {
  96. let data = scope StructuredData();
  97. if (data.Load(path) case .Err)
  98. return .Err;
  99. let configFile = new ConfigFile();
  100. configFile.mFilePath = new String(path);
  101. configFile.mConfigDir = new String();
  102. Path.GetDirectoryPath(configFile.mFilePath, configFile.mConfigDir).IgnoreError();
  103. for (let projName in data.Enumerate("Registry"))
  104. {
  105. RegistryEntry regEntry = new RegistryEntry();
  106. regEntry.mProjName = new String(projName);
  107. mRegistry.mEntries.Add(regEntry);
  108. regEntry.mConfigFile = configFile;
  109. var verString = scope String();
  110. data.GetString("Version", verString);
  111. regEntry.mVersion = new SemVer();
  112. regEntry.mVersion.Parse(verString).IgnoreError();
  113. using (data.Open("Location"))
  114. regEntry.mLocation.Parse(data).IgnoreError();
  115. }
  116. void AddFromLibraryPath(String absPath)
  117. {
  118. for (var entry in Directory.EnumerateDirectories(absPath))
  119. {
  120. String projName = scope .();
  121. entry.GetFileName(projName);
  122. String filePath = scope .();
  123. entry.GetFilePath(filePath);
  124. String projFilePath = scope .();
  125. projFilePath.Concat(filePath, "/BeefProj.toml");
  126. if (File.Exists(projFilePath))
  127. {
  128. RegistryEntry regEntry = new RegistryEntry();
  129. regEntry.mProjName = new String(projName);
  130. mRegistry.mEntries.Add(regEntry);
  131. regEntry.mConfigFile = configFile;
  132. regEntry.mVersion = new SemVer();
  133. regEntry.mVersion.Parse("0.0.0");
  134. regEntry.mLocation = .Path(new String(filePath));
  135. }
  136. else
  137. {
  138. AddFromLibraryPath(filePath);
  139. }
  140. }
  141. }
  142. for (data.Enumerate("UnversionedLibDirs"))
  143. {
  144. String dirStr = scope .();
  145. data.GetCurString(dirStr);
  146. if (!dirStr.IsWhiteSpace)
  147. {
  148. LibDirectory libDir = new .();
  149. libDir.mPath = new String(dirStr);
  150. libDir.mConfigFile = configFile;
  151. mLibDirectories.Add(libDir);
  152. String absPath = scope .();
  153. Path.GetAbsolutePath(libDir.mPath, configFile.mConfigDir, absPath);
  154. AddFromLibraryPath(absPath);
  155. absPath.Append(Path.DirectorySeparatorChar);
  156. #if !CLI
  157. FileSystemWatcher watcher = new FileSystemWatcher(absPath);
  158. watcher.OnChanged.Add(new (fileName) => LibsChanged());
  159. watcher.OnCreated.Add(new (fileName) => LibsChanged());
  160. watcher.OnDeleted.Add(new (fileName) => LibsChanged());
  161. watcher.OnRenamed.Add(new (newName, oldName) => LibsChanged());
  162. watcher.OnError.Add(new () => LibsChanged());
  163. watcher.StartRaisingEvents().IgnoreError();
  164. mWatchers.Add(watcher);
  165. #endif
  166. }
  167. }
  168. mConfigFiles.Add(configFile);
  169. return .Ok;
  170. }
  171. public void Refresh()
  172. {
  173. Load().IgnoreError();
  174. }
  175. public void QueuePaths(StringView topLevelDir)
  176. {
  177. let dir = scope String(topLevelDir);
  178. if (!dir.EndsWith(IDEUtils.cNativeSlash))
  179. dir.Append(IDEUtils.cNativeSlash);
  180. while (true)
  181. {
  182. let path = scope String(dir);
  183. path.Append("BeefConfig.toml");
  184. if (File.Exists(path))
  185. {
  186. if (mConfigPathQueue.Contains(path))
  187. break; // We have this and everything under it already
  188. mConfigPathQueue.Add(new String(path));
  189. }
  190. // We had logic to check parent directories, but this seems unsound. Revisit this decision when we have
  191. // better usage cases in mind.
  192. break;
  193. /*if (dir.Length < 2)
  194. break;
  195. int slashPos = dir.LastIndexOf(IDEUtils.cNativeSlash, dir.Length - 2);
  196. if (slashPos == -1)
  197. break;
  198. dir.RemoveToEnd(slashPos + 1);*/
  199. }
  200. }
  201. public Result<void> Load()
  202. {
  203. mLibsChanged = false;
  204. delete mRegistry;
  205. mRegistry = new Registry();
  206. ClearAndDeleteItems(mConfigFiles);
  207. ClearAndDeleteItems(mWatchers);
  208. for (int i = mConfigPathQueue.Count - 1; i >= 0; i--)
  209. {
  210. let path = mConfigPathQueue[i];
  211. Try!(Load(path));
  212. }
  213. mRegistry.StartResolve();
  214. return .Ok;
  215. }
  216. }
  217. }