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 EntityTitles = new Dictionary { {"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> lockersList; static ContentLocker() { lockersList = new Dictionary>(); 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 lockedList) { List unlockItems = new List(); foreach (var lockedElement in lockedList) { if (lockedElement.Value.GetReleaseDateTime() < DateTime.Now) { unlockItems.Add(lockedElement.Key); } } foreach (var unlockId in unlockItems) { lockedList.Remove(unlockId); } } /// /// Adds an object lock to the dictionary (if possible) and returns result of this operation. /// /// Unique identifier of the database table in which we store the locked object. /// Unique identifier of the locked object. /// Username of the owner of the lock. /// A timestamp of the object to check that system works with actual data. /// An instance of with info about operation result. 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()); } // 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; } /// /// Removed all locks for specified fieldId /// /// /// Locks removed count 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 lockItems = new List(); 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> GetEntityTitles(Dictionary> entities) { if (entities == null || !entities.Any()) return new Dictionary>(); var results = new System.Collections.Concurrent.ConcurrentDictionary>(); 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(); 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; } } }