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.BL.Enumerations; using Tango.Core.Commands; using Tango.FileSystem; using Tango.FSE.Common; using Tango.FSE.Common.Connection; using Tango.FSE.Common.FileSystem; using Tango.FSE.Common.Notifications; namespace Tango.FSE.Firmware.ViewModels { public class FileSystemViewVM : FSEViewModel { #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(); } } /// /// 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 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; } /// /// 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; } /// /// 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); 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); DownloadCommand = new RelayCommand>(DownloadSelectedItems); 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 (e.DifferentFromPrevious && e.MachineOperator.IsConnected && AuthenticationProvider.CurrentUser.HasPermission(Permissions.FSE_FirmwareFileSystemRead)) { try { await Navigate(null, true); } catch (Exception ex) { LogManager.Log(ex, "Error initializing firmware file system."); NotificationProvider.PushSnackbarItem(MessageType.Warning, "Firmware Module Error", true, "Could not initialize the remote firmware file system provider.", TimeSpan.FromSeconds(10)); } } } #endregion #region Private Methods private async void NavigateBack() { if (CurrentItem.Path.Length == 1) { 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 FirmwareStorageProvider.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) { if (items.Exists(x => x.FileSystemItem.Type != FileSystemItemType.File)) { await NotificationProvider.ShowError("Firmware file system does not support folder downloads."); return; } foreach (var item in items) { String destination = Path.Combine(item.Destination, item.FileSystemItem.Name); if (File.Exists(destination)) { if (!await NotificationProvider.ShowWarningQuestion($"'{item.FileSystemItem.Name}' already exists on '{item.Destination}'. Do you want to overwrite?")) { continue; } } try { var handler = await FirmwareStorageProvider.Download(item.FileSystemItem as FileItem, destination); FileSystemHandlers.Insert(0, handler); } catch (Exception ex) { await NotificationProvider.ShowError(ex.FlattenMessage()); } } } private void OnItemsDroppedIn(List items) { UploadFiles(items.Select(x => x.Path).ToList()); } 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 async Task Navigate(String path, bool throwError = false) { try { IsFree = false; Mouse.OverrideCursor = Cursors.AppStarting; if (path != null) { CurrentItem = await FirmwareStorageProvider.GetFolder(path) as FileSystemItem; } else { CurrentItem = await FirmwareStorageProvider.GetRoot() as FileSystemItem; } } 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 DownloadSelectedItems(List items) { if (items.Exists(x => x.Type != FileSystemItemType.File)) { await NotificationProvider.ShowError("Firmware file system does not support folder downloads."); return; } if (items.Count == 1) { 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) { try { var handler = await FirmwareStorageProvider.Download(fileItem, result.SelectedItem); FileSystemHandlers.Insert(0, handler); } catch (Exception ex) { await NotificationProvider.ShowError(ex.FlattenMessage()); } } } else { var result = await StorageProvider.SelectFolder("Select download destination folder"); if (result) { foreach (var item in items.OfType()) { String destination = Path.Combine(result.SelectedItem, item.Name); if (File.Exists(destination)) { if (!await NotificationProvider.ShowWarningQuestion($"'{item.Name}' already exists on '{result.SelectedItem}'. Do you want to overwrite?")) { continue; } } try { var handler = await FirmwareStorageProvider.Download(item, destination); FileSystemHandlers.Insert(0, handler); } catch (Exception ex) { await NotificationProvider.ShowError(ex.FlattenMessage()); } } } } } private async void UploadFilesAndFolder() { var result = await StorageProvider.OpenFiles("Select files to upload"); if (result) { UploadFiles(result.SelectedItems); } } private async void UploadFiles(List files) { try { String currentPathBefore = CurrentPath; foreach (var file in files) { if (!File.Exists(file)) { await NotificationProvider.ShowError($"File '{file}' cannot be uploaded."); return; } } foreach (var file in files) { String itemName = Path.GetFileName(file); 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 FirmwareStorageProvider.Upload(file, Path.Combine(CurrentItem.Path, Path.GetFileName(file))); 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 CreateNewFolder() { if (CurrentItem == null) 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 FirmwareStorageProvider.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 } }