QMonReceiverHandler.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. using System.IO.Abstractions;
  2. using System.Text.Json;
  3. using Quadarax.Foundation.Core.Collections;
  4. using Quadarax.Foundation.Core.Json.Extensions;
  5. using Quadarax.Foundation.Core.Logging;
  6. using Quadarax.Foundation.Core.QMonitor.Interfaces;
  7. using Quadarax.Foundation.Core.Object;
  8. using Quadarax.Foundation.Core.Thread;
  9. using Quadarax.Foundation.Core.Value.Extensions;
  10. namespace Quadarax.Foundation.Core.QMonitor
  11. {
  12. public class QMonReceiverHandler : DisposableObject, IReceiverHandler
  13. {
  14. #region *** Properties ***
  15. public string InstanceIdentifier { get; }
  16. public MonItemGeneral Descriptor { get; }
  17. public ReceiverHandlerStateEnum State
  18. {
  19. get => _state;
  20. private set
  21. {
  22. _state = value;
  23. ReceiverHandlerStateChanged?.Invoke(this, _state);
  24. }
  25. }
  26. public bool IsLiveBroadcast {get; private set; }
  27. public DateTime? Timestamp => _currentTimestampFtUtc == null ? null : DateTime.FromFileTimeUtc(_currentTimestampFtUtc.Value);
  28. public TimeSpan RefreshInterval { get => _refreshInterval; set => SetRefreshInterval(value); }
  29. public MonData[]? CurrentData { get; private set; }
  30. public event ReceiverHandlerStateChanged? ReceiverHandlerStateChanged;
  31. public event ReceiverHandlerDataChanged? ReceiverHandlerDataChanged;
  32. #endregion
  33. #region *** Private fields ***
  34. private ReceiverHandlerStateEnum _state;
  35. private TimeSpan _refreshInterval;
  36. private IFileSystem _fileSystem;
  37. private string _cachePath;
  38. private ILog? _log;
  39. private IFileSystemWatcher? _watcher;
  40. private LoopWorker _refreshTimer;
  41. private readonly LockedList<DataCacheItem> _dataCache = new();
  42. private ILogger? _logger;
  43. private long? _currentTimestampFtUtc;
  44. #endregion
  45. #region *** Constructor ***
  46. internal QMonReceiverHandler(IFileSystem fileSystem, string cachePath, string instanceIdentifier, MonItemGeneral descriptor,TimeSpan refreshInterval, ILogger? logger)
  47. {
  48. _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
  49. if (string.IsNullOrEmpty(cachePath)) throw new ArgumentNullException(nameof(cachePath));
  50. _cachePath = cachePath;
  51. _log = logger?.GetLogger(nameof(QMonReceiverHandler));
  52. _logger = logger;
  53. if(string.IsNullOrEmpty(instanceIdentifier)) throw new ArgumentNullException(nameof(instanceIdentifier));
  54. InstanceIdentifier = instanceIdentifier;
  55. Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
  56. RefreshFiles();
  57. _watcher = _fileSystem.FileSystemWatcher.New(_fileSystem.Path.Combine(_cachePath, InstanceIdentifier),
  58. GetFileSearchMask());
  59. _watcher.Created += Watcher_Created;
  60. _watcher.Deleted += Watcher_Deleted;
  61. RefreshInterval = refreshInterval;
  62. _currentTimestampFtUtc = GetLastAvailableDataTimestamp()?.ToFileTimeUtc();
  63. _state = ReceiverHandlerStateEnum.Stop;
  64. }
  65. #endregion
  66. #region *** Public Operations ***
  67. public DateTime? GetFirstAvailableDataTimestamp()
  68. {
  69. if (_dataCache.Count == 0) return null;
  70. var first = _dataCache.Select(x => x.TimestampFtUtc).MinBy(x => x);
  71. return DateTime.FromFileTimeUtc(first);
  72. }
  73. public DateTime? GetLastAvailableDataTimestamp()
  74. {
  75. if (_dataCache.Count == 0) return null;
  76. var last = _dataCache.Select(x => x.TimestampFtUtc).MinBy(x => x);
  77. return DateTime.FromFileTimeUtc(last);
  78. }
  79. public void Play(bool isLive)
  80. {
  81. State = ReceiverHandlerStateEnum.Play;
  82. if (isLive)
  83. {
  84. if (_dataCache.Count == 0) return;
  85. SetAsCurrent(_dataCache.Last());
  86. }
  87. }
  88. public void RewindToPreviousChange()
  89. {
  90. RewindToChange(false);
  91. }
  92. public void RewindToNextChange()
  93. {
  94. RewindToChange(true);
  95. }
  96. public void Record()
  97. {
  98. throw new NotImplementedException();
  99. }
  100. public void Rewind(TimeSpan? duration = null, bool isBackward = false)
  101. {
  102. State = ReceiverHandlerStateEnum.Rewind;
  103. if (_dataCache.Count == 0 || _dataCache.Count == 1)
  104. {
  105. State = ReceiverHandlerStateEnum.Stop;
  106. return;
  107. }
  108. if (duration == null || duration == TimeSpan.Zero)
  109. {
  110. if (isBackward)
  111. SetAsCurrent(_dataCache.First());
  112. else
  113. SetAsCurrent(_dataCache.Last());
  114. State = ReceiverHandlerStateEnum.Stop;
  115. return;
  116. }
  117. var findTimestamp = (Timestamp ?? DateTime.Now);
  118. var findTimestampFtUtc = (isBackward ? findTimestamp.Subtract(duration.Value) : findTimestamp.Add(duration.Value)).ToFileTimeUtc();
  119. int newIndex = -1;
  120. if (isBackward)
  121. {
  122. for (var i = 0; i < _dataCache.Count; i++)
  123. {
  124. if (_dataCache[i].TimestampFtUtc >= findTimestampFtUtc)
  125. {
  126. newIndex = i;
  127. break;
  128. }
  129. }
  130. }
  131. else
  132. {
  133. for (var i = _dataCache.Count - 1; i >= 0; i--)
  134. {
  135. if (_dataCache[i].TimestampFtUtc <= findTimestampFtUtc)
  136. {
  137. newIndex = i;
  138. break;
  139. }
  140. }
  141. }
  142. if (newIndex >= 0)
  143. {
  144. SetAsCurrent(_dataCache[newIndex]);
  145. }
  146. State = ReceiverHandlerStateEnum.Stop;
  147. }
  148. public void Stop()
  149. {
  150. State = ReceiverHandlerStateEnum.Stop;
  151. }
  152. #endregion
  153. #region *** Private Operations ***
  154. public int FindNextChangeIndex(int currentIndex, bool isForward)
  155. {
  156. if (currentIndex < 0 || currentIndex >= _dataCache.Count) return -1;
  157. var originalIndex = currentIndex;
  158. if (isForward)
  159. {
  160. if (currentIndex >= _dataCache.Count - 1) return -1;
  161. currentIndex++;
  162. }
  163. else
  164. {
  165. if (currentIndex <= 0) return -1;
  166. currentIndex--;
  167. }
  168. if (_dataCache[originalIndex].Data.Diff(_dataCache[currentIndex].Data).Count > 0)
  169. return currentIndex;
  170. return FindNextChangeIndex(currentIndex, isForward);
  171. }
  172. public void RewindToChange(bool isForward)
  173. {
  174. if (_currentTimestampFtUtc == null) return;
  175. var timestamp = _currentTimestampFtUtc;
  176. int currentIndex = -1;
  177. var currentItem = _dataCache.FirstOrDefault(x => x.TimestampFtUtc == _currentTimestampFtUtc);
  178. if (currentItem == null) return;
  179. currentIndex = _dataCache.IndexOf(currentItem);
  180. var nextChangeIndex = FindNextChangeIndex(currentIndex, isForward);
  181. if (nextChangeIndex >= 0)
  182. SetAsCurrent(_dataCache[nextChangeIndex]);
  183. }
  184. private bool SetAsCurrent(DataCacheItem item)
  185. {
  186. var result = false;
  187. using (var fReader = _fileSystem.File.OpenText(item.FileName))
  188. {
  189. var data = JsonSerializer.Deserialize<MonData>(fReader.BaseStream);
  190. if (data != null)
  191. {
  192. ReceiverHandlerDataChanged?.Invoke(this, data);
  193. _currentTimestampFtUtc = item.TimestampFtUtc;
  194. CurrentData = new[] { data };
  195. result = true;
  196. }
  197. }
  198. return result;
  199. }
  200. private void SetRefreshInterval(TimeSpan refreshInterval)
  201. {
  202. _refreshTimer?.Stop(true);
  203. _refreshInterval = refreshInterval;
  204. _refreshTimer?.Dispose();
  205. _log?.Log(LogSeverityEnum.Debug, $"[{InstanceIdentifier}/{Descriptor.Name}] Refresh interval was set to {_refreshInterval.ToReadableString()}");
  206. _refreshTimer = new LoopWorker("ReceiverHandler",null,async (_)=>await OnRefreshTimer(this), _logger);
  207. _refreshTimer.Start(_refreshInterval);
  208. }
  209. private async Task OnRefreshTimer(object? state)
  210. {
  211. //TODO: add overlap checking here
  212. if (State == ReceiverHandlerStateEnum.Play)
  213. {
  214. if (_dataCache.Count == 0) return;
  215. if (_currentTimestampFtUtc < _dataCache.Last().TimestampFtUtc)
  216. {
  217. var currentItem = _dataCache.FirstOrDefault(x => x.TimestampFtUtc == _currentTimestampFtUtc);
  218. if (currentItem == null) return;
  219. var currentIndex = _dataCache.IndexOf(currentItem);
  220. var newChangeIndex = FindNextChangeIndex(currentIndex, true);
  221. if (newChangeIndex >= 0)
  222. SetAsCurrent(_dataCache[newChangeIndex]);
  223. }
  224. }
  225. }
  226. private void RefreshFiles()
  227. {
  228. var result = new List<Tuple<string, DateTime>>();
  229. var dirName = _fileSystem.Path.Combine(_cachePath, InstanceIdentifier);
  230. if (!_fileSystem.Directory.Exists(dirName))
  231. {
  232. _fileSystem.Directory.CreateDirectory(dirName);
  233. _log?.Log(LogSeverityEnum.Debug, $"Data receiver created data cache directory '{dirName}'");
  234. }
  235. var files = _fileSystem.Directory.GetFiles(dirName, GetFileSearchMask());
  236. var start = DateTime.Now;
  237. //remove missing files (was deleted by retention policy)
  238. var cacheItemsToRemove = _dataCache.Where(x => !files.Contains(x.FileName)).ToList();
  239. foreach (var item in cacheItemsToRemove)
  240. {
  241. _dataCache.Remove(item);
  242. item.Data.Dispose();
  243. }
  244. var addedCnt = 0;
  245. foreach (var file in files)
  246. if (MayAddFileToCache(file)) addedCnt++;
  247. _log?.Log(LogSeverityEnum.Debug,$"[{InstanceIdentifier}/{Descriptor.Name}] Read {addedCnt} files and add to cache @ {DateTime.Now.Subtract(start).TotalMilliseconds} ms.");
  248. }
  249. private bool MayAddFileToCache(string fileName)
  250. {
  251. if (_dataCache.Any(x=>x.FileName == fileName)) return false;
  252. using (var fReader = _fileSystem.File.OpenText(fileName))
  253. {
  254. var data = JsonSerializer.Deserialize<MonData>(fReader.BaseStream);
  255. if (data == null) return false;
  256. _dataCache.Add(new DataCacheItem(fileName, data));
  257. }
  258. return true;
  259. }
  260. private string GetFileSearchMask()
  261. {
  262. return $"{Descriptor.Name}*.json";
  263. }
  264. protected override void OnDisposing()
  265. {
  266. ReceiverHandlerStateChanged = null;
  267. ReceiverHandlerStateChanged = null;
  268. _watcher?.Dispose();
  269. _watcher = null;
  270. _refreshTimer?.Dispose();
  271. }
  272. private void Watcher_Deleted(object sender, FileSystemEventArgs e)
  273. {
  274. if (_dataCache.Any(x => x.FileName == e.FullPath))
  275. {
  276. var item = _dataCache.First(x => x.FileName == e.FullPath);
  277. _dataCache.Remove(item);
  278. item.Data.Dispose();
  279. }
  280. }
  281. private void Watcher_Created(object sender, FileSystemEventArgs e)
  282. {
  283. if (_dataCache.All(x => x.FileName != e.FullPath))
  284. {
  285. MayAddFileToCache(e.FullPath);
  286. }
  287. }
  288. #endregion
  289. #region *** Nested Classes ***
  290. private class DataCacheItem
  291. {
  292. public long TimestampFtUtc { get; set; }
  293. public JsonDocument Data { get; set; }
  294. public string FileName { get; set; }
  295. public DataCacheItem(string fileName, MonData data)
  296. {
  297. TimestampFtUtc = data.TimestampFtUtc;
  298. Data = JsonDocument.Parse(data.Data);
  299. FileName = fileName;
  300. }
  301. }
  302. #endregion
  303. }
  304. }