Update files

Signed-off-by: Lev Rusanov <30170278+JDM170@users.noreply.github.com>
This commit is contained in:
2025-09-23 09:54:08 +07:00
parent 96eb8c5f51
commit bcc119d4fe
3 changed files with 542 additions and 154 deletions

View File

@@ -48,6 +48,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="KeyListener.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>

339
KeyListener.cs Normal file
View File

@@ -0,0 +1,339 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace EpisodeRenamer
{
/// <summary>
/// Класс для обработки горячих клавиш с использованием Task
/// </summary>
public class HotkeyListener : IDisposable
{
#region Windows API Imports
[DllImport("user32.dll")]
private static extern short GetAsyncKeyState(int vKey);
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
#endregion
#region Key Codes
private static class VirtualKeyCodes
{
public const int VK_CONTROL = 0x11;
public const int VK_ALT = 0x12;
public const int VK_SHIFT = 0x10;
public const int VK_R = 0x52;
public const int VK_ESCAPE = 0x1B;
// Добавьте другие коды клавиш по необходимости
}
#endregion
#region Events and Delegates
/// <summary>
/// Делегат для события нажатия комбинации клавиш
/// </summary>
public delegate void HotkeyEventHandler(string combinationName);
/// <summary>
/// Событие, возникающее при нажатии зарегистрированной комбинации клавиш
/// </summary>
public event HotkeyEventHandler OnHotkeyPressed;
/// <summary>
/// Событие, возникающее при ошибке в работе слушателя
/// </summary>
public event Action<Exception> OnError;
#endregion
#region Private Fields
private readonly List<KeyCombination> _keyCombinations;
private CancellationTokenSource _cancellationTokenSource;
private Task _listenerTask;
private bool _isDisposed;
private readonly object _lockObject = new object();
private bool _isRunning;
#endregion
#region Key Combination Class
/// <summary>
/// Класс для описания комбинации клавиш
/// </summary>
private class KeyCombination
{
public string Name { get; set; }
public int[] KeyCodes { get; set; }
public int CooldownMs { get; set; } = 300;
public DateTime LastTriggered { get; set; } = DateTime.MinValue;
}
#endregion
#region Constructor
/// <summary>
/// Создает новый экземпляр слушателя горячих клавиш
/// </summary>
public HotkeyListener()
{
_keyCombinations = new List<KeyCombination>();
_cancellationTokenSource = new CancellationTokenSource();
// Добавляем комбинацию по умолчанию
RegisterCombination("ControlAltR",
new[] { VirtualKeyCodes.VK_CONTROL, VirtualKeyCodes.VK_ALT, VirtualKeyCodes.VK_R });
}
#endregion
#region Public Methods
/// <summary>
/// Регистрирует новую комбинацию клавиш для отслеживания
/// </summary>
/// <param name="name">Имя комбинации</param>
/// <param name="keyCodes">Коды клавиш (все должны быть нажаты одновременно)</param>
/// <param name="cooldownMs">Время задержки между срабатываниями (мс)</param>
public void RegisterCombination(string name, int[] keyCodes, int cooldownMs = 300)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Имя комбинации не может быть пустым", nameof(name));
if (keyCodes == null || keyCodes.Length == 0)
throw new ArgumentException("Массив кодов клавиш не может быть пустым", nameof(keyCodes));
lock (_lockObject)
{
var existing = _keyCombinations.Find(k => k.Name == name);
if (existing != null)
{
_keyCombinations.Remove(existing);
}
_keyCombinations.Add(new KeyCombination
{
Name = name,
KeyCodes = keyCodes,
CooldownMs = cooldownMs
});
}
}
/// <summary>
/// Удаляет комбинацию клавиш из отслеживания
/// </summary>
/// <param name="name">Имя комбинации</param>
public void UnregisterCombination(string name)
{
lock (_lockObject)
{
var combination = _keyCombinations.Find(k => k.Name == name);
if (combination != null)
{
_keyCombinations.Remove(combination);
}
}
}
/// <summary>
/// Запускает прослушивание горячих клавиш
/// </summary>
public async Task StartListeningAsync()
{
if (_isRunning)
return;
lock (_lockObject)
{
if (_isRunning)
return;
_cancellationTokenSource = new CancellationTokenSource();
_isRunning = true;
}
try
{
// Переводим консоль на передний план для лучшего захвата клавиш
BringConsoleToFront();
_listenerTask = Task.Run(() => ListenForHotkeys(_cancellationTokenSource.Token),
_cancellationTokenSource.Token);
await Task.CompletedTask;
}
catch (Exception ex)
{
OnError?.Invoke(ex);
throw;
}
}
/// <summary>
/// Останавливает прослушивание горячих клавиш
/// </summary>
public async Task StopListeningAsync()
{
if (!_isRunning)
return;
lock (_lockObject)
{
if (!_isRunning)
return;
_isRunning = false;
}
try
{
_cancellationTokenSource?.Cancel();
if (_listenerTask != null && !_listenerTask.IsCompleted)
{
await _listenerTask.ContinueWith(t =>
{
// Игнорируем исключения отмены задачи
if (t.IsFaulted) OnError?.Invoke(t.Exception);
});
}
}
catch (OperationCanceledException)
{
// Ожидаемое исключение при отмене
}
catch (Exception ex)
{
OnError?.Invoke(ex);
throw;
}
}
/// <summary>
/// Получает статус работы слушателя
/// </summary>
public bool IsListening => _isRunning;
/// <summary>
/// Получает список зарегистрированных комбинаций
/// </summary>
public IReadOnlyList<string> RegisteredCombinations
{
get
{
lock (_lockObject)
{
return _keyCombinations.ConvertAll(k => k.Name).AsReadOnly();
}
}
}
#endregion
#region Private Methods
private async Task ListenForHotkeys(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(10, cancellationToken); // Небольшая задержка для снижения нагрузки
lock (_lockObject)
{
foreach (var combination in _keyCombinations)
{
if (IsCombinationPressed(combination.KeyCodes))
{
// Проверяем cooldown
if ((DateTime.Now - combination.LastTriggered).TotalMilliseconds >= combination.CooldownMs)
{
combination.LastTriggered = DateTime.Now;
TriggerHotkeyEvent(combination.Name);
}
}
}
}
}
}
catch (OperationCanceledException)
{
// Ожидаемое исключение при отмене
}
catch (Exception ex)
{
OnError?.Invoke(ex);
}
}
private bool IsCombinationPressed(int[] keyCodes)
{
foreach (var keyCode in keyCodes)
{
if (!IsKeyPressed(keyCode))
return false;
}
return true;
}
private bool IsKeyPressed(int keyCode)
{
return (GetAsyncKeyState(keyCode) & 0x8000) != 0;
}
private void TriggerHotkeyEvent(string combinationName)
{
try
{
OnHotkeyPressed?.Invoke(combinationName);
}
catch (Exception ex)
{
OnError?.Invoke(new InvalidOperationException(
$"Ошибка при обработке события для комбинации {combinationName}", ex));
}
}
private void BringConsoleToFront()
{
try
{
var consoleHandle = GetConsoleWindow();
SetForegroundWindow(consoleHandle);
}
catch (Exception ex)
{
// Не критичная ошибка, просто логируем
OnError?.Invoke(new InvalidOperationException("Не удалось перевести консоль на передний план", ex));
}
}
#endregion
#region IDisposable Implementation
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
StopListeningAsync().GetAwaiter().GetResult();
_cancellationTokenSource?.Dispose();
}
_isDisposed = true;
}
}
~HotkeyListener()
{
Dispose(false);
}
#endregion
}
}

