| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- using System.IO.Abstractions;
- using System.Text.Json;
- using Quadarax.Foundation.Core.Collections;
- using Quadarax.Foundation.Core.Json.Extensions;
- using Quadarax.Foundation.Core.Logging;
- using Quadarax.Foundation.Core.QMonitor.Interfaces;
- using Quadarax.Foundation.Core.Object;
- using Quadarax.Foundation.Core.Thread;
- using Quadarax.Foundation.Core.Value.Extensions;
- namespace Quadarax.Foundation.Core.QMonitor
- {
- public class QMonReceiverHandler : DisposableObject, IReceiverHandler
- {
- #region *** Properties ***
- public string InstanceIdentifier { get; }
- public MonItemGeneral Descriptor { get; }
- public ReceiverHandlerStateEnum State
- {
- get => _state;
- private set
- {
- _state = value;
- ReceiverHandlerStateChanged?.Invoke(this, _state);
- }
- }
- public bool IsLiveBroadcast {get; private set; }
- public DateTime? Timestamp => _currentTimestampFtUtc == null ? null : DateTime.FromFileTimeUtc(_currentTimestampFtUtc.Value);
- public TimeSpan RefreshInterval { get => _refreshInterval; set => SetRefreshInterval(value); }
- public MonData[]? CurrentData { get; private set; }
- public event ReceiverHandlerStateChanged? ReceiverHandlerStateChanged;
- public event ReceiverHandlerDataChanged? ReceiverHandlerDataChanged;
- #endregion
- #region *** Private fields ***
- private ReceiverHandlerStateEnum _state;
- private TimeSpan _refreshInterval;
- private IFileSystem _fileSystem;
- private string _cachePath;
- private ILog? _log;
- private IFileSystemWatcher? _watcher;
- private LoopWorker _refreshTimer;
- private readonly LockedList<DataCacheItem> _dataCache = new();
- private ILogger? _logger;
- private long? _currentTimestampFtUtc;
- #endregion
- #region *** Constructor ***
- internal QMonReceiverHandler(IFileSystem fileSystem, string cachePath, string instanceIdentifier, MonItemGeneral descriptor,TimeSpan refreshInterval, ILogger? logger)
- {
- _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
- if (string.IsNullOrEmpty(cachePath)) throw new ArgumentNullException(nameof(cachePath));
- _cachePath = cachePath;
- _log = logger?.GetLogger(nameof(QMonReceiverHandler));
- _logger = logger;
- if(string.IsNullOrEmpty(instanceIdentifier)) throw new ArgumentNullException(nameof(instanceIdentifier));
- InstanceIdentifier = instanceIdentifier;
- Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
- RefreshFiles();
- _watcher = _fileSystem.FileSystemWatcher.New(_fileSystem.Path.Combine(_cachePath, InstanceIdentifier),
- GetFileSearchMask());
- _watcher.Created += Watcher_Created;
- _watcher.Deleted += Watcher_Deleted;
- RefreshInterval = refreshInterval;
- _currentTimestampFtUtc = GetLastAvailableDataTimestamp()?.ToFileTimeUtc();
- _state = ReceiverHandlerStateEnum.Stop;
- }
- #endregion
- #region *** Public Operations ***
- public DateTime? GetFirstAvailableDataTimestamp()
- {
-
- if (_dataCache.Count == 0) return null;
- var first = _dataCache.Select(x => x.TimestampFtUtc).MinBy(x => x);
- return DateTime.FromFileTimeUtc(first);
-
- }
- public DateTime? GetLastAvailableDataTimestamp()
- {
-
- if (_dataCache.Count == 0) return null;
- var last = _dataCache.Select(x => x.TimestampFtUtc).MinBy(x => x);
- return DateTime.FromFileTimeUtc(last);
-
- }
- public void Play(bool isLive)
- {
- State = ReceiverHandlerStateEnum.Play;
- if (isLive)
- {
- if (_dataCache.Count == 0) return;
- SetAsCurrent(_dataCache.Last());
- }
-
- }
- public void RewindToPreviousChange()
- {
- RewindToChange(false);
- }
- public void RewindToNextChange()
- {
- RewindToChange(true);
- }
- public void Record()
- {
- throw new NotImplementedException();
- }
- public void Rewind(TimeSpan? duration = null, bool isBackward = false)
- {
- State = ReceiverHandlerStateEnum.Rewind;
- if (_dataCache.Count == 0 || _dataCache.Count == 1)
- {
- State = ReceiverHandlerStateEnum.Stop;
- return;
- }
- if (duration == null || duration == TimeSpan.Zero)
- {
- if (isBackward)
- SetAsCurrent(_dataCache.First());
- else
- SetAsCurrent(_dataCache.Last());
- State = ReceiverHandlerStateEnum.Stop;
- return;
- }
- var findTimestamp = (Timestamp ?? DateTime.Now);
- var findTimestampFtUtc = (isBackward ? findTimestamp.Subtract(duration.Value) : findTimestamp.Add(duration.Value)).ToFileTimeUtc();
- int newIndex = -1;
- if (isBackward)
- {
- for (var i = 0; i < _dataCache.Count; i++)
- {
- if (_dataCache[i].TimestampFtUtc >= findTimestampFtUtc)
- {
- newIndex = i;
- break;
- }
- }
-
- }
- else
- {
-
- for (var i = _dataCache.Count - 1; i >= 0; i--)
- {
- if (_dataCache[i].TimestampFtUtc <= findTimestampFtUtc)
- {
- newIndex = i;
- break;
- }
- }
- }
- if (newIndex >= 0)
- {
- SetAsCurrent(_dataCache[newIndex]);
- }
- State = ReceiverHandlerStateEnum.Stop;
- }
-
- public void Stop()
- {
- State = ReceiverHandlerStateEnum.Stop;
- }
- #endregion
- #region *** Private Operations ***
- public int FindNextChangeIndex(int currentIndex, bool isForward)
- {
- if (currentIndex < 0 || currentIndex >= _dataCache.Count) return -1;
- var originalIndex = currentIndex;
- if (isForward)
- {
- if (currentIndex >= _dataCache.Count - 1) return -1;
- currentIndex++;
- }
- else
- {
- if (currentIndex <= 0) return -1;
- currentIndex--;
- }
- if (_dataCache[originalIndex].Data.Diff(_dataCache[currentIndex].Data).Count > 0)
- return currentIndex;
- return FindNextChangeIndex(currentIndex, isForward);
- }
- public void RewindToChange(bool isForward)
- {
- if (_currentTimestampFtUtc == null) return;
- var timestamp = _currentTimestampFtUtc;
- int currentIndex = -1;
-
- var currentItem = _dataCache.FirstOrDefault(x => x.TimestampFtUtc == _currentTimestampFtUtc);
- if (currentItem == null) return;
- currentIndex = _dataCache.IndexOf(currentItem);
-
- var nextChangeIndex = FindNextChangeIndex(currentIndex, isForward);
- if (nextChangeIndex >= 0)
- SetAsCurrent(_dataCache[nextChangeIndex]);
- }
- private bool SetAsCurrent(DataCacheItem item)
- {
- var result = false;
- using (var fReader = _fileSystem.File.OpenText(item.FileName))
- {
- var data = JsonSerializer.Deserialize<MonData>(fReader.BaseStream);
- if (data != null)
- {
- ReceiverHandlerDataChanged?.Invoke(this, data);
- _currentTimestampFtUtc = item.TimestampFtUtc;
- CurrentData = new[] { data };
- result = true;
- }
- }
- return result;
- }
-
- private void SetRefreshInterval(TimeSpan refreshInterval)
- {
- _refreshTimer?.Stop(true);
- _refreshInterval = refreshInterval;
- _refreshTimer?.Dispose();
- _log?.Log(LogSeverityEnum.Debug, $"[{InstanceIdentifier}/{Descriptor.Name}] Refresh interval was set to {_refreshInterval.ToReadableString()}");
- _refreshTimer = new LoopWorker("ReceiverHandler",null,async (_)=>await OnRefreshTimer(this), _logger);
- _refreshTimer.Start(_refreshInterval);
- }
- private async Task OnRefreshTimer(object? state)
- {
- //TODO: add overlap checking here
- if (State == ReceiverHandlerStateEnum.Play)
- {
- if (_dataCache.Count == 0) return;
- if (_currentTimestampFtUtc < _dataCache.Last().TimestampFtUtc)
- {
- var currentItem = _dataCache.FirstOrDefault(x => x.TimestampFtUtc == _currentTimestampFtUtc);
- if (currentItem == null) return;
- var currentIndex = _dataCache.IndexOf(currentItem);
- var newChangeIndex = FindNextChangeIndex(currentIndex, true);
- if (newChangeIndex >= 0)
- SetAsCurrent(_dataCache[newChangeIndex]);
- }
- }
- }
-
- private void RefreshFiles()
- {
- var result = new List<Tuple<string, DateTime>>();
- var dirName = _fileSystem.Path.Combine(_cachePath, InstanceIdentifier);
- if (!_fileSystem.Directory.Exists(dirName))
- {
- _fileSystem.Directory.CreateDirectory(dirName);
- _log?.Log(LogSeverityEnum.Debug, $"Data receiver created data cache directory '{dirName}'");
- }
- var files = _fileSystem.Directory.GetFiles(dirName, GetFileSearchMask());
-
- var start = DateTime.Now;
-
- //remove missing files (was deleted by retention policy)
- var cacheItemsToRemove = _dataCache.Where(x => !files.Contains(x.FileName)).ToList();
- foreach (var item in cacheItemsToRemove)
- {
- _dataCache.Remove(item);
- item.Data.Dispose();
- }
- var addedCnt = 0;
- foreach (var file in files)
- if (MayAddFileToCache(file)) addedCnt++;
- _log?.Log(LogSeverityEnum.Debug,$"[{InstanceIdentifier}/{Descriptor.Name}] Read {addedCnt} files and add to cache @ {DateTime.Now.Subtract(start).TotalMilliseconds} ms.");
-
- }
- private bool MayAddFileToCache(string fileName)
- {
- if (_dataCache.Any(x=>x.FileName == fileName)) return false;
- using (var fReader = _fileSystem.File.OpenText(fileName))
- {
- var data = JsonSerializer.Deserialize<MonData>(fReader.BaseStream);
- if (data == null) return false;
- _dataCache.Add(new DataCacheItem(fileName, data));
- }
- return true;
- }
- private string GetFileSearchMask()
- {
- return $"{Descriptor.Name}*.json";
- }
- protected override void OnDisposing()
- {
- ReceiverHandlerStateChanged = null;
- ReceiverHandlerStateChanged = null;
- _watcher?.Dispose();
- _watcher = null;
- _refreshTimer?.Dispose();
- }
- private void Watcher_Deleted(object sender, FileSystemEventArgs e)
- {
- if (_dataCache.Any(x => x.FileName == e.FullPath))
- {
- var item = _dataCache.First(x => x.FileName == e.FullPath);
-
- _dataCache.Remove(item);
- item.Data.Dispose();
- }
- }
- private void Watcher_Created(object sender, FileSystemEventArgs e)
- {
- if (_dataCache.All(x => x.FileName != e.FullPath))
- {
- MayAddFileToCache(e.FullPath);
- }
- }
- #endregion
- #region *** Nested Classes ***
- private class DataCacheItem
- {
- public long TimestampFtUtc { get; set; }
- public JsonDocument Data { get; set; }
- public string FileName { get; set; }
- public DataCacheItem(string fileName, MonData data)
- {
- TimestampFtUtc = data.TimestampFtUtc;
- Data = JsonDocument.Parse(data.Data);
- FileName = fileName;
- }
- }
- #endregion
- }
- }
|