mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
PowerToys Awake - Improved Logging/Minor Bug Fixes (#15875)
* Some code cleanup * Making sure that the native wrapper lives in Awake.Core * Adding power state logging, as well as termination entries. * Better logging. * Typos and logging improvements * Remove dependency
This commit is contained in:
@@ -7,30 +7,12 @@ using System.IO;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Awake.Core.Models;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
namespace Awake.Core
|
namespace Awake.Core
|
||||||
{
|
{
|
||||||
[Flags]
|
|
||||||
public enum EXECUTION_STATE : uint
|
|
||||||
{
|
|
||||||
ES_AWAYMODE_REQUIRED = 0x00000040,
|
|
||||||
ES_CONTINUOUS = 0x80000000,
|
|
||||||
ES_DISPLAY_REQUIRED = 0x00000002,
|
|
||||||
ES_SYSTEM_REQUIRED = 0x00000001,
|
|
||||||
}
|
|
||||||
|
|
||||||
// See: https://docs.microsoft.com/windows/console/handlerroutine
|
|
||||||
public enum ControlType
|
|
||||||
{
|
|
||||||
CTRL_C_EVENT = 0,
|
|
||||||
CTRL_BREAK_EVENT = 1,
|
|
||||||
CTRL_CLOSE_EVENT = 2,
|
|
||||||
CTRL_LOGOFF_EVENT = 5,
|
|
||||||
CTRL_SHUTDOWN_EVENT = 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
public delegate bool ConsoleEventHandler(ControlType ctrlType);
|
public delegate bool ConsoleEventHandler(ControlType ctrlType);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -85,7 +67,7 @@ namespace Awake.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">Single or multiple EXECUTION_STATE entries.</param>
|
/// <param name="state">Single or multiple EXECUTION_STATE entries.</param>
|
||||||
/// <returns>true if successful, false if failed</returns>
|
/// <returns>true if successful, false if failed</returns>
|
||||||
private static bool SetAwakeState(EXECUTION_STATE state)
|
private static bool SetAwakeState(ExecutionState state)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -168,11 +150,11 @@ namespace Awake.Core
|
|||||||
bool success;
|
bool success;
|
||||||
if (keepDisplayOn)
|
if (keepDisplayOn)
|
||||||
{
|
{
|
||||||
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -209,11 +191,11 @@ namespace Awake.Core
|
|||||||
{
|
{
|
||||||
if (keepDisplayOn)
|
if (keepDisplayOn)
|
||||||
{
|
{
|
||||||
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
|
|||||||
12
src/modules/awake/Awake/Core/Models/BatteryReportingScale.cs
Normal file
12
src/modules/awake/Awake/Core/Models/BatteryReportingScale.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace Awake.Core.Models
|
||||||
|
{
|
||||||
|
public struct BatteryReportingScale
|
||||||
|
{
|
||||||
|
public uint Granularity;
|
||||||
|
public uint Capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/modules/awake/Awake/Core/Models/ControlType.cs
Normal file
16
src/modules/awake/Awake/Core/Models/ControlType.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace Awake.Core.Models
|
||||||
|
{
|
||||||
|
// See: https://docs.microsoft.com/windows/console/handlerroutine
|
||||||
|
public enum ControlType
|
||||||
|
{
|
||||||
|
CTRL_C_EVENT = 0,
|
||||||
|
CTRL_BREAK_EVENT = 1,
|
||||||
|
CTRL_CLOSE_EVENT = 2,
|
||||||
|
CTRL_LOGOFF_EVENT = 5,
|
||||||
|
CTRL_SHUTDOWN_EVENT = 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/modules/awake/Awake/Core/Models/ExecutionState.cs
Normal file
17
src/modules/awake/Awake/Core/Models/ExecutionState.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Awake.Core.Models
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum ExecutionState : uint
|
||||||
|
{
|
||||||
|
ES_AWAYMODE_REQUIRED = 0x00000040,
|
||||||
|
ES_CONTINUOUS = 0x80000000,
|
||||||
|
ES_DISPLAY_REQUIRED = 0x00000002,
|
||||||
|
ES_SYSTEM_REQUIRED = 0x00000001,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Awake.Core.Models
|
||||||
|
{
|
||||||
|
public struct SystemPowerCapabilities
|
||||||
|
{
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool PowerButtonPresent;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool SleepButtonPresent;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool LidPresent;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool SystemS1;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool SystemS2;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool SystemS3;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool SystemS4;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool SystemS5;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool HiberFilePresent;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool FullWake;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool VideoDimPresent;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool ApmPresent;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool UpsPresent;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool ThermalControl;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool ProcessorThrottle;
|
||||||
|
public byte ProcessorMinThrottle;
|
||||||
|
public byte ProcessorMaxThrottle;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool FastSystemS4;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool Hiberboot;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool WakeAlarmPresent;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool AoAc;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool DiskSpinDown;
|
||||||
|
public byte HiberFileType;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool AoAcConnectivitySupported;
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||||
|
private readonly byte[] spare3;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool SystemBatteriesPresent;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool BatteriesAreShortTerm;
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||||
|
public BatteryReportingScale[] BatteryScale;
|
||||||
|
public SystemPowerState AcOnLineWake;
|
||||||
|
public SystemPowerState SoftLidWake;
|
||||||
|
public SystemPowerState RtcWake;
|
||||||
|
public SystemPowerState MinDeviceWakeState;
|
||||||
|
public SystemPowerState DefaultLowLatencyWake;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/modules/awake/Awake/Core/Models/SystemPowerState.cs
Normal file
20
src/modules/awake/Awake/Core/Models/SystemPowerState.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace Awake.Core.Models
|
||||||
|
{
|
||||||
|
// Maps to the OS power state.
|
||||||
|
// See documentation: https://docs.microsoft.com/windows/win32/power/system-power-states
|
||||||
|
public enum SystemPowerState
|
||||||
|
{
|
||||||
|
PowerSystemUnspecified = 0,
|
||||||
|
PowerSystemWorking = 1,
|
||||||
|
PowerSystemSleeping1 = 2,
|
||||||
|
PowerSystemSleeping2 = 3,
|
||||||
|
PowerSystemSleeping3 = 4,
|
||||||
|
PowerSystemHibernate = 5,
|
||||||
|
PowerSystemShutdown = 6,
|
||||||
|
PowerSystemMaximum = 7,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,16 +5,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Awake.Core.Models;
|
||||||
|
|
||||||
namespace Awake.Core
|
namespace Awake.Core
|
||||||
{
|
{
|
||||||
internal static class NativeMethods
|
internal static class NativeMethods
|
||||||
{
|
{
|
||||||
|
[DllImport("PowrProf.dll", SetLastError = true)]
|
||||||
|
internal static extern bool GetPwrCapabilities(out SystemPowerCapabilities lpSystemPowerCapabilities);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
internal static extern bool SetConsoleCtrlHandler(ConsoleEventHandler handler, bool add);
|
internal static extern bool SetConsoleCtrlHandler(ConsoleEventHandler handler, bool add);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||||
internal static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
|
internal static extern ExecutionState SetThreadExecutionState(ExecutionState esFlags);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
|
||||||
<variable name="awakeversion" value="0.0.1" />
|
<variable name="buildid" value="ARBITER_01312022" />
|
||||||
|
|
||||||
<targets async="true">
|
<targets async="true">
|
||||||
<target name="logfile"
|
<target name="logfile"
|
||||||
xsi:type="File"
|
xsi:type="File"
|
||||||
fileName="${specialfolder:folder=LocalApplicationData}/Microsoft/PowerToys/Awake/Logs/${var:awakeversion}/log_${date:format=yyyy-MM-dd_HH}_DBG.txt"
|
fileName="${specialfolder:folder=LocalApplicationData}/Microsoft/PowerToys/Awake/Logs/${var:awakeversion}/applog_${date:format=yyyy-MM-dd_HH}_${var:buildid}.txt"
|
||||||
layout="[${longdate} ${level:uppercase=true} ${logger}] ${message}"
|
layout="[${longdate} ${level:uppercase=true} ${logger}] ${message}"
|
||||||
archiveEvery="Day"
|
archiveEvery="Day"
|
||||||
archiveNumbering="Rolling"
|
archiveNumbering="Rolling"
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ using System.Linq;
|
|||||||
using System.Reactive.Concurrency;
|
using System.Reactive.Concurrency;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Windows;
|
|
||||||
using Awake.Core;
|
using Awake.Core;
|
||||||
|
using Awake.Core.Models;
|
||||||
using interop;
|
using interop;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.PowerToys.Settings.UI.Library;
|
using Microsoft.PowerToys.Settings.UI.Library;
|
||||||
@@ -27,6 +28,14 @@ namespace Awake
|
|||||||
{
|
{
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
|
// PowerToys Awake build codename. Used for exact logging
|
||||||
|
// that does not map to PowerToys broad versioning to pinpoint
|
||||||
|
// internal issues easier.
|
||||||
|
// Format of the build ID is: CODENAME_MMDDYYYY, where MMDDYYYY
|
||||||
|
// is representative of the date when the last change was made before
|
||||||
|
// the pull request is issued.
|
||||||
|
private static readonly string BuildId = "ARBITER_01312022";
|
||||||
|
|
||||||
private static Mutex? _mutex = null;
|
private static Mutex? _mutex = null;
|
||||||
private static FileSystemWatcher? _watcher = null;
|
private static FileSystemWatcher? _watcher = null;
|
||||||
private static SettingsUtils? _settingsUtils = null;
|
private static SettingsUtils? _settingsUtils = null;
|
||||||
@@ -37,6 +46,7 @@ namespace Awake
|
|||||||
|
|
||||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||||
private static ConsoleEventHandler _handler;
|
private static ConsoleEventHandler _handler;
|
||||||
|
private static SystemPowerCapabilities _powerCapabilities;
|
||||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||||
|
|
||||||
private static ManualResetEvent _exitSignal = new ManualResetEvent(false);
|
private static ManualResetEvent _exitSignal = new ManualResetEvent(false);
|
||||||
@@ -56,17 +66,23 @@ namespace Awake
|
|||||||
|
|
||||||
_settingsUtils = new SettingsUtils();
|
_settingsUtils = new SettingsUtils();
|
||||||
|
|
||||||
_log.Info("Launching PowerToys Awake...");
|
_log.Info($"Launching {InternalConstants.AppName}...");
|
||||||
_log.Info(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
|
_log.Info(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
|
||||||
|
_log.Info($"Build: {BuildId}");
|
||||||
_log.Info($"OS: {Environment.OSVersion}");
|
_log.Info($"OS: {Environment.OSVersion}");
|
||||||
_log.Info($"OS Build: {APIHelper.GetOperatingSystemBuild()}");
|
_log.Info($"OS Build: {APIHelper.GetOperatingSystemBuild()}");
|
||||||
|
|
||||||
|
// To make it easier to diagnose future issues, let's get the
|
||||||
|
// system power capabilities and aggregate them in the log.
|
||||||
|
NativeMethods.GetPwrCapabilities(out _powerCapabilities);
|
||||||
|
_log.Info(JsonSerializer.Serialize(_powerCapabilities));
|
||||||
|
|
||||||
_log.Info("Parsing parameters...");
|
_log.Info("Parsing parameters...");
|
||||||
|
|
||||||
Option<bool>? configOption = new Option<bool>(
|
Option<bool>? configOption = new (
|
||||||
aliases: new[] { "--use-pt-config", "-c" },
|
aliases: new[] { "--use-pt-config", "-c" },
|
||||||
getDefaultValue: () => false,
|
getDefaultValue: () => false,
|
||||||
description: "Specifies whether PowerToys Awake will be using the PowerToys configuration file for managing the state.")
|
description: $"Specifies whether {InternalConstants.AppName} will be using the PowerToys configuration file for managing the state.")
|
||||||
{
|
{
|
||||||
Argument = new Argument<bool>(() => false)
|
Argument = new Argument<bool>(() => false)
|
||||||
{
|
{
|
||||||
@@ -76,7 +92,7 @@ namespace Awake
|
|||||||
|
|
||||||
configOption.Required = false;
|
configOption.Required = false;
|
||||||
|
|
||||||
Option<bool>? displayOption = new Option<bool>(
|
Option<bool>? displayOption = new (
|
||||||
aliases: new[] { "--display-on", "-d" },
|
aliases: new[] { "--display-on", "-d" },
|
||||||
getDefaultValue: () => true,
|
getDefaultValue: () => true,
|
||||||
description: "Determines whether the display should be kept awake.")
|
description: "Determines whether the display should be kept awake.")
|
||||||
@@ -89,7 +105,7 @@ namespace Awake
|
|||||||
|
|
||||||
displayOption.Required = false;
|
displayOption.Required = false;
|
||||||
|
|
||||||
Option<uint>? timeOption = new Option<uint>(
|
Option<uint>? timeOption = new (
|
||||||
aliases: new[] { "--time-limit", "-t" },
|
aliases: new[] { "--time-limit", "-t" },
|
||||||
getDefaultValue: () => 0,
|
getDefaultValue: () => 0,
|
||||||
description: "Determines the interval, in seconds, during which the computer is kept awake.")
|
description: "Determines the interval, in seconds, during which the computer is kept awake.")
|
||||||
@@ -102,10 +118,10 @@ namespace Awake
|
|||||||
|
|
||||||
timeOption.Required = false;
|
timeOption.Required = false;
|
||||||
|
|
||||||
Option<int>? pidOption = new Option<int>(
|
Option<int>? pidOption = new (
|
||||||
aliases: new[] { "--pid", "-p" },
|
aliases: new[] { "--pid", "-p" },
|
||||||
getDefaultValue: () => 0,
|
getDefaultValue: () => 0,
|
||||||
description: "Bind the execution of PowerToys Awake to another process.")
|
description: $"Bind the execution of {InternalConstants.AppName} to another process.")
|
||||||
{
|
{
|
||||||
Argument = new Argument<int>(() => 0)
|
Argument = new Argument<int>(() => 0)
|
||||||
{
|
{
|
||||||
@@ -135,9 +151,7 @@ namespace Awake
|
|||||||
private static bool ExitHandler(ControlType ctrlType)
|
private static bool ExitHandler(ControlType ctrlType)
|
||||||
{
|
{
|
||||||
_log.Info($"Exited through handler with control type: {ctrlType}");
|
_log.Info($"Exited through handler with control type: {ctrlType}");
|
||||||
|
|
||||||
Exit("Exiting from the internal termination handler.", Environment.ExitCode);
|
Exit("Exiting from the internal termination handler.", Environment.ExitCode);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +265,8 @@ namespace Awake
|
|||||||
{
|
{
|
||||||
RunnerHelper.WaitForPowerToysRunner(pid, () =>
|
RunnerHelper.WaitForPowerToysRunner(pid, () =>
|
||||||
{
|
{
|
||||||
Exit("Terminating from PowerToys binding hook.", 0, true);
|
_log.Info($"Triggered PID-based exit handler for PID {pid}.");
|
||||||
|
Exit("Terminating from process binding hook.", 0, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user