View File

@@ -1,12 +1,15 @@
using System; using Newtonsoft.Json;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Newtonsoft.Json; using System.Threading.Tasks;
class EpisodeRenamer namespace EpisodeRenamer
{ {
class EpisodeRenamer
{
private class PatternConfig private class PatternConfig
{ {
public bool Enabled { get; set; } public bool Enabled { get; set; }
@@ -39,7 +42,42 @@ class EpisodeRenamer
} }
} }
static void Main() static async Task Main(string[] args)
{
// Создаем экземпляр слушателя
using (var hotkeyListener = new HotkeyListener())
{
// Подписываемся на события
hotkeyListener.OnHotkeyPressed += (combination) =>
{
switch (combination)
{
case "ControlAltR":
LoadConfiguration(true);
break;
case "ControlC":
Console.WriteLine("\nВыход из программы.");
Environment.Exit(0);
break;
default:
break;
}
};
hotkeyListener.OnError += (ex) => Console.WriteLine($"Ошибка: {ex.Message}");
hotkeyListener.RegisterCombination("ControlC", new[] { 0x11, 0x43 });
// Запускаем прослушивание
await hotkeyListener.StartListeningAsync();
// Основной цикл программы
await RunMainProgramLoop();
// Останавливаем слушатель (автоматически вызывается в Dispose)
await hotkeyListener.StopListeningAsync();
}
}
static async Task RunMainProgramLoop()
{ {
Console.Title = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyTitleAttribute>().Title; Console.Title = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyTitleAttribute>().Title;
@@ -55,13 +93,14 @@ class EpisodeRenamer
return; return;
} }
Console.CancelKeyPress += (sender, e) => //Console.CancelKeyPress += (sender, e) =>
{ //{
Console.WriteLine("\nВыход из программы."); // Console.WriteLine("\nВыход из программы.");
Environment.Exit(0); // Environment.Exit(0);
}; //};
Console.WriteLine("Чтобы оставить текущую директорию нажмите 'Enter'"); Console.WriteLine("Чтобы оставить текущую директорию нажмите 'Enter'");
Console.WriteLine("Чтобы перезагрузить конфигурацию нажмите 'Ctrl + Alt + R'");
Console.WriteLine("Чтобы выйти нажмите 'Ctrl + C'"); Console.WriteLine("Чтобы выйти нажмите 'Ctrl + C'");
while (true) while (true)
@@ -69,7 +108,8 @@ class EpisodeRenamer
try try
{ {
Console.Write("\nВведите путь до папки с эпизодами: "); Console.Write("\nВведите путь до папки с эпизодами: ");
string input = Console.ReadLine().Trim() ?? string.Empty; string input = Console.ReadLine() ?? string.Empty;
input = input.Trim();
string folder = string.IsNullOrEmpty(input) string folder = string.IsNullOrEmpty(input)
? Directory.GetCurrentDirectory() ? Directory.GetCurrentDirectory()
@@ -91,8 +131,15 @@ class EpisodeRenamer
} }
} }
private static void LoadConfiguration() private static void LoadConfiguration(bool reloadConfiguration = false)
{ {
if (reloadConfiguration)
{
extensions.Clear();
patterns.Clear();
Console.WriteLine("\nЗапрошена перезагрузка конфигурации.");
}
string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json"); string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json");
if (!File.Exists(configPath)) if (!File.Exists(configPath))
@@ -182,4 +229,5 @@ class EpisodeRenamer
return length + index.Value; return length + index.Value;
return index.Value; return index.Value;
} }
}
} }