355 lines
13 KiB
C#
355 lines
13 KiB
C#
using EnVisage.Code;
|
||
using EnVisage.Code.BLL;
|
||
using EnVisage.Code.Cache;
|
||
using EnVisage.Models;
|
||
using EnVisage.Properties;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Timers;
|
||
using System.Web;
|
||
|
||
namespace EnVisage.App_Start
|
||
{
|
||
public class LockerResponse
|
||
{
|
||
public enum ResponseCode
|
||
{
|
||
OK = 0,
|
||
ObjectLocked = 1,
|
||
ObjectExpired = 2,
|
||
}
|
||
public ResponseCode Code { get; set; }
|
||
public bool Status { get; set; }
|
||
public string LockedBy { get; set; }
|
||
public string EntityTitle { get; set; }
|
||
public LockerResponse()
|
||
{
|
||
Code = ResponseCode.OK;
|
||
}
|
||
}
|
||
public static class ContentLocker
|
||
{
|
||
static readonly Dictionary<string, string> EntityTitles = new Dictionary<string, string>
|
||
{
|
||
{"SystemSettings", "System Settings"},
|
||
{"Clients", "Client"},
|
||
{"CreditDepartment", "Credit Department"},
|
||
{"ExpenditureCategory", "Expenditure Category"},
|
||
{"GLAccount", "GL Account"},
|
||
{"NonProjectTime", "Non-Project Time"},
|
||
{"ScenarioGroup", "Scenario Group"},
|
||
{"ExpenditureCategoryRate", "Rate"},
|
||
{"Project Statuses", "Project Status"},
|
||
{"Types", "Type"},
|
||
{"UnitOfMeasure", "Unit Of Measure"},
|
||
{"NonProjectTimeCategories", "Non-Project Time Categories"},
|
||
{"PeopleResource", "People Resource"}
|
||
};
|
||
static Timer timer;
|
||
|
||
static Dictionary<string, Dictionary<string, LockedElement>> lockersList;
|
||
|
||
static ContentLocker()
|
||
{
|
||
lockersList = new Dictionary<string, Dictionary<string, LockedElement>>();
|
||
|
||
timer = new Timer(Settings.Default.CheckInterval);
|
||
|
||
timer.Elapsed += СheckDeadLocks;
|
||
|
||
if (!timer.Enabled)
|
||
timer.Start();
|
||
}
|
||
|
||
static void СheckDeadLocks(object sender, ElapsedEventArgs e)
|
||
{
|
||
foreach (var lockedElement in lockersList)
|
||
{
|
||
UnlockDead(lockedElement.Value);
|
||
}
|
||
}
|
||
|
||
static void UnlockDead(Dictionary<string, LockedElement> lockedList)
|
||
{
|
||
List<string> unlockItems = new List<string>();
|
||
|
||
foreach (var lockedElement in lockedList)
|
||
{
|
||
if (lockedElement.Value.GetReleaseDateTime() < DateTime.Now)
|
||
{
|
||
unlockItems.Add(lockedElement.Key);
|
||
}
|
||
}
|
||
|
||
foreach (var unlockId in unlockItems)
|
||
{
|
||
lockedList.Remove(unlockId);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds an object lock to the dictionary (if possible) and returns result of this operation.
|
||
/// </summary>
|
||
/// <param name="tableId">Unique identifier of the database table in which we store the locked object.</param>
|
||
/// <param name="fieldId">Unique identifier of the locked object.</param>
|
||
/// <param name="owner">Username of the owner of the lock.</param>
|
||
/// <param name="timestamp">A timestamp of the object to check that system works with actual data.</param>
|
||
/// <returns>An instance of <see cref="LockerResponse"/> with info about operation result.</returns>
|
||
public static LockerResponse AddLock(String tableId, String fieldId, string owner, string timestamp)
|
||
{
|
||
// add a table to the main dictionary if there is no such table yet
|
||
if (!lockersList.ContainsKey(tableId))
|
||
{
|
||
lockersList.Add(tableId, new Dictionary<string, LockedElement>());
|
||
}
|
||
// if there is a lock on the specified object but made by another user then return failed result with info about locked object
|
||
if (lockersList[tableId].ContainsKey(fieldId) && lockersList[tableId][fieldId].Owner != owner)
|
||
{
|
||
return new LockerResponse { Status = false, Code = LockerResponse.ResponseCode.ObjectLocked, LockedBy = lockersList[tableId][fieldId].Owner, EntityTitle = GetLockedEntityTitle(tableId) };
|
||
}
|
||
// if there is a lock on the specified object made by current user then do not add lock and return successfull result
|
||
if (lockersList[tableId].ContainsKey(fieldId) && lockersList[tableId][fieldId].Owner == owner)
|
||
{
|
||
return new LockerResponse { Status = true, EntityTitle = GetLockedEntityTitle(tableId) };
|
||
}
|
||
// if there is no lock on the specified object but it has been updated since last access then return failed result with info about modified object
|
||
if (!IsObjectValid(tableId, fieldId, timestamp))
|
||
{
|
||
return new LockerResponse { Status = false, Code = LockerResponse.ResponseCode.ObjectExpired, EntityTitle = GetLockedEntityTitle(tableId) };
|
||
}
|
||
// add a lock to the specified object by current user
|
||
lockersList[tableId].Add(fieldId, new LockedElement(owner));
|
||
// return successfull result
|
||
return new LockerResponse { Status = true, EntityTitle = GetLockedEntityTitle(tableId) };
|
||
}
|
||
|
||
public static bool RemoveLock(String tableId, String fieldId, string owner)
|
||
{
|
||
if (!lockersList.ContainsKey(tableId))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!lockersList[tableId].ContainsKey(fieldId))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (lockersList[tableId][fieldId].Owner == owner)
|
||
{
|
||
lockersList[tableId].Remove(fieldId);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Removed all locks for specified fieldId
|
||
/// </summary>
|
||
/// <param name="fieldId"></param>
|
||
/// <returns>Locks removed count</returns>
|
||
public static int RemoveAllLocks(String fieldId)
|
||
{
|
||
if (String.IsNullOrWhiteSpace(fieldId))
|
||
return 0;
|
||
|
||
var fieldIdProcessed = fieldId.ToLower().Trim();
|
||
int removedLocksCount = 0;
|
||
var lockedTableKeys = lockersList.Keys.ToList();
|
||
|
||
for (var tIndex = lockedTableKeys.Count - 1; tIndex >= 0; tIndex--)
|
||
{
|
||
var tableKey = lockedTableKeys[tIndex];
|
||
var tableLocks = lockersList[tableKey];
|
||
|
||
if ((tableLocks != null) && (tableLocks.Keys.Count > 0))
|
||
{
|
||
var fieldKeys = tableLocks.Keys.ToList();
|
||
|
||
for (var fIndex = fieldKeys.Count - 1; fIndex >= 0; fIndex--)
|
||
{
|
||
var currentFieldKey = fieldKeys[fIndex];
|
||
if (currentFieldKey.ToLower().Trim() == fieldIdProcessed)
|
||
{
|
||
RemoveLock(tableKey, currentFieldKey, tableLocks[currentFieldKey].Owner);
|
||
removedLocksCount++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return removedLocksCount;
|
||
}
|
||
|
||
public static bool UpdateLock(String tableId, String fieldId, string owner)
|
||
{
|
||
if (!lockersList.ContainsKey(tableId))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!lockersList[tableId].ContainsKey(fieldId))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (lockersList[tableId][fieldId].Owner == owner)
|
||
{
|
||
lockersList[tableId][fieldId].UpdateLock();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public static string[] getLockList(String tableId)
|
||
{
|
||
if (lockersList.ContainsKey(tableId))
|
||
{
|
||
List<string> lockItems = new List<string>();
|
||
foreach (var lockedElement in lockersList)
|
||
{
|
||
lockItems.Add(lockedElement.Key);
|
||
}
|
||
return lockItems.ToArray();
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public static LockerResponse IsLocked(String tableId, String fieldId, string owner)
|
||
{
|
||
if (lockersList.ContainsKey(tableId) && lockersList[tableId].ContainsKey(fieldId) && lockersList[tableId][fieldId].Owner != owner)
|
||
{
|
||
// if lock is expired then we should release it
|
||
if (lockersList[tableId][fieldId].GetReleaseDateTime() < DateTime.Now)
|
||
{
|
||
lockersList[tableId].Remove(fieldId);
|
||
return new LockerResponse { Status = false, EntityTitle = GetLockedEntityTitle(tableId) };
|
||
}
|
||
// object is locked by another user
|
||
return new LockerResponse { Status = true, Code = LockerResponse.ResponseCode.ObjectLocked, LockedBy = lockersList[tableId][fieldId].Owner, EntityTitle = GetLockedEntityTitle(tableId) };
|
||
}
|
||
// object is free to edit
|
||
return new LockerResponse { Status = false, EntityTitle = GetLockedEntityTitle(tableId) };
|
||
}
|
||
|
||
public static bool IsLock(String tableId, String fieldId, string owner)
|
||
{
|
||
return IsLocked(tableId, fieldId, owner).Status;
|
||
}
|
||
|
||
public static string GetLockedEntityTitle(string tableId)
|
||
{
|
||
if (EntityTitles.ContainsKey(tableId))
|
||
return EntityTitles[tableId];
|
||
return tableId;
|
||
}
|
||
|
||
private static bool IsObjectValid(string tableId, string fieldId, string timestamp)
|
||
{
|
||
var projectValid = true;
|
||
if (string.IsNullOrWhiteSpace(timestamp))
|
||
return projectValid;
|
||
using (var dbContext = new EnVisageEntities())
|
||
{
|
||
switch (tableId)
|
||
{
|
||
case "Project":
|
||
var stamp = timestamp.StringToByteArray();
|
||
var id = Guid.Parse(fieldId);
|
||
projectValid = dbContext.Projects.Count(t => t.Id == id && t.EditTimestamp == stamp) > 0;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
return projectValid;
|
||
}
|
||
private static Dictionary<string, Dictionary<Guid, string>> GetEntityTitles(Dictionary<string, IEnumerable<Guid>> entities)
|
||
{
|
||
if (entities == null || !entities.Any())
|
||
return new Dictionary<string, Dictionary<Guid, string>>();
|
||
var results = new System.Collections.Concurrent.ConcurrentDictionary<string, Dictionary<Guid, string>>();
|
||
entities.AsParallel().ForAll((pair) =>
|
||
{
|
||
using (var dbContext = new EnVisageEntities())
|
||
{
|
||
switch (pair.Key)
|
||
{
|
||
case "Project":
|
||
using (var manager = new ProjectManager(dbContext))
|
||
{
|
||
var items = manager.DataTable.Where(t => pair.Value.Contains(t.Id)).Select(t => new { t.Id, t.Name });
|
||
if (items != null && items.Any())
|
||
results.TryAdd(pair.Key, items.ToDictionary(t=>t.Id, v=>v.Name));
|
||
}
|
||
break;
|
||
case "Scenario":
|
||
using (var manager = new ScenarioManager(dbContext))
|
||
{
|
||
var items = manager.DataTable.Where(t => pair.Value.Contains(t.Id)).Select(t => new { t.Id, t.Name });
|
||
if (items != null && items.Any())
|
||
results.TryAdd(pair.Key, items.ToDictionary(t => t.Id, v => v.Name));
|
||
}
|
||
break;
|
||
case "Team":
|
||
using (var manager = new TeamManager(dbContext))
|
||
{
|
||
var items = manager.DataTable.Where(t => pair.Value.Contains(t.Id)).Select(t => new { t.Id, t.Name });
|
||
if (items != null && items.Any())
|
||
results.TryAdd(pair.Key, items.ToDictionary(t => t.Id, v => v.Name));
|
||
}
|
||
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
return results.ToDictionary(k=>k.Key, v=>v.Value);
|
||
}
|
||
|
||
public static ContentLockerObjectListModel GetLockedObjects(int pageSize = 25, int startOffset = 0, string orderBy = "ReleaseTimeUTC", bool isAsc = false)
|
||
{
|
||
var usersCache = (new UsersCache()).Value;
|
||
var items = new List<ContentLockerObjectModel>();
|
||
foreach (var table in lockersList)
|
||
{
|
||
if (table.Value != null && table.Value.Any())
|
||
items.AddRange(table.Value.Select(t => new ContentLockerObjectModel
|
||
{
|
||
EntityId = Guid.Parse(t.Key),
|
||
EntityType = GetLockedEntityTitle(table.Key),
|
||
Owner = usersCache.FirstOrDefault(u => u.UserName.ToString() == t.Value.Owner)?.DisplayName,
|
||
ReleaseTimeUTC = t.Value.GetReleaseDateTime().ToUniversalTime()
|
||
}));
|
||
}
|
||
switch (orderBy)
|
||
{
|
||
case "Owner":
|
||
items = (isAsc ? items.OrderBy(t => t.Owner) : items.OrderByDescending(t => t.Owner)).ToList();
|
||
break;
|
||
default:
|
||
items = (isAsc ? items.OrderBy(t => t.ReleaseTimeUTC) : items.OrderByDescending(t => t.ReleaseTimeUTC)).ToList();
|
||
break;
|
||
}
|
||
var result = new ContentLockerObjectListModel
|
||
{
|
||
Data = items.Skip(startOffset).Take(pageSize),
|
||
Total = items.Count()
|
||
};
|
||
var titles = GetEntityTitles(result.Data.GroupBy(gr => gr.EntityType).ToDictionary(k => k.Key, v => v.Select(gr => gr.EntityId)));
|
||
foreach (var item in result.Data)
|
||
{
|
||
if (titles.ContainsKey(item.EntityType))
|
||
{
|
||
if (titles[item.EntityType].ContainsKey(item.EntityId))
|
||
item.EntityTitle = titles[item.EntityType][item.EntityId];
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
}
|
||
} |