using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Avalonia.Threading; using FileTime.App.Core.Clipboard; using FileTime.App.Core.Command; using FileTime.Avalonia.Application; using FileTime.Avalonia.IconProviders; using FileTime.Avalonia.Misc; using FileTime.Avalonia.ViewModels; using FileTime.Core.Command; using FileTime.Core.Components; using FileTime.Core.Interactions; using FileTime.Core.Models; using FileTime.Core.Providers; using FileTime.Core.Timeline; using FileTime.Providers.Local; namespace FileTime.Avalonia.Services { public class CommandHandlerService { private bool _addCommandToNextBatch; private readonly AppState _appState; private readonly LocalContentProvider _localContentProvider; private readonly ItemNameConverterService _itemNameConverterService; private readonly DialogService _dialogService; private readonly IClipboard _clipboard; private readonly TimeRunner _timeRunner; private readonly IIconProvider _iconProvider; private readonly IEnumerable _contentProviders; private readonly Dictionary> _commandHandlers; public CommandHandlerService( AppState appState, LocalContentProvider localContentProvider, ItemNameConverterService itemNameConverterService, DialogService dialogService, IClipboard clipboard, TimeRunner timeRunner, IIconProvider iconProvider, IEnumerable contentProviders) { _appState = appState; _localContentProvider = localContentProvider; _itemNameConverterService = itemNameConverterService; _dialogService = dialogService; _clipboard = clipboard; _timeRunner = timeRunner; _iconProvider = iconProvider; _contentProviders = contentProviders; _commandHandlers = new Dictionary> { {Commands.AutoRefresh, ToggleAutoRefresh}, {Commands.ChangeTimelineMode, ChangeTimelineMode}, {Commands.CloseTab, CloseTab}, {Commands.Copy, Copy}, {Commands.CopyPath, CopyPath}, {Commands.CreateContainer, CreateContainer}, {Commands.CreateElement, CreateElement}, {Commands.Cut, Cut}, {Commands.EnterRapidTravel, EnterRapidTravelMode}, {Commands.GoToHome, GotToHome}, {Commands.GoToPath, GoToContainer}, {Commands.GoToProvider, GotToProvider}, {Commands.GoToRoot, GotToRoot}, {Commands.GoUp, GoUp}, {Commands.HardDelete, HardDelete}, {Commands.Mark, MarkCurrentItem}, {Commands.MoveCursorDown, MoveCursorDown}, {Commands.MoveCursorDownPage, MoveCursorDownPage}, {Commands.MoveCursorUp, MoveCursorUp}, {Commands.MoveCursorUpPage, MoveCursorUpPage}, {Commands.MoveToFirst, MoveToFirst}, {Commands.MoveToLast, MoveToLast}, {Commands.NextTimelineBlock, SelectNextTimelineBlock}, {Commands.NextTimelineCommand, SelectNextTimelineCommand}, {Commands.Open, OpenContainer}, {Commands.OpenInFileBrowser, OpenInDefaultFileExplorer}, {Commands.OpenOrRun, OpenOrRun}, {Commands.PasteMerge, PasteMerge}, {Commands.PasteOverwrite, PasteOverwrite}, {Commands.PasteSkip, PasteSkip}, {Commands.PreviousTimelineBlock, SelectPreviousTimelineBlock}, {Commands.PreviousTimelineCommand, SelectPreviousTimelineCommand}, {Commands.Refresh, RefreshCurrentLocation}, {Commands.Rename, Rename}, {Commands.RunCommand, RunCommandInContainer}, {Commands.ShowAllShotcut, ShowAllShortcut}, {Commands.SoftDelete, SoftDelete}, {Commands.SwitchToLastTab, async() => await SwitchToTab(-1)}, {Commands.SwitchToTab1, async() => await SwitchToTab(1)}, {Commands.SwitchToTab2, async() => await SwitchToTab(2)}, {Commands.SwitchToTab3, async() => await SwitchToTab(3)}, {Commands.SwitchToTab4, async() => await SwitchToTab(4)}, {Commands.SwitchToTab5, async() => await SwitchToTab(5)}, {Commands.SwitchToTab6, async() => await SwitchToTab(6)}, {Commands.SwitchToTab7, async() => await SwitchToTab(7)}, {Commands.SwitchToTab8, async() => await SwitchToTab(8)}, {Commands.TimelinePause, PauseTimeline}, {Commands.TimelineRefresh, RefreshTimeline}, {Commands.TimelineStart, ContinueTimeline}, {Commands.ToggleAdvancedIcons, ToggleAdvancedIcons}, {Commands.ToggleHidden, ToggleHidden}, }; } public async Task HandleCommandAsync(Commands command) => await _commandHandlers[command].Invoke(); private async Task OpenContainer() { _appState.RapidTravelText = ""; await _appState.SelectedTab.Open(); } public async Task OpenContainer(IContainer container) { _appState.RapidTravelText = ""; await _appState.SelectedTab.OpenContainer(container); } private async Task OpenOrRun() { if (_appState.SelectedTab.SelectedItem is ContainerViewModel) { await OpenContainer(); } else if (_appState.SelectedTab.SelectedItem is ElementViewModel elementViewModel && elementViewModel.Element is LocalFile localFile) { Process.Start(new ProcessStartInfo(localFile.File.FullName) { UseShellExecute = true }); if (_appState.ViewMode == ViewMode.RapidTravel) { await _appState.ExitRapidTravelMode(); } } } private async Task GoUp() { await _appState.SelectedTab.GoUp(); } private async Task MoveCursorUp() { await _appState.SelectedTab.MoveCursorUp(); } private async Task MoveCursorDown() { await _appState.SelectedTab.MoveCursorDown(); } private async Task MoveCursorUpPage() { await _appState.SelectedTab.MoveCursorUpPage(); } private async Task MoveCursorDownPage() { await _appState.SelectedTab.MoveCursorDownPage(); } private async Task MoveToFirst() { await _appState.SelectedTab.MoveCursorToFirst(); } private async Task MoveToLast() { await _appState.SelectedTab.MoveCursorToLast(); } private async Task GotToProvider() { await _appState.SelectedTab.GotToProvider(); } private async Task GotToRoot() { await _appState.SelectedTab.GotToRoot(); } private async Task GotToHome() { await _appState.SelectedTab.GotToHome(); } private Task EnterRapidTravelMode() { _appState.ViewMode = ViewMode.RapidTravel; _appState.PreviousKeys.Clear(); _appState.PossibleCommands = new(); return Task.CompletedTask; } private async Task SwitchToTab(int number) { var tabContainer = _appState.Tabs.FirstOrDefault(t => t.TabNumber == number); if (number == -1) { var greatestNumber = _appState.Tabs.Max(t => t.TabNumber); tabContainer = _appState.Tabs.FirstOrDefault(t => t.TabNumber == greatestNumber); } else if (tabContainer == null) { var newContainer = await _appState.SelectedTab.CurrentLocation.Container.Clone(); var newTab = new Tab(); await newTab.Init(newContainer); tabContainer = new TabContainer(newTab, _localContentProvider, _itemNameConverterService); await tabContainer.Init(number); var i = 0; for (i = 0; i < _appState.Tabs.Count; i++) { if (_appState.Tabs[i].TabNumber > number) break; } _appState.Tabs.Insert(i, tabContainer); } if (_appState.ViewMode == ViewMode.RapidTravel) { await _appState.ExitRapidTravelMode(); } _appState.SelectedTab = tabContainer; } private async Task CloseTab() { var tabs = _appState.Tabs; if (tabs.Count > 1) { var currentTab = tabs.FirstOrDefault(t => t == _appState.SelectedTab); if (currentTab != null) { tabs.Remove(currentTab); var tabNumber = tabs[0].TabNumber; for (var i = 0; i < tabs.Count; i++) { tabNumber = tabs[i].TabNumber; if (tabs[i].TabNumber > currentTab.TabNumber) break; } await SwitchToTab(tabNumber); } } } private Task CreateContainer() { var handler = async (List inputs) => { var container = _appState.SelectedTab.CurrentLocation.Container; var createContainerCommand = new CreateContainerCommand(new AbsolutePath(container), inputs[0].Value); await AddCommand(createContainerCommand); }; _dialogService.ReadInputs(new List() { new InputElement("Container name", InputType.Text) }, handler); return Task.CompletedTask; } private Task CreateElement() { var handler = async (List inputs) => { var container = _appState.SelectedTab.CurrentLocation.Container; var createElementCommand = new CreateElementCommand(new AbsolutePath(container), inputs[0].Value); await AddCommand(createElementCommand); }; _dialogService.ReadInputs(new List() { new InputElement("Element name", InputType.Text) }, handler); return Task.CompletedTask; } private async Task MarkCurrentItem() { await _appState.SelectedTab.MarkCurrentItem(); } private async Task Copy() { _clipboard.Clear(); _clipboard.SetCommand(); var currentSelectedItems = await _appState.SelectedTab.TabState.GetCurrentMarkedItems(); if (currentSelectedItems.Count > 0) { foreach (var selectedItem in currentSelectedItems) { _clipboard.AddContent(selectedItem); } await _appState.SelectedTab.TabState.ClearCurrentMarkedItems(); } else { var currentSelectedItem = _appState.SelectedTab.SelectedItem?.Item; if (currentSelectedItem != null) { _clipboard.AddContent(new AbsolutePath(currentSelectedItem)); } } } private Task Cut() { _clipboard.Clear(); _clipboard.SetCommand(); return Task.CompletedTask; } private async Task SoftDelete() => await Delete(false); private async Task HardDelete() => await Delete(true); public async Task Delete(bool hardDelete = false) { IList? itemsToDelete = null; var askForDelete = false; var questionText = ""; var shouldDelete = false; var shouldClearMarkedItems = false; var currentSelectedItems = await _appState.SelectedTab.TabState.GetCurrentMarkedItems(); var currentSelectedItem = _appState.SelectedTab.SelectedItem?.Item; if (currentSelectedItems.Count > 0) { itemsToDelete = new List(currentSelectedItems); shouldClearMarkedItems = true; //FIXME: check 'is Container' if (currentSelectedItems.Count == 1) { if ((await currentSelectedItems[0].Resolve()) is IContainer container && (await container.GetItems())?.Count > 0) { askForDelete = true; questionText = $"The container '{container.Name}' is not empty. Proceed with delete?"; } else { shouldDelete = true; } } else { askForDelete = true; questionText = $"Are you sure you want to delete {itemsToDelete.Count} item?"; } } else if (currentSelectedItem != null) { itemsToDelete = new List() { new AbsolutePath(currentSelectedItem) }; if (currentSelectedItem is IContainer container && (await container.GetItems())?.Count > 0) { askForDelete = true; questionText = $"The container '{container.Name}' is not empty. Proceed with delete?"; } else { shouldDelete = true; } } if (itemsToDelete?.Count > 0) { if (askForDelete) { _dialogService.ShowMessageBox(questionText, HandleDelete); } else if (shouldDelete) { await HandleDelete(); } } async Task HandleDelete() { var deleteCommand = new DeleteCommand { HardDelete = hardDelete }; foreach (var itemToDelete in itemsToDelete!) { deleteCommand.ItemsToDelete.Add(itemToDelete); } await AddCommand(deleteCommand); _clipboard.Clear(); if (shouldClearMarkedItems) { await _appState.SelectedTab.TabState.ClearCurrentMarkedItems(); } } } private async Task PasteMerge() { await Paste(TransportMode.Merge); } private async Task PasteOverwrite() { await Paste(TransportMode.Overwrite); } private async Task PasteSkip() { await Paste(TransportMode.Skip); } private async Task Paste(TransportMode transportMode) { if (_clipboard.CommandType != null) { var command = (ITransportationCommand)Activator.CreateInstance(_clipboard.CommandType!)!; command.TransportMode = transportMode; command.Sources.Clear(); foreach (var item in _clipboard.Content) { command.Sources.Add(item); } var currentLocation = _appState.SelectedTab.CurrentLocation.Container; command.Target = currentLocation is VirtualContainer virtualContainer ? virtualContainer.BaseContainer : currentLocation; await AddCommand(command); _clipboard.Clear(); } } private Task Rename() { var selectedItem = _appState.SelectedTab.SelectedItem?.Item; if (selectedItem != null) { var handler = async (List inputs) => { var renameCommand = new RenameCommand(new AbsolutePath(selectedItem), inputs[0].Value); await AddCommand(renameCommand); }; _dialogService.ReadInputs(new List() { new InputElement("New name", InputType.Text, selectedItem.Name) }, handler); } return Task.CompletedTask; } private async Task RefreshCurrentLocation() { await _appState.SelectedTab.CurrentLocation.Container.RefreshAsync(); await _appState.SelectedTab.UpdateCurrentSelectedItem(); } private Task PauseTimeline() { _timeRunner.EnableRunning = false; return Task.CompletedTask; } private async Task ContinueTimeline() { _timeRunner.EnableRunning = true; await _timeRunner.TryStartCommandRunner(); } private async Task RefreshTimeline() { await _timeRunner.Refresh(); } private Task ChangeTimelineMode() { _addCommandToNextBatch = !_addCommandToNextBatch; _dialogService.ShowToastMessage("Timeline mode: " + (_addCommandToNextBatch ? "Continuous" : "Parallel")); return Task.CompletedTask; } private Task GoToContainer() { var handler = async (List inputs) => { var path = inputs[0].Value; foreach (var contentProvider in _contentProviders) { if (contentProvider.CanHandlePath(path)) { var possibleContainer = await contentProvider.GetByPath(path); if (possibleContainer is IContainer container) { await _appState.SelectedTab.OpenContainer(container); } //TODO: multiple possible content provider handler return; } } }; _dialogService.ReadInputs(new List() { new InputElement("Path", InputType.Text) }, handler); return Task.CompletedTask; } private Task ToggleAdvancedIcons() { _iconProvider.EnableAdvancedIcons = !_iconProvider.EnableAdvancedIcons; _dialogService.ShowToastMessage("Advanced icons are: " + (_iconProvider.EnableAdvancedIcons ? "ON" : "OFF")); return Task.CompletedTask; } private Task ToggleHidden() { throw new NotImplementedException(); } private Task OpenInDefaultFileExplorer() { if (_appState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder) { var path = localFolder.Directory.FullName; if (path != null) { Process.Start("explorer.exe", "\"" + path + "\""); } } return Task.CompletedTask; } private async Task CopyPath() { string? textToCopy = null; if (_appState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder) { textToCopy = localFolder.Directory.FullName; } if (_appState.SelectedTab.CurrentLocation.Container is LocalFile localFile) { textToCopy = localFile.File.FullName; } else if (_appState.SelectedTab.CurrentLocation.Container.FullName is string fullName) { textToCopy = fullName; } if (textToCopy != null && global::Avalonia.Application.Current?.Clipboard is not null) { await global::Avalonia.Application.Current.Clipboard.SetTextAsync(textToCopy); } } private Task ShowAllShortcut() { _appState.IsAllShortcutVisible = true; return Task.CompletedTask; } private Task RunCommandInContainer() { var handler = (List inputs) => { var input = inputs[0].Value; string? path = null; string? arguments = null; if (input.StartsWith("\"")) { var pathEnd = input.IndexOf('\"', 1); path = input.Substring(1, pathEnd); arguments = input.Substring(pathEnd + 1).Trim(); } else { var inputParts = input.Split(' '); path = inputParts[0]; arguments = inputParts.Length > 1 ? string.Join(' ', inputParts[1..]).Trim() : null; } if (!string.IsNullOrWhiteSpace(path)) { using var process = new Process(); process.StartInfo.FileName = path; if (!string.IsNullOrWhiteSpace(arguments)) { process.StartInfo.Arguments = arguments; } if (_appState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder) { process.StartInfo.WorkingDirectory = localFolder.Directory.FullName; } process.Start(); } return Task.CompletedTask; }; _dialogService.ReadInputs(new List() { new InputElement("Command", InputType.Text) }, handler); return Task.CompletedTask; } private Task SelectPreviousTimelineBlock() { var currentSelected = GetSelectedTimelineCommandOrSelectFirst(); if (currentSelected == null) return Task.CompletedTask; ParallelCommandsViewModel? newBlockVM = null; ParallelCommandsViewModel? previousBlockVM = null; foreach (var timelineBlock in _appState.TimelineCommands) { foreach (var command in timelineBlock.ParallelCommands) { if (command.IsSelected) { newBlockVM = previousBlockVM; break; } } previousBlockVM = timelineBlock; } if (newBlockVM == null) return Task.CompletedTask; foreach (var val in _appState.TimelineCommands.Select(t => t.ParallelCommands.Select((c, i) => (ParalellCommandVM: t, CommandVM: c, Index: i))).SelectMany(t => t)) { val.CommandVM.IsSelected = val.ParalellCommandVM == newBlockVM && val.Index == 0; } return Task.CompletedTask; } private Task SelectNextTimelineCommand() { var currentSelected = GetSelectedTimelineCommandOrSelectFirst(); if (currentSelected == null) return Task.CompletedTask; ParallelCommandViewModel? lastCommand = null; var any = false; foreach (var command in _appState.TimelineCommands.SelectMany(t => t.ParallelCommands)) { var isSelected = lastCommand == currentSelected; command.IsSelected = isSelected; any = any || isSelected; lastCommand = command; } if (!any && lastCommand != null) lastCommand.IsSelected = true; return Task.CompletedTask; } private Task SelectPreviousTimelineCommand() { var currentSelected = GetSelectedTimelineCommandOrSelectFirst(); if (currentSelected == null) return Task.CompletedTask; ParallelCommandViewModel? lastCommand = null; foreach (var command in _appState.TimelineCommands.SelectMany(t => t.ParallelCommands)) { if (lastCommand != null) { lastCommand.IsSelected = command == currentSelected; } lastCommand = command; } if (lastCommand != null) lastCommand.IsSelected = false; return Task.CompletedTask; } private Task SelectNextTimelineBlock() { var currentSelected = GetSelectedTimelineCommandOrSelectFirst(); if (currentSelected == null) return Task.CompletedTask; ParallelCommandsViewModel? newBlockVM = null; var select = false; foreach (var timelineBlock in _appState.TimelineCommands) { if (select) { newBlockVM = timelineBlock; break; } foreach (var command in timelineBlock.ParallelCommands) { if (command.IsSelected) { select = true; break; } } } if (newBlockVM == null) return Task.CompletedTask; foreach (var val in _appState.TimelineCommands.Select(t => t.ParallelCommands.Select((c, i) => (ParalellCommandVM: t, CommandVM: c, Index: i))).SelectMany(t => t)) { val.CommandVM.IsSelected = val.ParalellCommandVM == newBlockVM && val.Index == 0; } return Task.CompletedTask; } private ParallelCommandViewModel? GetSelectedTimelineCommandOrSelectFirst() { var currentSelected = _appState.TimelineCommands.SelectMany(t => t.ParallelCommands).FirstOrDefault(c => c.IsSelected); if (currentSelected != null) return currentSelected; var firstCommand = _appState.TimelineCommands.SelectMany(t => t.ParallelCommands).FirstOrDefault(); if (firstCommand != null) { firstCommand.IsSelected = true; } return null; } private async Task AddCommand(ICommand command) { if (_addCommandToNextBatch) { await _timeRunner.AddCommand(command, toNewBatch: true); } else { ParallelCommandsViewModel? batchToAdd = null; foreach (var val in _appState.TimelineCommands.Select(t => t.ParallelCommands.Select(c => (ParalellCommandVM: t, CommandVM: c))).SelectMany(t => t)) { if (val.CommandVM.IsSelected) { batchToAdd = val.ParalellCommandVM; break; } } if (batchToAdd != null) { await _timeRunner.AddCommand(command, batchToAdd.Id); } else { await _timeRunner.AddCommand(command); } } } private Task ToggleAutoRefresh() { var tab = _appState.SelectedTab.TabState.Tab; tab.AutoRefresh = !tab.AutoRefresh; _dialogService.ShowToastMessage("Auto refresh is: " + (tab.AutoRefresh ? "ON" : "OFF")); return Task.CompletedTask; } } }