2021-10-19 08:35:44 +00:00
|
|
|
using System;
|
2021-12-16 13:05:11 +00:00
|
|
|
using System.Collections.Concurrent;
|
|
|
|
using System.Collections.Generic;
|
2021-10-19 08:35:44 +00:00
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
2021-12-16 13:05:11 +00:00
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using MDP.SID.Scripting.Grpc;
|
2022-06-13 10:57:54 +00:00
|
|
|
using MDP.SID.Shared.Public.Events;
|
2021-12-16 13:05:11 +00:00
|
|
|
using Newtonsoft.Json;
|
|
|
|
using Event = MDP.SID.Scripting.Grpc.Event;
|
2021-10-19 08:35:44 +00:00
|
|
|
using Timer = System.Timers.Timer;
|
|
|
|
|
|
|
|
public class Interval
|
|
|
|
{
|
|
|
|
public TimeSpan Start { get; set; }
|
|
|
|
public TimeSpan End { get; set; }
|
|
|
|
public string Name { get; set; }
|
|
|
|
}
|
|
|
|
|
|
|
|
private readonly Interval[] _accessIntervals =
|
|
|
|
{
|
2022-01-14 12:55:13 +00:00
|
|
|
// Set schedule range and access level name
|
|
|
|
// TimeSpan(hours, minutes, seconds)
|
|
|
|
// Name = "Access_level"
|
2021-12-20 14:31:11 +00:00
|
|
|
new() {Start = new TimeSpan(16, 23, 0), End = new TimeSpan(16, 24, 0), Name = "Breakfast"},
|
2022-06-13 10:57:54 +00:00
|
|
|
new() {Start = new TimeSpan(10, 24, 0), End = new TimeSpan(16, 25, 0), Name = "Everywhere"},
|
2021-12-20 14:31:11 +00:00
|
|
|
new() {Start = new TimeSpan(16, 25, 0), End = new TimeSpan(16, 26, 0), Name = "Dinner"}
|
2021-10-19 08:35:44 +00:00
|
|
|
};
|
|
|
|
|
2022-05-30 12:47:00 +00:00
|
|
|
// Set a list of door names that will be monitored
|
2021-10-19 08:35:44 +00:00
|
|
|
private readonly string[] _canteenDoors =
|
|
|
|
{
|
2022-07-05 08:17:40 +00:00
|
|
|
"Canteen_Door_1", "Canteen_Door_2"
|
2021-10-19 08:35:44 +00:00
|
|
|
};
|
|
|
|
|
2022-05-30 12:50:04 +00:00
|
|
|
/// <summary>
|
|
|
|
/// True -> remove last access granted user al with door open event
|
|
|
|
/// False -> remove user al with access granted event
|
|
|
|
/// </summary>
|
2022-06-13 10:57:54 +00:00
|
|
|
private const bool RemoveUserAlThenDoorOpened = true;
|
2021-10-19 08:35:44 +00:00
|
|
|
|
2022-01-14 12:55:13 +00:00
|
|
|
// Set time when access levels will reset
|
2021-12-20 14:31:11 +00:00
|
|
|
public readonly TimeSpan AccessLevelsResetTime = new(16, 27, 0);
|
2021-10-19 08:35:44 +00:00
|
|
|
|
2022-05-30 12:50:04 +00:00
|
|
|
private string _path;
|
|
|
|
|
|
|
|
private readonly SemaphoreSlim _semaphoreSlim = new(1);
|
|
|
|
|
2021-10-19 08:35:44 +00:00
|
|
|
private Timer _timer;
|
|
|
|
|
2022-07-05 08:17:40 +00:00
|
|
|
private Timer _lastAccessGrantTimer;
|
|
|
|
|
2021-12-16 13:05:11 +00:00
|
|
|
private static readonly ConcurrentDictionary<int, IList<string>> _failedAddOrUpdate = new();
|
2021-10-19 08:35:44 +00:00
|
|
|
|
2022-06-13 10:57:54 +00:00
|
|
|
private int? _lastAccessGrantedUserId;
|
|
|
|
|
|
|
|
private int? _lastAccessGrantedId;
|
2022-05-30 12:47:00 +00:00
|
|
|
|
2021-10-19 08:35:44 +00:00
|
|
|
private async Task ResetUsersAccessLevelsAsync()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
await _semaphoreSlim.WaitAsync();
|
|
|
|
|
|
|
|
if (File.Exists(_path) is false)
|
|
|
|
{
|
|
|
|
Context.LogError("Users file not found");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var alNames = _accessIntervals.Select(a => a.Name);
|
|
|
|
|
|
|
|
var accessLevels = await Context.GetAccessLevelsAsync();
|
|
|
|
|
|
|
|
var updateEntities = accessLevels
|
|
|
|
.Where(a => alNames.Contains(a.Name))
|
2021-12-20 14:31:11 +00:00
|
|
|
.Select(a => new EntityUpdate { Id = a.Id, Update = true })
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
updateEntities.AddRange(accessLevels
|
|
|
|
.Where(a => alNames.Contains(a.Name) is false)
|
|
|
|
.Select(a => new EntityUpdate { Id = a.Id}));
|
2021-10-19 08:35:44 +00:00
|
|
|
|
|
|
|
var text = await File.ReadAllTextAsync(_path);
|
|
|
|
|
|
|
|
var users = JsonConvert.DeserializeObject<Dictionary<int, IList<string>>>(text);
|
|
|
|
|
|
|
|
if (users is null || users.Any() is false)
|
|
|
|
{
|
|
|
|
Context.LogWarning("Users file empty");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var userIds = users.Keys.ToArray();
|
|
|
|
|
|
|
|
var configuration = new BulkUpdateUser();
|
|
|
|
|
|
|
|
configuration.AccessLevels.AddRange(updateEntities);
|
|
|
|
|
|
|
|
await Context.BulkUpdateUsersAsync(0, userIds.Length, userIds, configuration);
|
|
|
|
|
|
|
|
File.Delete(_path);
|
|
|
|
|
|
|
|
Context.LogInformation("Users file deleted after bulk update operation");
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
Context.LogError($"Failed reset users access levels. Exception: {ex.Message}");
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
_semaphoreSlim.Release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void AddOrUpdateUser(int userId, string accessLevel)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
_semaphoreSlim.Wait();
|
|
|
|
|
|
|
|
Dictionary<int, IList<string>> users;
|
|
|
|
|
|
|
|
if (File.Exists(_path) is false)
|
|
|
|
{
|
|
|
|
users = new Dictionary<int, IList<string>>();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
var text = File.ReadAllText(_path);
|
|
|
|
|
|
|
|
users = JsonConvert.DeserializeObject<Dictionary<int, IList<string>>>(text);
|
|
|
|
|
|
|
|
// no content
|
|
|
|
if (users is null)
|
|
|
|
{
|
|
|
|
users = new Dictionary<int, IList<string>>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AddFromFailed(users);
|
|
|
|
|
|
|
|
if (users.TryGetValue(userId, out var accessLevels))
|
|
|
|
{
|
|
|
|
if (accessLevels.Contains(accessLevel) is false)
|
|
|
|
{
|
|
|
|
accessLevels.Add(accessLevel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
users.Add(userId, new List<string> { accessLevel });
|
|
|
|
}
|
|
|
|
|
|
|
|
var output = JsonConvert.SerializeObject(users);
|
|
|
|
|
|
|
|
File.WriteAllText(_path, output);
|
|
|
|
|
|
|
|
_failedAddOrUpdate.Clear();
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
Context.LogError($"Failed add or update user with Id: {userId} al: {accessLevel}. Exception: {ex.Message}");
|
|
|
|
|
|
|
|
AddToFailed(userId, accessLevel);
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
_semaphoreSlim.Release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void AddToFailed(int userId, string accessLevel)
|
|
|
|
{
|
|
|
|
if (_failedAddOrUpdate.TryGetValue(userId, out var als))
|
|
|
|
{
|
|
|
|
if (als.Contains(accessLevel)) return;
|
|
|
|
|
|
|
|
var newAls = new List<string>(als) { accessLevel };
|
|
|
|
|
|
|
|
_failedAddOrUpdate.TryUpdate(userId, newAls, als);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_failedAddOrUpdate.TryAdd(userId, new List<string> { accessLevel });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void AddFromFailed(Dictionary<int, IList<string>> users)
|
|
|
|
{
|
|
|
|
foreach (var user in _failedAddOrUpdate)
|
|
|
|
{
|
|
|
|
if (users.TryGetValue(user.Key, out var accessLevels))
|
|
|
|
{
|
|
|
|
foreach (var al in user.Value)
|
|
|
|
{
|
|
|
|
if (accessLevels.Contains(al)) continue;
|
|
|
|
|
|
|
|
accessLevels.Add(al);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
users.Add(user.Key, user.Value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async void EventReceived(Event received)
|
|
|
|
{
|
2022-06-13 10:57:54 +00:00
|
|
|
if (_canteenDoors.Contains(received.DoorName) is false) return;
|
|
|
|
|
|
|
|
if (Guid.TryParse(received.TypeUid, out var typeUid) is false) return;
|
|
|
|
|
|
|
|
if (typeUid == EventTypes.AccessGranted)
|
|
|
|
{
|
|
|
|
if (received.UserId.HasValue is false) return;
|
|
|
|
|
|
|
|
_lastAccessGrantedUserId = received.UserId.Value;
|
|
|
|
|
|
|
|
if (RemoveUserAlThenDoorOpened)
|
|
|
|
{
|
|
|
|
if (_lastAccessGrantedId.HasValue)
|
|
|
|
{
|
2022-07-04 10:33:15 +00:00
|
|
|
await Context.SetEventsDescriptionAsync(new List<int> { (int)_lastAccessGrantedId }, "Ignore");
|
2022-06-13 10:57:54 +00:00
|
|
|
}
|
|
|
|
|
2022-07-04 10:52:16 +00:00
|
|
|
var eventTime = received.Time.ToDateTime().ToLocalTime().TimeOfDay;
|
|
|
|
|
2022-07-05 08:17:40 +00:00
|
|
|
var interval = _accessIntervals.FirstOrDefault(a => a.End >= eventTime && a.Start <= eventTime);
|
|
|
|
|
|
|
|
if (interval is not null)
|
2022-07-04 10:52:16 +00:00
|
|
|
{
|
2022-07-05 08:17:40 +00:00
|
|
|
_lastAccessGrantedId = received.Id;
|
|
|
|
|
|
|
|
if (_lastAccessGrantTimer is not null && _lastAccessGrantTimer.Enabled) return;
|
|
|
|
|
|
|
|
var timeToIntervalEnd = interval.End - eventTime;
|
|
|
|
|
|
|
|
StartLastAccessGrantTimer(timeToIntervalEnd);
|
2022-07-04 10:52:16 +00:00
|
|
|
}
|
2022-07-05 08:17:40 +00:00
|
|
|
}
|
2022-06-13 10:57:54 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
await RemoveUserAccessLevelAsync(received);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (RemoveUserAlThenDoorOpened && typeUid == EventTypes.DoorOpened)
|
|
|
|
{
|
|
|
|
await RemoveUserAccessLevelAsync(received);
|
|
|
|
}
|
2022-05-30 12:47:00 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 10:57:54 +00:00
|
|
|
private async Task RemoveUserAccessLevelAsync(Event received)
|
2022-05-30 12:47:00 +00:00
|
|
|
{
|
2022-06-13 10:57:54 +00:00
|
|
|
if (_lastAccessGrantedUserId.HasValue is false) return;
|
|
|
|
|
|
|
|
try
|
2021-10-19 08:35:44 +00:00
|
|
|
{
|
|
|
|
var accessTime = received.Time.ToDateTime().ToLocalTime().TimeOfDay;
|
|
|
|
|
|
|
|
var interval = _accessIntervals.FirstOrDefault(a => a.End >= accessTime && a.Start <= accessTime);
|
|
|
|
|
|
|
|
if (interval is null)
|
|
|
|
{
|
|
|
|
Context.LogDebug($"Al interval by access time not found. Access time: {accessTime}");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-30 12:47:00 +00:00
|
|
|
var userAccessLevels = await Context.GetUserAccessLevelsAsync(_lastAccessGrantedUserId.Value);
|
2021-10-19 08:35:44 +00:00
|
|
|
|
|
|
|
var userAccessLevel = userAccessLevels.FirstOrDefault(a => a.Name == interval.Name);
|
|
|
|
|
|
|
|
if (userAccessLevel is null)
|
|
|
|
{
|
2022-05-30 12:47:00 +00:00
|
|
|
Context.LogWarning($"Al for user with Id: {_lastAccessGrantedUserId.Value} not found");
|
2021-10-19 08:35:44 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await Context.DeleteUserAccessLevelAsync(userAccessLevel.Id);
|
|
|
|
|
2022-05-30 12:47:00 +00:00
|
|
|
Context.LogDebug($"Al: {userAccessLevel.Name} removed for user with Id: {_lastAccessGrantedUserId.Value}");
|
2021-10-19 08:35:44 +00:00
|
|
|
|
2022-05-30 12:47:00 +00:00
|
|
|
AddOrUpdateUser(_lastAccessGrantedUserId.Value, userAccessLevel.Name);
|
2022-06-13 10:57:54 +00:00
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
2022-05-30 12:47:00 +00:00
|
|
|
_lastAccessGrantedUserId = null;
|
2022-06-13 10:57:54 +00:00
|
|
|
|
|
|
|
_lastAccessGrantedId = null;
|
2021-10-19 08:35:44 +00:00
|
|
|
}
|
2022-06-13 10:57:54 +00:00
|
|
|
}
|
2021-10-19 08:35:44 +00:00
|
|
|
|
2022-07-05 08:17:40 +00:00
|
|
|
private void StartLastAccessGrantTimer(TimeSpan interval)
|
|
|
|
{
|
|
|
|
_lastAccessGrantTimer = new Timer(interval.TotalMilliseconds);
|
|
|
|
|
|
|
|
_lastAccessGrantTimer.Elapsed += async (_, _) => await OnLastAccessGrantEvent();
|
|
|
|
|
|
|
|
_lastAccessGrantTimer.AutoReset = false;
|
|
|
|
|
|
|
|
_lastAccessGrantTimer.Start();
|
|
|
|
}
|
|
|
|
|
|
|
|
private async Task OnLastAccessGrantEvent()
|
|
|
|
{
|
|
|
|
if (_lastAccessGrantedId.HasValue)
|
|
|
|
{
|
|
|
|
await Context.SetEventsDescriptionAsync(new List<int> { (int)_lastAccessGrantedId }, "Ignore");
|
|
|
|
|
|
|
|
_lastAccessGrantedId = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-19 08:35:44 +00:00
|
|
|
private async Task OnResetEvent()
|
|
|
|
{
|
|
|
|
_timer.Interval = new TimeSpan(24, 0, 0).TotalMilliseconds;
|
|
|
|
|
|
|
|
_timer.Start();
|
|
|
|
|
|
|
|
Context.LogInformation("Reset users al's event raised");
|
|
|
|
|
|
|
|
await ResetUsersAccessLevelsAsync();
|
|
|
|
}
|
|
|
|
|
|
|
|
Context.OnEventReceived += EventReceived;
|
|
|
|
|
|
|
|
var timeToReset = AccessLevelsResetTime - DateTime.Now.TimeOfDay;
|
|
|
|
|
|
|
|
if (timeToReset.CompareTo(TimeSpan.Zero) < 0)
|
|
|
|
{
|
|
|
|
timeToReset = new TimeSpan(1, AccessLevelsResetTime.Hours, AccessLevelsResetTime.Minutes, AccessLevelsResetTime.Seconds) - DateTime.Now.TimeOfDay;
|
|
|
|
}
|
|
|
|
|
|
|
|
Context.LogDebug($"Time until al reset first time hit (milliseconds): {timeToReset}");
|
|
|
|
|
|
|
|
_timer = new Timer(timeToReset.TotalMilliseconds);
|
|
|
|
_timer.Elapsed += async (_, _) => await OnResetEvent();
|
|
|
|
_timer.AutoReset = false;
|
|
|
|
_timer.Start();
|
|
|
|
|
|
|
|
var scriptFilesPath = await Context.GetScriptFilesPathAsync();
|
|
|
|
|
|
|
|
_path = Path.Combine(scriptFilesPath, "Canteen.txt");
|
|
|
|
|
|
|
|
CancellationToken.WaitHandle.WaitOne();
|
|
|
|
|
|
|
|
_timer.Stop();
|
|
|
|
|
|
|
|
_timer.Dispose();
|
|
|
|
|
2022-07-05 08:17:40 +00:00
|
|
|
if (_lastAccessGrantTimer is not null)
|
|
|
|
{
|
|
|
|
_lastAccessGrantTimer.Stop();
|
|
|
|
|
|
|
|
_lastAccessGrantTimer.Dispose();
|
|
|
|
}
|
|
|
|
|
2021-10-19 08:35:44 +00:00
|
|
|
Context.LogInformation("Script stopped");
|