using DTS.Common.Classes.Locking; using DTS.Common.Utilities.Logging; using System; using System.Data; using System.Data.SqlClient; namespace DTS.Common.Storage { public class LockManager { public static bool FreeLock(int itemId, string userName, int? userId, out LockError error, int categoryId) { error = null; try { using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_LockFree.ToString(); #region params cmd.Parameters.Add(new SqlParameter("@UserId", SqlDbType.Int) { Value = userId }); cmd.Parameters.Add(new SqlParameter("@UserName", SqlDbType.NVarChar, 50) { Value = userName }); cmd.Parameters.Add(new SqlParameter("@ItemId", SqlDbType.Int) { Value = itemId }); cmd.Parameters.Add(new SqlParameter("@CategoryId", SqlDbType.Int) { Value = categoryId }); var errorNumber = new SqlParameter("@errorNumber", SqlDbType.Int) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorNumber); var errorMessage = new SqlParameter("@errorMessage", SqlDbType.NVarChar, 250) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorMessage); #endregion params cmd.ExecuteNonQuery(); var errorCode = int.Parse(errorNumber.Value.ToString()); switch (errorCode) { case 0: return true; //all cool, lock updated case 1: //test setup not found case 2: //user not found error = new LockError(errorCode, (errorMessage.Value as string) ?? $"{errorCode}"); return false; case 3: //no lock to update error = new LockError(3, (errorMessage.Value as string) ?? $"{errorCode}"); return false; default: error = new LockError(4, (errorMessage.Value as string) ?? $"{errorCode}"); return false; } } finally { cmd.Connection.Dispose(); } } } catch (Exception ex) { APILogger.Log(ex); return false; } } public static bool UpdateLock(int itemId, int categoryId, string userName, int? userId, out LockError error) { error = null; try { using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_LockUpdate.ToString(); #region params cmd.Parameters.Add(new SqlParameter("@UserId", SqlDbType.Int) { Value = userId }); cmd.Parameters.Add(new SqlParameter("@UserName", SqlDbType.NVarChar, 50) { Value = userName }); cmd.Parameters.Add(new SqlParameter("@ItemId", SqlDbType.Int) { Value = itemId }); cmd.Parameters.Add(new SqlParameter("@CategoryId", SqlDbType.Int) { Value = categoryId }); cmd.Parameters.Add(new SqlParameter("@MachineName", SqlDbType.NVarChar, 50) { Value = Environment.MachineName }); var errorNumber = new SqlParameter("@errorNumber", SqlDbType.Int) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorNumber); var errorMessage = new SqlParameter("@errorMessage", SqlDbType.NVarChar, 250) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorMessage); var lockingUser = new SqlParameter("@LockingUser", SqlDbType.NVarChar, 50) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lockingUser); var lockingMachine = new SqlParameter("@LockingMachine", SqlDbType.NVarChar, 50) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lockingMachine); var lastTimeUsed = new SqlParameter("@LastUsedTime", SqlDbType.DateTime) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lastTimeUsed); var lockCreateTime = new SqlParameter("@LockCreateTime", SqlDbType.DateTime) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lockCreateTime); #endregion params cmd.ExecuteNonQuery(); var errorCode = int.Parse(errorNumber.Value.ToString()); switch (errorCode) { case 0: return true; //all cool, lock updated case 1: //test setup not found case 2: //user not found error = new LockError(errorCode, (errorMessage.Value as string) ?? $"{errorCode}"); return false; case 3: //no lock to update error = new LockError(LockError.LOCKDOESNTEXIST_ERROR, (errorMessage.Value as string) ?? $"{errorCode}"); return false; case 4: //lock stolen error = new LockError(LockError.LOCKSTOLEN_ERROR, $"Lock now belongs to user {lockingUser.Value}:{lockingMachine.Value}", (string)lockingUser.Value, (string)lockingMachine.Value); return false; default: error = new LockError(4, (errorMessage.Value as string) ?? $"{errorCode}"); return false; } } finally { cmd.Connection.Dispose(); } } } catch (SqlException ex) { switch (ex.Number) { case ERROR_BAD_NETPATH: error = new LockError(LockError.BAD_NETPATH_ERROR, Strings.Strings.BAD_NETPATH_ERROR_MSG); break; case ERROR_SEM_TIMEOUT: error = new LockError(LockError.SEM_TIMEOUT_ERROR, Strings.Strings.SEM_TIME_ERROR_MSG); break; } APILogger.Log("SqlException in UpdateLock", ex); return false; } catch (Exception ex) { APILogger.Log("Exception in UpdateLock", ex); return false; } } /// /// The semaphore timeout period has expired /// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-?redirectedfrom=MSDN /// private const int ERROR_SEM_TIMEOUT = 0x00000079; /// /// network path not found, apparently also can occur during lock management? /// 14782 Improve lost remote db connection modal dialogs /// private const int ERROR_BAD_NETPATH = 0x00000035; public enum ItemCategories { TestSetup, Group, Sensor, SystemSettings } public static bool LockItem(string itemKey, int itemId, ItemCategories itemCategory, string userName, int? userId, out LockRecord existingLock, out LockError error) { error = null; existingLock = null; try { using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_LockGet.ToString(); #region params int? id = null; if (itemId > 0) { id = itemId; } //NOTE YOU"LL NEED THE ID TO FREE, AND THE CURRENT SP DOESN'T RETURN IT cmd.Parameters.Add(new SqlParameter("@UserId", SqlDbType.Int) { Value = userId }); cmd.Parameters.Add(new SqlParameter("@UserName", SqlDbType.NVarChar, 50) { Value = userName }); cmd.Parameters.Add(new SqlParameter("@ItemId", SqlDbType.Int) { Value = id }); cmd.Parameters.Add(new SqlParameter("@ItemKey", SqlDbType.NVarChar, 50) { Value = itemKey }); cmd.Parameters.Add(new SqlParameter("@ItemCategory", SqlDbType.NVarChar, 50) { Value = itemCategory.ToString() }); cmd.Parameters.Add(new SqlParameter("@MachineName", SqlDbType.NVarChar, 50) { Value = Environment.MachineName }); var errorNumber = new SqlParameter("@errorNumber", SqlDbType.Int) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorNumber); var errorMessage = new SqlParameter("@errorMessage", SqlDbType.NVarChar, 250) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorMessage); var lockingUser = new SqlParameter("@LockingUser", SqlDbType.NVarChar, 50) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lockingUser); var lockingMachine = new SqlParameter("@LockingMachineName", SqlDbType.NVarChar, 50) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lockingMachine); var lastTimeUsed = new SqlParameter("@LastUsedTime", SqlDbType.DateTime) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lastTimeUsed); var lockCreateTime = new SqlParameter("@LockCreateTime", SqlDbType.DateTime) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lockCreateTime); var lockItemCategory = new SqlParameter("@LockedItemCategory", SqlDbType.Int) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lockItemCategory); var lockedItemId = new SqlParameter("@LockedItemId", SqlDbType.Int) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(lockedItemId); #endregion params cmd.ExecuteNonQuery(); int resultItemId = itemId; if (itemId < 0) { if (!DBNull.Value.Equals(lockedItemId.Value)) { resultItemId = Convert.ToInt32(lockedItemId.Value); } } var errorCode = int.Parse(errorNumber.Value.ToString()); switch (errorCode) { case 0: existingLock = new LockRecord(userName, Environment.MachineName, DateTime.Now, DateTime.Now, itemKey, Convert.ToInt32(lockItemCategory.Value), resultItemId); return true; //all cool, locked away case 1: //item not found error = new LockError(LockError.ITEM_NOT_FOUND, (errorMessage.Value as string) ?? $"{errorCode}"); return false; case 2: //user not found error = new LockError(errorCode, (errorMessage.Value as string) ?? $"{errorCode}"); return false; case 3: //already locked existingLock = new LockRecord((string)lockingUser.Value, (string)lockingMachine.Value, Convert.ToDateTime(lockCreateTime.Value), Convert.ToDateTime(lastTimeUsed.Value), itemKey, Convert.ToInt32(lockItemCategory.Value), resultItemId); error = new LockError(errorCode, (errorMessage.Value as string) ?? $"{errorCode}"); return false; default: error = new LockError(LockError.UNKNOWN_ERROR, (errorMessage.Value as string) ?? $"{errorCode}"); return false; } } finally { cmd.Connection.Dispose(); } } } catch (Exception ex) { APILogger.Log(ex); error = new LockError(LockError.UNKNOWN_ERROR, ex.Message); return false; } } } }