init
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ConfigToDb.Properties;
|
||||
using DTS.Common.Storage;
|
||||
using DTS.Slice.Users.UserSettings;
|
||||
|
||||
|
||||
namespace ConfigToDb
|
||||
{
|
||||
public class ConfigToDbMigrator
|
||||
{
|
||||
public bool migrateConfigToDb(string nextLowerPath, out string result)
|
||||
{
|
||||
var oldUserSettings = new SettingElementCollection();
|
||||
var oldApplicationSettings = new SettingElementCollection();
|
||||
GetOldConfigSettings(Resources.UserSettings, nextLowerPath, out result, out oldUserSettings);
|
||||
if (!result.Contains(Resources.OldSettingsCouldNotBeFound) && !result.Contains(Resources.OldSettingsCouldNotBeProcessed))
|
||||
{
|
||||
GetOldConfigSettings(Resources.ApplicationSettings, nextLowerPath, out result, out oldApplicationSettings);
|
||||
if (InsertConfigSettingsIntoDb(oldUserSettings, oldApplicationSettings, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//private SettingElementCollection GetOldConfigSettings(string settingsType, string nextLowerPath, out string result)
|
||||
//{
|
||||
// var oldSettings = new SettingElementCollection();
|
||||
// GetOldSettings(settingsType, nextLowerPath, out result, out oldSettings);
|
||||
// return oldSettings;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the settings from the old config file
|
||||
/// </summary>
|
||||
/// <param name="settingsType">Either UserSettings or ApplicationSettings.</param>
|
||||
/// <param name="nextLowerPath">The path to the old config file.</param>
|
||||
/// <param name="result">Text describing the success/failure of the config migration.</param>
|
||||
/// <param name="oldSettings">The settings from the old config file</param>
|
||||
/// <returns>True if the settings were successfully found and processed, false if not.</returns>
|
||||
private void GetOldConfigSettings(string settingsType, string nextLowerPath, out string result, out SettingElementCollection oldSettings)
|
||||
{
|
||||
result = string.Empty;
|
||||
oldSettings = null;
|
||||
|
||||
var oldPath = string.Empty;
|
||||
try
|
||||
{
|
||||
//Open the config file from the most-recently installed version of DataPRO - this assumes that it was installed in the default folder!!!
|
||||
oldPath = nextLowerPath + Resources.RegistryDataPROExe;
|
||||
var oldConfig = ConfigurationManager.OpenExeConfiguration(@oldPath);
|
||||
oldSettings = GetConfigSettings(settingsType, oldConfig);
|
||||
if (oldSettings == null)
|
||||
{
|
||||
result = Resources.OldSettingsCouldNotBeProcessed;
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
result = string.Format(Resources.OldSettingsCouldNotBeFound, ex.Message, oldPath);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the settings in a section group of the config file.
|
||||
/// </summary>
|
||||
/// <param name="settingsType">Examples include "userSettings" and "applicationSettings"</param>
|
||||
/// <param name="config">The config file to be interrogated.</param>
|
||||
/// <returns>A collection of settings from the requested section of the config file.</returns>
|
||||
private SettingElementCollection GetConfigSettings(string settingsType, Configuration config)
|
||||
{
|
||||
var clientSettingsSection = new ClientSettingsSection();
|
||||
var configurationSectionGroup = config.SectionGroups[settingsType];
|
||||
if ((configurationSectionGroup != null) && (configurationSectionGroup.Sections[0] != null))
|
||||
{
|
||||
clientSettingsSection = configurationSectionGroup.Sections[0] as System.Configuration.ClientSettingsSection;
|
||||
}
|
||||
return clientSettingsSection != null ? clientSettingsSection.Settings : null;
|
||||
}
|
||||
private bool InsertConfigSettingsIntoDb(SettingElementCollection oldUserSettings, SettingElementCollection oldApplicationSettings, out string result)
|
||||
{
|
||||
result = string.Empty;
|
||||
|
||||
var oldUserSettingsDictionary = oldUserSettings.Cast<SettingElement>().ToDictionary(oldSetting => oldSetting.Name, oldSetting => oldSetting.Value.ValueXml.InnerXml);
|
||||
foreach (var setting in oldUserSettingsDictionary)
|
||||
{
|
||||
switch (setting.Key)
|
||||
{
|
||||
case "LastUsedSampleRate":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.LastUsedSampleRate, setting.Value);
|
||||
break;
|
||||
case "RealtimeChartWidthInSeconds":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.RealtimeChartWidthInSeconds, setting.Value);
|
||||
break;
|
||||
case "LastRunTestSetup":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.LastRunTestSetup, setting.Value);
|
||||
break;
|
||||
case "DefaultSuppressMissingSensorsWarning":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultSuppressMissingSensorsWarning, setting.Value);
|
||||
break;
|
||||
case "UsersCurrentTestSetup":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.UsersCurrentTestSetup, setting.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var oldApplicationSettingsDictionary = oldApplicationSettings.Cast<SettingElement>().ToDictionary(oldSetting => oldSetting.Name, oldSetting => oldSetting.Value.ValueXml.InnerXml);
|
||||
foreach (var setting in oldApplicationSettingsDictionary)
|
||||
{
|
||||
switch (setting.Key)
|
||||
{
|
||||
case "DefaultUploadFolder":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultUploadFolder, setting.Value);
|
||||
break;
|
||||
case "DefaultUploadEnabled":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultUploadEnabled, setting.Value);
|
||||
break;
|
||||
case "DefaultTestSampleRate":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultTestSampleRate, setting.Value);
|
||||
break;
|
||||
case "DefaultPostTriggerSeconds":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultPostTriggerSeconds, setting.Value);
|
||||
break;
|
||||
case "DefaultNumberOfEvents":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultNumberOfEvents, setting.Value);
|
||||
break;
|
||||
case "DefaultPreTriggerSeconds":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultPreTriggerSeconds, setting.Value);
|
||||
break;
|
||||
case "ArmTriggerDiagnosticsRunOnNextStep":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ArmTriggerDiagnosticsRunOnNextStep, setting.Value);
|
||||
break;
|
||||
case "DefaultTestTriggerCheckStep":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultTriggerCheckStep, setting.Value);
|
||||
break;
|
||||
case "DefaultTestRealtimeModeGraphCount":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultRealtimeGraphCount, setting.Value);
|
||||
break;
|
||||
case "DefaultTestROIStart":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultROIStart, setting.Value);
|
||||
break;
|
||||
case "DefaultTestROIEnd":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultROIEnd, setting.Value);
|
||||
break;
|
||||
case "DefaultTestDownloadROI":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultDownloadROI, setting.Value);
|
||||
break;
|
||||
case "DefaultTestViewROI":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultViewROI, setting.Value);
|
||||
break;
|
||||
case "DefaultTestDownloadAll":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultDownloadAll, setting.Value);
|
||||
break;
|
||||
case "DefaultTestViewAll":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultViewAll, setting.Value);
|
||||
break;
|
||||
case "DefaultTestTreeModeDiagnostics":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultTreeModeDiagnostics, setting.Value);
|
||||
break;
|
||||
case "DefaultTestRequireAllUnitsPassDiagnostics":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultRequireAllUnitsPassDiagnostics, setting.Value);
|
||||
break;
|
||||
case "DefaultTestRequireUserConfirmationOnErrors":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultRequireUserConfirmationOnErrors, setting.Value);
|
||||
break;
|
||||
case "DefaultTestAllowMissingSensors":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultAllowMissingSensors, setting.Value);
|
||||
break;
|
||||
case "DefaultTestExcitationWarmupMS":
|
||||
double seconds = Convert.ToDouble(setting.Value) / 1000;
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultTestExcitationWarmupSeconds, seconds.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case "DefaultTestExcitationTOMWarmupDelayMS":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultTestExcitationTOMWarmupDelayMS, setting.Value);
|
||||
break;
|
||||
case "DefaultTestExcitationIEPEWarmupDelayMS":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultTestExcitationIEPEWarmupDelayMS, setting.Value);
|
||||
break;
|
||||
case "DefaultTestRunPostTestDiagnostics":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultRunPostTestDiagnostics, setting.Value);
|
||||
break;
|
||||
case "DefaultTriggerCheckStep":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultTriggerCheckStep, setting.Value);
|
||||
break;
|
||||
case "DefaultTestArmCheckListStep":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultArmCheckListStep, setting.Value);
|
||||
break;
|
||||
case "DefaultTestCheckListInputVoltageCheck":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultCheckListInputVoltageCheck, setting.Value);
|
||||
break;
|
||||
case "DefaultTestCheckListBatteryVoltageCheck":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultCheckListBatteryVoltageCheck, setting.Value);
|
||||
break;
|
||||
case "DefaultTestCheckListSquibResistanceCheck":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultCheckListSquibResistanceCheck, setting.Value);
|
||||
break;
|
||||
case "DefaultTestCheckListSensorIDCheck":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultCheckListSensorIdCheck, setting.Value);
|
||||
break;
|
||||
case "DefaultTestCheckListTriggerStartCheck":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultCheckListTriggerStartCheck, setting.Value);
|
||||
break;
|
||||
case "DefaultTestCheckListMustPass":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultCheckListMustPass, setting.Value);
|
||||
break;
|
||||
case "DefaultTestAllowSensorIdToBlankChannel":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultRequireSensorIdFound, setting.Value);
|
||||
break;
|
||||
case "DefaultTestViewRealtime":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultViewRealtime, setting.Value);
|
||||
break;
|
||||
case "DefaultTestExport":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultExport, setting.Value);
|
||||
break;
|
||||
case "DefaultTestArmChecklistRequireUserConfirmationOnErrors":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.DefaultRequireUserConfirmationOnErrors, setting.Value);
|
||||
break;
|
||||
case "DefaultTestExportFormat":
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.csvunfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportCSVUnfiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.diademadc, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportDiademADC, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.isounfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportISOUnFiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.somatunfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportSomatUnfiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.tdmsadc, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportTDMSADC, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.toyotaunfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportToyotaUnfiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.tsvunfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportTSVUnfiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.csvfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportCSVFiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.isofiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportISOFiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.somatfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportSomatFiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.tdasadc, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportTDASADC, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.toyotafiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportToyotaFiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.tsvfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportTSVFiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.rdfadc, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportRDFADC, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.ChryslerDDAS, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportChryslerDDAS, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.HDFUnfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportHDFUnfiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.HDFFiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportHDFFiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.HDFMV, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportHDFMV, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.HDFADC, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportHDFADC, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.xlsxfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportXLSXFiltered, true.ToString());
|
||||
}
|
||||
if (ExportDesired(SupportedExportFormatBitFlags.xlsxunfiltered, Convert.ToInt32(setting.Value)))
|
||||
{
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.ExportXLSXUnfiltered, true.ToString());
|
||||
}
|
||||
break;
|
||||
case "DefaultTestQuitTestWithoutWarning":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.SupressQuitTestWarning, setting.Value);
|
||||
break;
|
||||
case "DefaultTestSuppressNotAllChannelsViewedWarningRealTime":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.SuppressViewAllRealtime, setting.Value);
|
||||
break;
|
||||
case "DefaultTestSuppressNotAllChannelsViewedWarningViewer":
|
||||
StoreInDb((int)PropertyEnums.PropertyIds.SupressViewAllViewer, setting.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public enum SupportedExportFormatBitFlags
|
||||
{
|
||||
none = 0x0,
|
||||
csvunfiltered = 0x1,
|
||||
diademadc = 0x2,
|
||||
isounfiltered = 0x4,
|
||||
somatunfiltered = 0x8,
|
||||
tdmsadc = 0x10,
|
||||
toyotaunfiltered = 0x20,
|
||||
tsvunfiltered = 0x40,
|
||||
csvfiltered = 0x80,
|
||||
//diademfiltered = 0x100, //unused & available
|
||||
isofiltered = 0x200,
|
||||
somatfiltered = 0x400,
|
||||
tdasadc = 0x800,
|
||||
toyotafiltered = 0x1000,
|
||||
tsvfiltered = 0x2000,
|
||||
rdfadc = 0x4000,
|
||||
ChryslerDDAS = 0x8000,
|
||||
HDFUnfiltered = 0x10000,
|
||||
HDFFiltered = 0x20000,
|
||||
HDFMV = 0x40000,
|
||||
HDFADC = 0x80000,
|
||||
xlsxfiltered = 0x100000,
|
||||
xlsxunfiltered = 0x200000
|
||||
}
|
||||
public bool ExportDesired(SupportedExportFormatBitFlags exportType, int settingValue)
|
||||
{
|
||||
bool desired = false;
|
||||
desired = ((SupportedExportFormatBitFlags)settingValue & exportType) == exportType;
|
||||
return desired;
|
||||
}
|
||||
|
||||
private void StoreInDb(int propertyId, string settingValue)
|
||||
{
|
||||
var userIds = GetAllUserIds();
|
||||
|
||||
foreach (var userId in userIds)
|
||||
{
|
||||
using (var cmd = DatabaseImport.DbOperations.GetSQLCommand(true))
|
||||
{
|
||||
try
|
||||
{
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.CommandText = "sp_UserPropertiesUpdateInsert";
|
||||
|
||||
cmd.Parameters.Add(new SqlParameter("@UserId", SqlDbType.Int) { Value = userId });
|
||||
cmd.Parameters.Add(new SqlParameter("@PropertyId", SqlDbType.Int) { Value = propertyId });
|
||||
cmd.Parameters.Add(new SqlParameter("@PropertyValue", SqlDbType.NVarChar) { Value = settingValue });
|
||||
var errorNumberParam =
|
||||
new SqlParameter("@errorNumber", SqlDbType.Int) { Direction = ParameterDirection.Output };
|
||||
cmd.Parameters.Add(errorNumberParam);
|
||||
var errorMessageParam =
|
||||
new SqlParameter("@errorMessage", SqlDbType.NVarChar, 250)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
cmd.Parameters.Add(errorMessageParam);
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
if (int.Parse(errorNumberParam.Value.ToString()) != 0)
|
||||
{
|
||||
//errorMessageParam.Value
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
cmd.Connection.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<int> GetAllUserIds()
|
||||
{
|
||||
var userIdList = new List<int>();
|
||||
try
|
||||
{
|
||||
using (var cmd = DatabaseImport.DbOperations.GetSQLCommand(true))
|
||||
{
|
||||
try
|
||||
{
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_UserGetIdsAll.ToString(); //Only used in DataPROPre20.mdf, not DataPRO.mdf
|
||||
|
||||
using (var ds = DatabaseImport.DbOperations.Connection.QueryDataSet(cmd))
|
||||
{
|
||||
if (ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
|
||||
{
|
||||
foreach (DataRow dr in ds.Tables[0].Rows)
|
||||
{
|
||||
userIdList.Add(Convert.ToInt32(dr[DbOperations.Users.UserFields.ID.ToString()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
cmd.Connection.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//APILogger.Log("problem getting user", ex);
|
||||
}
|
||||
return userIdList;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user