PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/ABB.SrcML/AbstractFileMonitor.cs

https://github.com/nkcsgexi/SrcML.NET
C# | 485 lines | 284 code | 59 blank | 142 comment | 33 complexity | eb426fce5e11e1da8281e9e9ce61282b MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Concurrent;
  4. using System.Collections.ObjectModel;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Diagnostics;
  10. using log4net;
  11. using ABB.SrcML.Utilities;
  12. namespace ABB.SrcML {
  13. /// <summary>
  14. /// <para>Represents an abstract file monitor. This class contains archives for storing various file types To start using it, you first instantiate
  15. /// it with a <see cref="AbstractArchive">default archive</see>. You then call <see cref="RegisterArchive"/> for each alternative
  16. /// archive. This class automatically routes files to the appropriate archive.</para>
  17. /// <para>You begin monitoring by calling <see cref="StartMonitoring"/>. <see cref="StartMonitoring"/> should subscribe to any events and then call
  18. /// functions to respond to those events:</para>
  19. /// <list type="bullet">
  20. /// <item><description><see cref="AddFile(string)"/></description></item>
  21. /// <item><description><see cref="DeleteFile(string)"/></description></item>
  22. /// <item><description></description><see cref="UpdateFile(string)"/></item>
  23. /// <item><description><see cref="RenameFile(string,string)"/></description></item>
  24. /// </list>
  25. /// <para>When the archive is done processing the file, it raises its own <see cref="AbstractArchive.FileChanged">event</see></para>
  26. /// </summary>
  27. public abstract class AbstractFileMonitor : IFileMonitor {
  28. private bool monitorIsReady;
  29. private int numberOfWorkingArchives;
  30. private IArchive defaultArchive;
  31. private HashSet<IArchive> registeredArchives;
  32. private Dictionary<string, IArchive> archiveMap;
  33. /// <summary>
  34. /// If true, this monitor will use the Async methods on all of its <see cref="AbstractArchive"/> objects. By default it is false.
  35. /// </summary>
  36. public bool UseAsyncMethods { get; set; }
  37. /// <summary>
  38. /// Creates a new AbstractFileMonitor with the default archive and a collection of non-default archives that should be registered
  39. /// </summary>
  40. /// <param name="baseDirectory">The folder where this monitor stores it archives</param>
  41. /// <param name="defaultArchive">The default archive</param>
  42. /// <param name="otherArchives">A list of other archives that should be registered via <see cref="RegisterArchive(IArchive)"/></param>
  43. protected AbstractFileMonitor(string baseDirectory, IArchive defaultArchive, params IArchive[] otherArchives) {
  44. this.MonitorStoragePath = baseDirectory;
  45. this.registeredArchives = new HashSet<IArchive>();
  46. this.archiveMap = new Dictionary<string, IArchive>(StringComparer.InvariantCultureIgnoreCase);
  47. this.numberOfWorkingArchives = 0;
  48. this.monitorIsReady = true;
  49. this.UseAsyncMethods = false;
  50. RegisterArchive(defaultArchive, true);
  51. foreach(var archive in otherArchives) {
  52. this.RegisterArchive(archive, false);
  53. }
  54. }
  55. /// <summary>
  56. /// Indicates that the monitor has finished updating all changed files.
  57. /// </summary>
  58. public bool IsReady {
  59. get { return this.monitorIsReady; }
  60. protected set {
  61. if(value != monitorIsReady) {
  62. monitorIsReady = value;
  63. OnIsReadyChanged(new IsReadyChangedEventArgs(monitorIsReady));
  64. }
  65. }
  66. }
  67. /// <summary>
  68. /// The folder where all of the archives can store their data. <see cref="AbstractArchive"/> objects can use this as their root folder
  69. /// </summary>
  70. public string MonitorStoragePath { get; protected set; }
  71. /// <summary>
  72. /// Event fires when any of the archives raises their <see cref="AbstractArchive.FileChanged"/>.
  73. /// </summary>
  74. public event EventHandler<FileEventRaisedArgs> FileChanged;
  75. /// <summary>
  76. /// Event fires when the <see cref="IsReady"/> property changes
  77. /// </summary>
  78. public event EventHandler<IsReadyChangedEventArgs> IsReadyChanged;
  79. /// <summary>
  80. /// Event fires when <see cref="StopMonitoring()"/> is completed
  81. /// </summary>
  82. public event EventHandler MonitoringStopped;
  83. /// <summary>
  84. /// Gets the list of source files from the object being monitored
  85. /// </summary>
  86. /// <returns>An enumerable of files to be monitored</returns>
  87. public abstract Collection<string> GetFilesFromSource();
  88. /// <summary>
  89. /// Number of the elements in the returned collection from GetFilesFromSource()
  90. /// </summary>
  91. public int NumberOfAllMonitoredFiles { get; protected set; }
  92. /// <summary>
  93. /// Gets the list of files already present in this archive
  94. /// </summary>
  95. /// <returns>An enumerable of files present in all of the archives</returns>
  96. public virtual IEnumerable<string> GetArchivedFiles() {
  97. var archivedFiles = from archive in registeredArchives
  98. from filePath in archive.GetFiles()
  99. select filePath;
  100. return archivedFiles;
  101. }
  102. /// <summary>
  103. /// Registers an archive in the file monitor. All file changes will be automatically routed to the appropriate archive
  104. /// based on file extension (via <see cref="AbstractArchive.SupportedExtensions"/>
  105. /// </summary>
  106. /// <param name="archive">the archive to add.</param>
  107. /// <param name="isDefault">whether or not to use this archive as the default archive</param>
  108. public void RegisterArchive(IArchive archive, bool isDefault) {
  109. this.registeredArchives.Add(archive);
  110. archive.FileChanged += RespondToArchiveFileEvent;
  111. archive.IsReadyChanged += archive_IsReadyChanged;
  112. if(isDefault) {
  113. this.defaultArchive = archive;
  114. } else {
  115. foreach(var extension in archive.SupportedExtensions) {
  116. if(archiveMap.ContainsKey(extension)) {
  117. SrcMLFileLogger.DefaultLogger.WarnFormat("AbstractFileMonitor.RegisterNonDefaultArchive() - Archive already registered for extension {0}, will be replaced with the new archive.", extension);
  118. }
  119. archiveMap[extension] = archive;
  120. }
  121. }
  122. }
  123. void archive_IsReadyChanged(object sender, IsReadyChangedEventArgs e) {
  124. if(e.ReadyState) {
  125. numberOfWorkingArchives--;
  126. } else {
  127. numberOfWorkingArchives++;
  128. }
  129. if(0 == numberOfWorkingArchives) {
  130. IsReady = true;
  131. } else {
  132. IsReady = false;
  133. }
  134. }
  135. /// <summary>
  136. /// Raises the <see cref="FileChanged"/> event.
  137. /// </summary>
  138. /// <param name="sender">The caller</param>
  139. /// <param name="e">The event arguments</param>
  140. protected virtual void RespondToArchiveFileEvent(object sender, FileEventRaisedArgs e) {
  141. //SrcMLFileLogger.DefaultLogger.Info("AbstractFileMonitor.RespondToArchiveFileEvent() type = " + e.EventType + ", file = " + e.FilePath + ", oldfile = " + e.OldFilePath + ", HasSrcML = " + e.HasSrcML);
  142. FileInfo fi = new FileInfo(e.FilePath);
  143. OnFileChanged(e);
  144. }
  145. /// <summary>
  146. /// Starts monitoring
  147. /// </summary>
  148. public abstract void StartMonitoring();
  149. /// <summary>
  150. /// Stops monitoring. Also calls <see cref="Dispose()"/>
  151. /// </summary>
  152. public virtual void StopMonitoring() {
  153. Dispose();
  154. OnMonitoringStopped(new EventArgs());
  155. }
  156. /// <summary>
  157. /// Processes a file addition by adding the file to the appropriate archive
  158. /// </summary>
  159. /// <param name="filePath">the file to add</param>
  160. public void AddFile(string filePath) {
  161. if(UseAsyncMethods) {
  162. this.GetArchiveForFile(filePath).AddOrUpdateFileAsync(filePath);
  163. } else {
  164. this.GetArchiveForFile(filePath).AddOrUpdateFile(filePath);
  165. }
  166. }
  167. /// <summary>
  168. /// Processes a file deletion by deleting the file from the appropriate archive
  169. /// </summary>
  170. /// <param name="filePath">The file to delete</param>
  171. public void DeleteFile(string filePath) {
  172. if(UseAsyncMethods) {
  173. this.GetArchiveForFile(filePath).DeleteFileAsync(filePath);
  174. } else {
  175. this.GetArchiveForFile(filePath).DeleteFile(filePath);
  176. }
  177. }
  178. /// <summary>
  179. /// Processes a file update by updating the file in the appropriate archive
  180. /// </summary>
  181. /// <param name="filePath">the file to update</param>
  182. public void UpdateFile(string filePath) {
  183. if(UseAsyncMethods) {
  184. this.GetArchiveForFile(filePath).AddOrUpdateFileAsync(filePath);
  185. } else {
  186. this.GetArchiveForFile(filePath).AddOrUpdateFile(filePath);
  187. }
  188. }
  189. /// <summary>
  190. /// Processes a file rename. If the old and new path are both in the same archive,
  191. /// a <see cref="AbstractArchive.RenameFile(string,string)"/> is called on the appropriate archive.
  192. /// If they are in different archives, the <see cref="AbstractArchive.DeleteFile(string)"/> is called on <paramref name="oldFilePath"/>
  193. /// and <see cref="AbstractArchive.AddOrUpdateFile(string)"/> is called on <paramref name="newFilePath"/>
  194. /// </summary>
  195. /// <param name="oldFilePath">the old file name</param>
  196. /// <param name="newFilePath">the new file name</param>
  197. public void RenameFile(string oldFilePath, string newFilePath) {
  198. var oldArchive = GetArchiveForFile(oldFilePath);
  199. var newArchive = GetArchiveForFile(newFilePath);
  200. if(!oldArchive.Equals(newArchive)) {
  201. if(UseAsyncMethods) {
  202. oldArchive.DeleteFileAsync(oldFilePath);
  203. newArchive.AddOrUpdateFileAsync(newFilePath);
  204. } else {
  205. oldArchive.DeleteFile(oldFilePath);
  206. newArchive.AddOrUpdateFile(newFilePath);
  207. }
  208. } else {
  209. if(UseAsyncMethods) {
  210. oldArchive.RenameFileAsync(oldFilePath, newFilePath);
  211. } else {
  212. oldArchive.RenameFile(oldFilePath, newFilePath);
  213. }
  214. }
  215. }
  216. /// <summary>
  217. /// Synchronizes the archives with the object being monitored. Startup adds or updates outdated archive files and deletes archive files that are
  218. /// no longer present on disk.
  219. /// </summary>
  220. public virtual void Startup() {
  221. SrcMLFileLogger.DefaultLogger.Info("AbstractFileMonitor.Startup()");
  222. // make a hashset of all the files to monitor
  223. var monitoredFiles = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
  224. foreach(var filePath in GetFilesFromSource()) {
  225. monitoredFiles.Add(filePath);
  226. }
  227. // find all the files in the hashset that require updating
  228. var outdatedFiles = from filePath in monitoredFiles
  229. where GetArchiveForFile(filePath).IsOutdated(filePath)
  230. select filePath;
  231. // update the outdated files
  232. foreach(var filePath in outdatedFiles) {
  233. try {
  234. AddFile(filePath);
  235. } catch(Exception) {
  236. // TODO log exception
  237. }
  238. }
  239. // find all the files to delete (files in the archive that are not in
  240. // the list of files to monitor
  241. var filesToDelete = from archive in registeredArchives
  242. from filePath in archive.GetFiles()
  243. where !monitoredFiles.Contains(filePath)
  244. select new {
  245. Archive = archive,
  246. FilePath = filePath,
  247. };
  248. // delete the extra files from the archive
  249. foreach(var data in filesToDelete) {
  250. try {
  251. data.Archive.DeleteFile(data.FilePath);
  252. } catch(Exception) {
  253. // TODO log exception
  254. }
  255. }
  256. }
  257. /// <summary> For Sando, add degree of parallelism
  258. /// Synchronizes the archives with the object being monitored. Startup adds or updates outdated archive files and deletes archive files that are
  259. /// no longer present on disk.
  260. /// </summary>
  261. public virtual void Startup_Concurrent(int degreeOfParallelism) {
  262. SrcMLFileLogger.DefaultLogger.Info("AbstractFileMonitor.Startup()");
  263. // make a hashset of all the files to monitor
  264. var monitoredFiles = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
  265. foreach(var filePath in GetFilesFromSource()) {
  266. monitoredFiles.Add(filePath);
  267. }
  268. // find all the files in the hashset that require updating
  269. var outdatedFiles = from filePath in monitoredFiles
  270. where GetArchiveForFile(filePath).IsOutdated(filePath)
  271. select filePath;
  272. Stopwatch sw = new Stopwatch();
  273. sw.Start();
  274. ConcurrentQueue<string> missedFiles = new ConcurrentQueue<string>();
  275. ParallelOptions option = new ParallelOptions();
  276. //number of threads, for Sando application, 2 is the best trade-off
  277. option.MaxDegreeOfParallelism = degreeOfParallelism;
  278. Parallel.ForEach(outdatedFiles, option, currentFile => {
  279. string filePath = currentFile;
  280. try {
  281. AddFile(filePath);
  282. } catch(Exception e) {
  283. //Trace.WriteLine(fileName + " " + e.Message);
  284. missedFiles.Enqueue(filePath);
  285. }
  286. });
  287. Task.WaitAll();
  288. //As a remedial action, regenerate the file missed in the last step
  289. if(missedFiles.Count > 0) {
  290. foreach(string fileName in missedFiles)
  291. try {
  292. AddFile(fileName);
  293. } catch(Exception e) {
  294. //Log exception
  295. }
  296. }
  297. sw.Stop();
  298. Console.WriteLine("Concurrently generating SrcML files: " + sw.Elapsed);
  299. // find all the files to delete (files in the archive that are not in
  300. // the list of files to monitor
  301. var filesToDelete = from archive in registeredArchives
  302. from filePath in archive.GetFiles()
  303. where !monitoredFiles.Contains(filePath)
  304. select new {
  305. Archive = archive,
  306. FilePath = filePath,
  307. };
  308. // delete the extra files from the archive
  309. foreach(var data in filesToDelete) {
  310. try {
  311. data.Archive.DeleteFile(data.FilePath);
  312. } catch(Exception) {
  313. // TODO log exception
  314. }
  315. }
  316. }
  317. public virtual void Startup_Concurrent() {
  318. SrcMLFileLogger.DefaultLogger.Info("AbstractFileMonitor.Startup()");
  319. // make a hashset of all the files to monitor
  320. var monitoredFiles = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
  321. foreach(var filePath in GetFilesFromSource()) {
  322. monitoredFiles.Add(filePath);
  323. }
  324. // find all the files in the hashset that require updating
  325. var outdatedFiles = from filePath in monitoredFiles
  326. where GetArchiveForFile(filePath).IsOutdated(filePath)
  327. select filePath;
  328. Stopwatch sw = new Stopwatch();
  329. sw.Start();
  330. ConcurrentQueue<string> missedFiles = new ConcurrentQueue<string>();
  331. Parallel.ForEach(outdatedFiles, currentFile => {
  332. string filePath = currentFile;
  333. try {
  334. AddFile(filePath);
  335. } catch(Exception e) {
  336. //Trace.WriteLine(fileName + " " + e.Message);
  337. missedFiles.Enqueue(filePath);
  338. }
  339. });
  340. Task.WaitAll();
  341. //As a remedial action, regenerate the file missed in the last step
  342. if(missedFiles.Count > 0) {
  343. foreach(string fileName in missedFiles)
  344. try {
  345. AddFile(fileName);
  346. } catch(Exception e) {
  347. //Log exception
  348. }
  349. }
  350. sw.Stop();
  351. Console.WriteLine("Concurrently generating SrcML files: " + sw.Elapsed);
  352. // find all the files to delete (files in the archive that are not in
  353. // the list of files to monitor
  354. var filesToDelete = from archive in registeredArchives
  355. from filePath in archive.GetFiles()
  356. where !monitoredFiles.Contains(filePath)
  357. select new {
  358. Archive = archive,
  359. FilePath = filePath,
  360. };
  361. // delete the extra files from the archive
  362. foreach(var data in filesToDelete) {
  363. try {
  364. data.Archive.DeleteFile(data.FilePath);
  365. } catch(Exception) {
  366. // TODO log exception
  367. }
  368. }
  369. }
  370. /// <summary>
  371. /// disposes of all of the archives and stops the events
  372. /// </summary>
  373. public void Dispose() {
  374. SrcMLFileLogger.DefaultLogger.Info("AbstractFileMonitor.Dispose()");
  375. IsReadyChanged = null;
  376. FileChanged = null;
  377. foreach(var archive in registeredArchives) {
  378. archive.Dispose();
  379. }
  380. }
  381. /// <summary>
  382. /// event handler for <see cref="FileChanged"/>
  383. /// </summary>
  384. /// <param name="e">event arguments</param>
  385. protected virtual void OnFileChanged(FileEventRaisedArgs e) {
  386. EventHandler<FileEventRaisedArgs> handler = FileChanged;
  387. if(handler != null) {
  388. handler(this, e);
  389. }
  390. }
  391. /// <summary>
  392. /// event handler for <see cref="IsReadyChanged"/>
  393. /// </summary>
  394. /// <param name="e">event arguments</param>
  395. protected virtual void OnIsReadyChanged(IsReadyChangedEventArgs e) {
  396. EventHandler<IsReadyChangedEventArgs> handler = IsReadyChanged;
  397. if(handler != null) {
  398. handler(this, e);
  399. }
  400. }
  401. /// <summary>
  402. /// event handler for <see cref="MonitoringStopped"/>
  403. /// </summary>
  404. /// <param name="e">null event</param>
  405. protected virtual void OnMonitoringStopped(EventArgs e) {
  406. EventHandler handler = MonitoringStopped;
  407. if(handler != null) {
  408. handler(this, e);
  409. }
  410. }
  411. /// <summary>
  412. /// Gets the appropriate archive for string this file name (based on <see cref="Path.GetExtension(string)"/>
  413. /// </summary>
  414. /// <param name="fileName">The file name</param>
  415. /// <returns>The archive that should contain this file name</returns>
  416. private IArchive GetArchiveForFile(string fileName) {
  417. if(null == fileName) throw new ArgumentNullException("fileName");
  418. IArchive selectedArchive = null;
  419. var extension = Path.GetExtension(fileName);
  420. if(!this.archiveMap.TryGetValue(extension, out selectedArchive)) {
  421. selectedArchive = defaultArchive;
  422. }
  423. return selectedArchive;
  424. }
  425. }
  426. }