using MaterialDesignThemes.Wpf; 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.Tasks; using System.Windows.Input; using Tango.Core.Commands; using Tango.Core.Helpers; using Tango.FileSystem; using Tango.FSE.Common; using Tango.FSE.Common.Connection; using Tango.FSE.Common.FileSystem; using static Tango.SharedUI.Controls.NavigationControl; namespace Tango.FSE.PPCConsole.ViewModels { /// /// Represents the remote file system view model. /// /// /// public class FileSystemViewVM : FSEViewModel, INavigationViewModel { #region Properties private FileSystemItem _currentItem; /// /// Gets or sets the current file system item. /// public FileSystemItem CurrentItem { get { return _currentItem; } set { _currentItem = value; RaisePropertyChangedAuto(); OnCurrentItemChanged(); } } private String _currentPath; /// /// Gets or sets the current remote path. /// public String CurrentPath { get { return _currentPath; } set { _currentPath = value; RaisePropertyChangedAuto(); } } private List _drives; /// /// Gets or sets the file system drives. /// public List Drives { get { return _drives; } set { _drives = value; RaisePropertyChangedAuto(); } } /// /// Gets or sets the selected file system items. /// public ObservableCollection SelectedItems { get; set; } /// /// Gets or sets the list of file system handlers downloads/uploads. /// public ObservableCollection FileSystemHandlers { get; set; } #endregion #region Commands /// /// Navigates to the current path. /// public RelayCommand NavigateCommand { get; set; } /// /// Opens the current selected item (context menu). /// public RelayCommand OpenItemCommand { get; set; } /// /// Navigates back. /// public RelayCommand BackCommand { get; set; } /// /// Navigates to the specified special folder. /// public RelayCommand NavigateSpecialFolderCommand { get; set; } /// /// Navigates to the specified folder. /// public RelayCommand NavigateToFolderCommand { get; set; } /// /// Deletes the specified file system item (context menu or DEL key). /// public RelayCommand> DeleteCommand { get; set; } /// /// Handles the drag & drop of items from the operation system shell onto the file system view. /// public RelayCommand> DropCommand { get; set; } /// /// Handles the drag & drop of items from the file system view onto the operation system shell. /// public RelayCommand> DragCommand { get; set; } /// /// Removes the specified file system handler. /// public RelayCommand DeleteFileSystemHandlerCommand { get; set; } /// /// Opens the parent directory of a file system handler item. /// A download will execute the local explorer.exe. /// An upload will navigate to the remote parent folder. /// public RelayCommand OpenFileSystemHandlerDestinationCommand { get; set; } /// /// Not supported. /// public RelayCommand RetryFailedFileSystemHandlerCommand { get; set; } /// /// Handles the copy paste of remote items to a remote location. /// public RelayCommand> CopyPasteCommand { get; set; } /// /// Handles the cut parse of remote items to a remote location. /// public RelayCommand> CutPasteCommand { get; set; } /// /// Downloads the specified remote items. /// public RelayCommand> DownloadCommand { get; set; } /// /// Invokes a dialog for choosing items to upload and starts the upload. /// public RelayCommand UploadCommand { get; set; } /// /// Renames the specified file system item. /// public RelayCommand RenameCommand { get; set; } /// /// Creates a new folder at the current remote path. /// public RelayCommand NewFolderCommand { get; set; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// public FileSystemViewVM() { SelectedItems = new ObservableCollection(); FileSystemHandlers = new ObservableCollection(); NavigateCommand = new RelayCommand(NavigateToCurrentPath); OpenItemCommand = new RelayCommand(OpenFileSystemItem); BackCommand = new RelayCommand(NavigateBack, () => !(CurrentItem is FolderItem) || !(CurrentItem as FolderItem).IsRoot); NavigateSpecialFolderCommand = new RelayCommand(NavigateToSpecialFolder); NavigateToFolderCommand = new RelayCommand(async (x) => await Navigate(x)); DeleteCommand = new RelayCommand>(DeleteSelectedItems); DragCommand = new RelayCommand>(OnItemsDraggedOut); DropCommand = new RelayCommand>(OnItemsDroppedIn); DeleteFileSystemHandlerCommand = new RelayCommand(DeleteFileSystemHandler); OpenFileSystemHandlerDestinationCommand = new RelayCommand(OpenFileSystemHandlerDestination); RetryFailedFileSystemHandlerCommand = new RelayCommand(RetryFailedFileSystemHandler); CopyPasteCommand = new RelayCommand>((items) => PasteItems(items, false)); CutPasteCommand = new RelayCommand>((items) => PasteItems(items, true)); DownloadCommand = new RelayCommand>(DownloadSelectedItems); RenameCommand = new RelayCommand(RenameFileSystemItem); NewFolderCommand = new RelayCommand(CreateNewFolder); UploadCommand = new RelayCommand(UploadFilesAndFolder); } #endregion #region Override Methods /// /// Called when the application has been started. /// public override void OnApplicationStarted() { base.OnApplicationStarted(); MachineProvider.MachineConnected += MachineProvider_MachineConnected; } #endregion #region Event Handlers private async void MachineProvider_MachineConnected(object sender, MachineConnectedEventArgs e) { if (MachineProvider.ConnectionType.IsRemote()) { if (e.DifferentFromPrevious) { try { await Navigate(null, null, true); } catch (Exception ex) { NotificationProvider.PushErrorReportingSnackbar(ex, "PPC Module Error", "Could not initialize the remote file system provider."); } } } } #endregion #region Private Methods private async void NavigateBack() { if (CurrentItem.Path.Length == 3) { await Navigate(null); } else { String parent = Path.GetDirectoryName(CurrentItem.Path); await Navigate(parent); } } private void NavigateToCurrentPath() { InvokeUI(async () => { await Navigate(CurrentPath); }); } private async void OpenFileSystemItem(FileSystemItem item) { if (item == null) return; if (item.Type == FileSystemItemType.Folder || item.Type == FileSystemItemType.Drive) { await Navigate(item.Path); } else if (item.Type == FileSystemItemType.File) { //TODO: Download/Open file?... } } private async void DeleteSelectedItems(IList items) { if (items != null && items.Count > 0) { if (await NotificationProvider.ShowWarningQuestion($"Are you sure you want to delete {(items.Count == 1 ? $"'{items.First().Name}'" : $"the {items.Count} selected files/folders")}?", "DELETE")) { using (var task = NotificationProvider.PushTaskItem("Removing...")) { int remainingItems = items.Count; foreach (var item in items) { task.UpdateProgress($"Removing '{item.Name}'..."); try { remainingItems--; await FileSystemProvider.Delete(item); } catch (Exception ex) { LogManager.Log(ex, $"Could not remove '{item.Name}'."); if (remainingItems > 0) { if (!await NotificationProvider.ShowWarningQuestion($"Could not remove '{item.Name}'.\n{ex.FlattenMessage()}\nDo you wish to continue removing the remaining items?")) { break; } } else { await NotificationProvider.ShowError($"Could not remove '{item.Name}'.\n{ex.FlattenMessage()}"); } } } } NavigateToCurrentPath(); } } } private async void OnItemsDraggedOut(List items) { foreach (var item in items.Where(x => x.FileSystemItem.Type != FileSystemItemType.Drive)) { Debug.WriteLine($"Dropped out: {item.FileSystemItem.Name} => {item.Destination}"); if (File.Exists(Path.Combine(item.Destination, item.FileSystemItem.Name)) || Directory.Exists(Path.Combine(item.Destination, item.FileSystemItem.Name))) { if (!await NotificationProvider.ShowWarningQuestion($"'{item.FileSystemItem.Name}' already exists on '{Path.GetDirectoryName(item.Destination)}'. Do you want to overwrite?")) { continue; } } var handler = await FileSystemProvider.Download(item.FileSystemItem, item.Destination); FileSystemHandlers.Insert(0, handler); } } private async void OnItemsDroppedIn(List items) { try { String currentPathBefore = CurrentPath; foreach (var item in items.Where(x => x.Type != FileSystemItemType.Drive)) { Debug.WriteLine($"Dropped in: {item.Name} => {CurrentItem.Path}"); if ((CurrentItem as IFileSystemContainer).Items.ToList().Exists(x => x.Name.ToLower() == item.Name.ToLower())) { if (!await NotificationProvider.ShowWarningQuestion($"'{item.Name}' already exists on '{CurrentItem.Name}'. Do you want to overwrite?")) { continue; } } var handler = await FileSystemProvider.Upload(item.Path, CurrentItem); handler.StatusChanged += (x, status) => { if (status == FileSystemHandlerStatus.Completed && currentPathBefore == CurrentPath) { NavigateToCurrentPath(); } }; FileSystemHandlers.Insert(0, handler); } } catch (Exception ex) { await NotificationProvider.ShowError($"Error uploading to file system.\n{ex.FlattenMessage()}"); } } private async void DeleteFileSystemHandler(FileSystemHandler handler) { if (handler.Status != FileSystemHandlerStatus.Completed && handler.Status != FileSystemHandlerStatus.Failed) { if (await NotificationProvider.ShowWarningQuestion($"This item is currently {handler.Status}. Do you wish to abort and delete this item?")) { try { handler.Abort(); } catch { } FileSystemHandlers.Remove(handler); } } else { FileSystemHandlers.Remove(handler); } } private async void OpenFileSystemHandlerDestination(FileSystemHandler handler) { if (handler.Type == FileSystemHandlerType.FileDownload || handler.Type == FileSystemHandlerType.FolderDownload) { await StorageProvider.ShowInExplorer(handler.Destination); } else { await Navigate(Path.GetDirectoryName(handler.Destination)); } } private void RetryFailedFileSystemHandler(FileSystemHandler handler) { //Not supported Yet! //if (handler.Status == FileSystemHandlerStatus.Failed) //{ // if (handler.Type == FileSystemHandlerType.FileDownload || handler.Type == FileSystemHandlerType.FolderDownload) // { // var newHandler = await FileSystemProvider.Download(handler.FileSystemItem, Path.GetDirectoryName(handler.Destination)); // FileSystemHandlers.Replace(handler, newHandler); // } // else // { // var newHandler = await FileSystemProvider.Upload(Path.GetDirectoryName(handler.Destination), handler.FileSystemItem); // FileSystemHandlers.Replace(handler, newHandler); // } //} } private async void NavigateToSpecialFolder(string folder) { Environment.SpecialFolder specialFolder = (Environment.SpecialFolder)Enum.Parse(typeof(Environment.SpecialFolder), folder); await Navigate(null, specialFolder); } private async Task Navigate(String path, Environment.SpecialFolder? specialFolder = null, bool throwError = false) { try { IsFree = false; Mouse.OverrideCursor = Cursors.AppStarting; if (path != null) { CurrentItem = await FileSystemProvider.GetFolder(path) as FileSystemItem; } else if (specialFolder != null) { CurrentItem = await FileSystemProvider.GetSpecialFolder(specialFolder.Value) as FileSystemItem; } else { CurrentItem = await FileSystemProvider.GetThisPC() as FileSystemItem; try { Drives = (CurrentItem as IFileSystemContainer).Items.Cast().ToList(); } catch (Exception ex) { LogManager.Log(ex, "Error setting file system drives menu."); } } } catch (Exception ex) { IsFree = true; Mouse.OverrideCursor = null; if (!throwError) { await NotificationProvider.ShowError($"Error navigating to the specified path.\n{ex.FlattenMessage()}"); } else { throw ex; } } finally { IsFree = true; Mouse.OverrideCursor = null; } } private async void PasteItems(List items, bool move = false) { using (var task = NotificationProvider.PushTaskItem("Please wait...")) { int remainingItems = items.Count; foreach (var item in items) { Debug.WriteLine($"{(move ? "Cut" : "Copy")} Paste Item '{item.Name}' To '{CurrentItem.Name}'."); try { remainingItems--; if (move) { task.UpdateProgress($"Moving '{item.Name}'..."); await FileSystemProvider.Move(item, CurrentItem); } else { task.UpdateProgress($"Copying '{item.Name}'..."); await FileSystemProvider.Copy(item, CurrentItem); } } catch (Exception ex) { string operation = move ? "move" : "copy"; LogManager.Log(ex, $"Could not {operation} '{item.Name}'."); if (remainingItems > 0) { if (!await NotificationProvider.ShowWarningQuestion($"Could not {operation} '{item.Name}'.\n{ex.FlattenMessage()}\nDo you wish to continue with the remaining items?")) { break; } } else { await NotificationProvider.ShowError($"Could not {operation} '{item.Name}'.\n{ex.FlattenMessage()}"); } } } NavigateToCurrentPath(); } } private async void DownloadSelectedItems(List items) { if (items.Count == 1 && items.First().Type == FileSystemItemType.File) { var fileItem = items.First() as FileItem; var result = await StorageProvider.SaveFile("Select download destination file", $"{fileItem.Description}|*{fileItem.Extension}", fileItem.Name, fileItem.Extension); if (result) { var handler = await FileSystemProvider.Download(fileItem, result.SelectedItem, true); FileSystemHandlers.Insert(0, handler); } } else { var result = await StorageProvider.SelectFolder("Select download destination folder"); if (result) { String destination = result.SelectedItem; foreach (var item in items.Where(x => x.Type != FileSystemItemType.Drive)) { if (File.Exists(Path.Combine(destination, item.Name)) || Directory.Exists(Path.Combine(destination, item.Name))) { if (!await NotificationProvider.ShowWarningQuestion($"'{item.Name}' already exists on '{Path.GetDirectoryName(destination)}'. Do you want to overwrite?")) { continue; } } var handler = await FileSystemProvider.Download(item, destination); FileSystemHandlers.Insert(0, handler); } } } } private async void UploadFilesAndFolder() { var result = await StorageProvider.SelectFilesAndFolders("Select files and folders to upload"); if (result) { try { String currentPathBefore = CurrentPath; foreach (var item in result.SelectedItems) { if (!File.Exists(item) && !Directory.Exists(item)) { await NotificationProvider.ShowError($"File or folder '{item}' cannot be uploaded."); return; } } foreach (var item in result.SelectedItems) { String itemName = Path.GetFileName(item); if ((CurrentItem as IFileSystemContainer).Items.ToList().Exists(x => x.Name.ToLower() == itemName.ToLower())) { if (!await NotificationProvider.ShowWarningQuestion($"'{itemName}' already exists on '{CurrentItem.Name}'. Do you want to overwrite?")) { continue; } } var handler = await FileSystemProvider.Upload(item, CurrentItem); handler.StatusChanged += (x, status) => { if (status == FileSystemHandlerStatus.Completed && currentPathBefore == CurrentPath) { NavigateToCurrentPath(); } }; FileSystemHandlers.Insert(0, handler); } } catch (Exception ex) { await NotificationProvider.ShowError($"Error uploading to file system.\n{ex.FlattenMessage()}"); } } } private async void RenameFileSystemItem(FileSystemItem item) { if (item.Type != FileSystemItemType.Drive) { var result = await NotificationProvider.ShowInputBox( "Rename", $"Please enter a new {(item.Type == FileSystemItemType.File ? "file" : "folder")} name and press 'ENTER'.", PackIconKind.Rename, item.Name, $"{(item.Type == FileSystemItemType.File ? "file" : "folder")} name", 100, "RENAME"); if (result.Confirmed && result.Input != item.Name) { try { using (NotificationProvider.PushTaskItem("Renaming...")) { await FileSystemProvider.Rename(item, result.Input); item.Path = Path.Combine(Path.GetDirectoryName(item.Path), result.Input); } } catch (Exception ex) { LogManager.Log(ex, $"Error renaming '{item.Path}' to {result.Input}."); await NotificationProvider.ShowError($"Error renaming '{item.Name}'.\n{ex.FlattenMessage()}"); } } } } private async void CreateNewFolder() { if (CurrentItem == null) return; if (CurrentItem is FolderItem) { if ((CurrentItem as FolderItem).IsRoot) return; } var result = await NotificationProvider.ShowInputBox( "New Folder", $"Please enter a folder name and press 'ENTER'.", PackIconKind.FolderAdd, "untitled", "folder name", 100, "CREATE"); if (result.Confirmed) { try { using (NotificationProvider.PushTaskItem("Creating new folder...")) { var folderItem = await FileSystemProvider.CreateFolder(CurrentItem, result.Input); NavigateToCurrentPath(); //Instead of inserting folder item just refresh the current path... } } catch (Exception ex) { LogManager.Log(ex, $"Error creating new folder '{Path.Combine(CurrentItem.Path, result.Input)}."); await NotificationProvider.ShowError($"Error creating folder '{result.Input}'.\n{ex.FlattenMessage()}"); } } } private void OnCurrentItemChanged() { CurrentPath = CurrentItem.Path; InvalidateRelayCommands(); } #endregion } }