265 lines
6.9 KiB
Plaintext
265 lines
6.9 KiB
Plaintext
|
using System;
|
||
|
using System.Threading.Tasks;
|
||
|
using MDP.SID.Scripting.Service;
|
||
|
using GrpcScriptService;
|
||
|
using Newtonsoft.Json;
|
||
|
using System.Threading;
|
||
|
using System.Net.Http;
|
||
|
using System.Net;
|
||
|
using System.IO;
|
||
|
using System.Text.RegularExpressions;
|
||
|
using System.Text;
|
||
|
using System.Linq;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Collections.Concurrent;
|
||
|
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 =
|
||
|
{
|
||
|
// Explanation -> TimeSpan(int hours, int minutes, int seconds)
|
||
|
new Interval {Start = new TimeSpan(16, 55, 0), End = new TimeSpan(17, 00, 0), Name = "Breakfast"},
|
||
|
new Interval {Start = new TimeSpan(17, 00, 0), End = new TimeSpan(17, 05, 0), Name = "Lunch"},
|
||
|
new Interval {Start = new TimeSpan(17, 05, 0), End = new TimeSpan(17, 10, 0), Name = "Dinner"}
|
||
|
};
|
||
|
|
||
|
private readonly string[] _canteenDoors =
|
||
|
{
|
||
|
"CANTEEN", "CANTEEN-DOOR-R-TUR-2 Ent"
|
||
|
};
|
||
|
|
||
|
private string _path;
|
||
|
|
||
|
private SemaphoreSlim _semaphoreSlim = new(1);
|
||
|
|
||
|
public readonly TimeSpan AccessLevelsResetTime = new(17, 11, 0);
|
||
|
|
||
|
private Timer _timer;
|
||
|
|
||
|
private static ConcurrentDictionary<int, IList<string>> _failedAddOrUpdate = new();
|
||
|
|
||
|
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))
|
||
|
.Select(a => new EntityUpdate { Id = a.Id, Update = true });
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
if (received.TypeUid == "891adc81-6991-44c2-8aa4-f7a0792f77f5" && _canteenDoors.Contains(received.DoorName))
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
var userAccessLevels = await Context.GetUserAccessLevelsAsync((int)received.UserId);
|
||
|
|
||
|
var userAccessLevel = userAccessLevels.FirstOrDefault(a => a.Name == interval.Name);
|
||
|
|
||
|
if (userAccessLevel is null)
|
||
|
{
|
||
|
Context.LogWarning($"Al for user with Id: {(int)received.UserId} not found");
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
await Context.DeleteUserAccessLevelAsync(userAccessLevel.Id);
|
||
|
|
||
|
Context.LogDebug($"Al: {userAccessLevel.Name} removed for user with Id: {(int)received.UserId}");
|
||
|
|
||
|
AddOrUpdateUser((int)received.UserId, userAccessLevel.Name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
|
||
|
Context.LogInformation("Script stopped");
|