using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using IPClab3.Models; using IPClab3.Services; namespace IPClab3.ViewModels; public partial class MainWindowViewModel(ProcessHackerService processHackerService, FilePickerService filePickerService) : ObservableObject { private ProcessHackerService _processHackerService = processHackerService; private FilePickerService _filePickerService = filePickerService; private CancellationTokenSource _cts; private List _allProcesses; [ObservableProperty] private ObservableCollection _suggestions = []; private string _searchText = string.Empty; public string SearchText { get => _searchText; set { if (SetProperty(ref _searchText, value)) { _ = UpdateSuggestionsAsync(value); } } } [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(AttachProcessCommand))] public partial ProcessItem SelectedProcess { get; set; } [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ReadMemoryCommand))] [NotifyCanExecuteChangedFor(nameof(WriteMemoryCommand))] public partial int StartedPid { get; set; } [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ReadMemoryCommand))] [NotifyCanExecuteChangedFor(nameof(WriteMemoryCommand))] public partial IntPtr MemoryAddress { get; set; } private async Task UpdateSuggestionsAsync(string query) { // 1. Cancel the previous running search task immediately _cts?.Cancel(); _cts?.Dispose(); _cts = new CancellationTokenSource(); var token = _cts.Token; if (string.IsNullOrWhiteSpace(query)) { Suggestions.Clear(); return; } try { // Optional: Debounce briefly (e.g., wait 150ms) to ensure the user // stopped typing before blasting the OS with process queries. await Task.Delay(150, token); // 2. Offload the heavy blocking OS operation to a pool thread List filteredResults = await Task.Run(() => { // Check token before starting the heavy lifting token.ThrowIfCancellationRequested(); // Hit the live OS snapshot var liveSnapshot = Process.GetProcesses(); token.ThrowIfCancellationRequested(); // Filter on the background thread return liveSnapshot .Where(p => p.ProcessName.Contains(query, StringComparison.OrdinalIgnoreCase)) .Select(p => new ProcessItem { Pid = p.Id, ProcessName = p.ProcessName }) .OrderBy(p => p.ProcessName) .ToList(); }, token); // 3. Check token one final time before updating the UI thread token.ThrowIfCancellationRequested(); // Safely swap out items on the Main/UI thread Suggestions.Clear(); foreach (var process in filteredResults) { Suggestions.Add(process); } } catch (OperationCanceledException) { // Ignored: Expected when the user types quickly and cancels old operations } catch (Exception ex) { Debug.WriteLine($"Error fetching processes: {ex.Message}"); } } [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(StartProcessCommand))] public partial string FilePath { get; set; } public async Task PickFile(IntPtr hwnd) { FilePath = await _filePickerService.RequestFileOpener(hwnd); } private bool CanStartProcess() { return File.Exists(FilePath); } [RelayCommand(CanExecute = nameof(CanStartProcess))] public void StartProcess() { StartedPid = _processHackerService.StartProcess(FilePath); } private bool CanAttachProcess() { if (SelectedProcess == null) return false; try { var process = Process.GetProcessById(SelectedProcess.Pid); return true; } catch (ArgumentException) { return false; } } [RelayCommand(CanExecute = nameof(CanAttachProcess))] public void AttachProcess() { StartedPid = _processHackerService.AttachProcess(SelectedProcess.Pid); } [ObservableProperty] public partial string ReadData { get; set; } = "Прочитать из памяти"; [ObservableProperty] public partial int ReadLength { get; set; } [ObservableProperty] public partial string WriteData { get; set; } = "Записать в память"; private bool CanWorkWithMemory() { return StartedPid != 0 && MemoryAddress != IntPtr.Zero; } [RelayCommand(CanExecute = nameof(CanWorkWithMemory))] public void ReadMemory() { if (_processHackerService.ReadMemory(MemoryAddress, ReadLength, out var bytes)) ReadData = Encoding.UTF8.GetString(bytes); } [RelayCommand(CanExecute = nameof(CanWorkWithMemory))] public void WriteMemory() { if (_processHackerService.WriteMemory(MemoryAddress, WriteData)) { IsSuccessOpen = true; Task.Run(() => { Task.Delay(10000); IsSuccessOpen = false; }); } } [ObservableProperty] public partial bool IsSuccessOpen { get; set; } }