From 96fe20a20e7c107473cefeda3b06950955952bec Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Sun, 15 Mar 2020 01:55:42 +0200 Subject: Improved Console. Increased SignalR adapter connect timeout. Implemented Tango.FileSystem !!! --- .../Visual_Studio/Tango.FileSystem/DragItem.cs | 14 + .../Visual_Studio/Tango.FileSystem/DriveItem.cs | 38 + .../Tango.FileSystem/FileExplorerControl.cs | 423 +++++++++ .../Tango.FileSystem/FileExplorerControlMode.cs | 14 + .../Visual_Studio/Tango.FileSystem/FileItem.cs | 132 +++ .../Tango.FileSystem/FileSystemItem.cs | 78 ++ .../Tango.FileSystem/FileSystemItemType.cs | 15 + .../Tango.FileSystem/FileSystemManager.cs | 89 ++ .../Visual_Studio/Tango.FileSystem/FolderItem.cs | 34 + .../Tango.FileSystem/IFileSystemContainer.cs | 14 + .../Tango.FileSystem/Images/drive.png | Bin 0 -> 2166 bytes .../Tango.FileSystem/Images/folder.png | Bin 0 -> 317 bytes .../Tango.FileSystem/Network/FileSystemItemDTO.cs | 33 + .../Tango.FileSystem/Network/FileUploadRequest.cs | 14 + .../Tango.FileSystem/Network/FileUploadResponse.cs | 13 + .../Network/GetFileSystemItemRequest.cs | 13 + .../Network/GetFileSystemItemResponse.cs | 13 + .../Network/StartFileDownloadRequest.cs | 13 + .../Network/StartFileDownloadResponse.cs | 15 + .../Network/StartFolderDownloadRequest.cs | 13 + .../Network/StartFolderDownloadResponse.cs | 15 + .../Tango.FileSystem/Properties/AssemblyInfo.cs | 55 ++ .../Properties/Resources.Designer.cs | 62 ++ .../Tango.FileSystem/Properties/Resources.resx | 117 +++ .../Properties/Settings.Designer.cs | 30 + .../Tango.FileSystem/Properties/Settings.settings | 7 + .../Tango.FileSystem/Tango.FileSystem.csproj | 122 +++ .../Tango.FileSystem/Themes/Generic.xaml | 211 +++++ .../Tango.FileSystem/VirtualFileDataObject.cs | 983 +++++++++++++++++++++ 29 files changed, 2580 insertions(+) create mode 100644 Software/Visual_Studio/Tango.FileSystem/DragItem.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/DriveItem.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/FileExplorerControlMode.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/FileItem.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/FileSystemItemType.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/FolderItem.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/IFileSystemContainer.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Images/drive.png create mode 100644 Software/Visual_Studio/Tango.FileSystem/Images/folder.png create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/FileSystemItemDTO.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/FileUploadRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/FileUploadResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Properties/Resources.Designer.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Properties/Resources.resx create mode 100644 Software/Visual_Studio/Tango.FileSystem/Properties/Settings.Designer.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Properties/Settings.settings create mode 100644 Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj create mode 100644 Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml create mode 100644 Software/Visual_Studio/Tango.FileSystem/VirtualFileDataObject.cs (limited to 'Software/Visual_Studio/Tango.FileSystem') diff --git a/Software/Visual_Studio/Tango.FileSystem/DragItem.cs b/Software/Visual_Studio/Tango.FileSystem/DragItem.cs new file mode 100644 index 000000000..e71b095e1 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/DragItem.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem +{ + public class DragItem + { + public FileSystemItem FileSystemItem { get; set; } + public String Destination { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/DriveItem.cs b/Software/Visual_Studio/Tango.FileSystem/DriveItem.cs new file mode 100644 index 000000000..9c4494fec --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/DriveItem.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem +{ + public class DriveItem : FileSystemItem, IFileSystemContainer + { + public ObservableCollection Items { get; set; } + + public String Label { get; set; } + + public DriveType DriveType { get; set; } + + public DriveItem() + { + Type = FileSystemItemType.Drive; + Items = new ObservableCollection(); + } + + protected override string OnGetName() + { + return Label; + } + + public override string Description + { + get + { + return DriveType.ToString().ToTitle(); + } + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs b/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs new file mode 100644 index 000000000..23cac7733 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs @@ -0,0 +1,423 @@ +using System; +using System.Collections; +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; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Tango.Core.IO; + +namespace Tango.FileSystem +{ + public class FileExplorerControl : Control + { + private ListBox _listBox; + private DataGrid _datagrid; + private bool _preventSynchronization; + private Point _dragOutStartPoint; + private bool _isMouseDown; + private List _selectedItemsBeforeDrag; + + public IFileSystemContainer CurrentItem + { + get { return (IFileSystemContainer)GetValue(CurrentItemProperty); } + set { SetValue(CurrentItemProperty, value); } + } + public static readonly DependencyProperty CurrentItemProperty = + DependencyProperty.Register("CurrentItem", typeof(IFileSystemContainer), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public FileSystemItem SelectedItem + { + get { return (FileSystemItem)GetValue(SelectedItemProperty); } + set { SetValue(SelectedItemProperty, value); } + } + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register("SelectedItem", typeof(FileSystemItem), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand ItemDoubleClickedCommand + { + get { return (ICommand)GetValue(ItemDoubleClickedCommandProperty); } + set { SetValue(ItemDoubleClickedCommandProperty, value); } + } + public static readonly DependencyProperty ItemDoubleClickedCommandProperty = + DependencyProperty.Register("ItemDoubleClickedCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand DeleteCommand + { + get { return (ICommand)GetValue(DeleteCommandProperty); } + set { SetValue(DeleteCommandProperty, value); } + } + public static readonly DependencyProperty DeleteCommandProperty = + DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand DropCommand + { + get { return (ICommand)GetValue(DropCommandProperty); } + set { SetValue(DropCommandProperty, value); } + } + public static readonly DependencyProperty DropCommandProperty = + DependencyProperty.Register("DropCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand DragCommand + { + get { return (ICommand)GetValue(DragCommandProperty); } + set { SetValue(DragCommandProperty, value); } + } + public static readonly DependencyProperty DragCommandProperty = + DependencyProperty.Register("DragCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ImageSource DriveIcon + { + get { return (ImageSource)GetValue(DriveIconProperty); } + set { SetValue(DriveIconProperty, value); } + } + public static readonly DependencyProperty DriveIconProperty = + DependencyProperty.Register("DriveIcon", typeof(ImageSource), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ImageSource FolderIcon + { + get { return (ImageSource)GetValue(FolderIconProperty); } + set { SetValue(FolderIconProperty, value); } + } + public static readonly DependencyProperty FolderIconProperty = + DependencyProperty.Register("FolderIcon", typeof(ImageSource), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public FileExplorerControlMode Mode + { + get { return (FileExplorerControlMode)GetValue(ModeProperty); } + set { SetValue(ModeProperty, value); } + } + public static readonly DependencyProperty ModeProperty = + DependencyProperty.Register("Mode", typeof(FileExplorerControlMode), typeof(FileExplorerControl), new PropertyMetadata(FileExplorerControlMode.Large)); + + public ObservableCollection SelectedItems + { + get { return (ObservableCollection)GetValue(SelectedItemsProperty); } + set { SetValue(SelectedItemsProperty, value); } + } + public static readonly DependencyProperty SelectedItemsProperty = + DependencyProperty.Register("SelectedItems", typeof(ObservableCollection), typeof(FileExplorerControl), new PropertyMetadata(null, (d, e) => (d as FileExplorerControl).OnSelectedItemsChanged())); + + public bool AllowDrag + { + get { return (bool)GetValue(AllowDragProperty); } + set { SetValue(AllowDragProperty, value); } + } + public static readonly DependencyProperty AllowDragProperty = + DependencyProperty.Register("AllowDrag", typeof(bool), typeof(FileExplorerControl), new PropertyMetadata(null)); + + static FileExplorerControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(FileExplorerControl), new FrameworkPropertyMetadata(typeof(FileExplorerControl))); + } + + public FileExplorerControl() + { + _selectedItemsBeforeDrag = new List(); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _listBox = GetTemplateChild("PART_listbox") as ListBox; + _datagrid = GetTemplateChild("PART_datagrid") as DataGrid; + + _listBox.SelectionChanged += _listBox_SelectionChanged; + _datagrid.SelectionChanged += _datagrid_SelectionChanged; + } + + protected override void OnPreviewKeyUp(KeyEventArgs e) + { + base.OnPreviewKeyUp(e); + + if (e.Key == Key.Delete) + { + if (SelectedItems != null && SelectedItems.Count > 0) + { + DeleteCommand?.Execute(SelectedItems); + } + } + } + + private void _datagrid_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!_preventSynchronization) + { + _preventSynchronization = true; + + _listBox.SelectedItems.Clear(); + + foreach (var item in _datagrid.SelectedItems) + { + _listBox.SelectedItems.Add(item); + } + + SynchronizeSelectedItems(_listBox.SelectedItems); + + _preventSynchronization = false; + } + } + + private void _listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!_preventSynchronization) + { + _preventSynchronization = true; + + _datagrid.SelectedItems.Clear(); + + foreach (var item in _listBox.SelectedItems) + { + _datagrid.SelectedItems.Add(item); + } + + SynchronizeSelectedItems(_listBox.SelectedItems); + + _preventSynchronization = false; + } + } + + private void SynchronizeSelectedItems(IList items) + { + if (SelectedItems == null) + { + SelectedItems = items.Cast().ToObservableCollection(); + } + else + { + SelectedItems.Clear(); + + foreach (var item in items.Cast().ToList()) + { + SelectedItems.Add(item); + } + } + } + + private void OnSelectedItemsChanged() + { + if (SelectedItems != null) + { + SelectedItems.CollectionChanged -= SelectedItems_CollectionChanged; + SelectedItems.CollectionChanged += SelectedItems_CollectionChanged; + SynchronizeControls(); + } + } + + private void SelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + SynchronizeControls(); + } + + private void SynchronizeControls() + { + if (_listBox == null) return; + + if (!_preventSynchronization) + { + _preventSynchronization = true; + + _listBox.SelectedItems.Clear(); + _datagrid.SelectedItems.Clear(); + + foreach (var item in SelectedItems) + { + _listBox.SelectedItems.Add(item); + _datagrid.SelectedItems.Add(item); + } + + _preventSynchronization = false; + } + } + + protected override void OnDrop(DragEventArgs e) + { + base.OnDrop(e); + + try + { + string[] items = (string[])e.Data.GetData(DataFormats.FileDrop, false); + + if (items != null && items.Length > 0) + { + List fItems = new List(); + + foreach (var item in items) + { + if (Directory.Exists(item)) + { + fItems.Add(new FolderItem() { Path = item }); + } + else if (File.Exists(item)) + { + fItems.Add(new FileItem() { Path = item }); + } + } + + if (fItems.Count > 0) + { + DropCommand?.Execute(fItems); + } + } + } + catch { } //Ignore + } + + protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnPreviewMouseLeftButtonDown(e); + + if (!AllowDrag) return; + + if (e.OriginalSource is FrameworkElement) + { + var listBoxItem = (e.OriginalSource as FrameworkElement).FindAncestor(); + var dataGridRow = (e.OriginalSource as FrameworkElement).FindAncestor(); + if (listBoxItem == null && dataGridRow == null) + { + return; + } + } + + _selectedItemsBeforeDrag.Clear(); + _selectedItemsBeforeDrag.AddRange(SelectedItems); + _isMouseDown = true; + AllowDrop = false; + _dragOutStartPoint = e.GetPosition(null); + } + + protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e) + { + base.OnPreviewMouseLeftButtonUp(e); + AllowDrop = true; + _isMouseDown = false; + } + + protected async override void OnPreviewMouseMove(MouseEventArgs e) + { + base.OnPreviewMouseMove(e); + + if (_isMouseDown) + { + Point mpos = e.GetPosition(null); + Vector diff = this._dragOutStartPoint - mpos; + + if (e.LeftButton == MouseButtonState.Pressed && + Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance || + Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance) + { + if (SelectedItems.Count == 0) + { + return; + } + + + SelectedItems.Clear(); + + foreach (var item in _selectedItemsBeforeDrag) + { + SelectedItems.Add(item); + } + + List> dropItems = new List>(); + + foreach (var item in SelectedItems) + { + var tempFile = TemporaryManager.Default.CreateFile(".remote"); + dropItems.Add(new Tuple(item, tempFile)); + } + + List notifyItems = new List(); + + List watchers = new List(); + + FileSystemEventHandler handler = (x, args) => + { + var detectedDropItem = dropItems.SingleOrDefault(y => y.Item2.FileName == System.IO.Path.GetFileName(args.FullPath)); + + Debug.WriteLine($"File Created: {args.FullPath}"); + + if (detectedDropItem != null) + { + try + { + detectedDropItem.Item2.Delete(); //Delete temp file. + + if (File.Exists(args.FullPath)) + { + File.Delete(args.FullPath); //Delete dropped fake file. + + dropItems.Remove(detectedDropItem); + + notifyItems.Add(new DragItem() + { + FileSystemItem = detectedDropItem.Item1, + Destination = System.IO.Path.GetDirectoryName(args.FullPath), + }); + + if (dropItems.Count == 0) + { + foreach (var watcher in watchers) + { + watcher.Dispose(); + } + + //Notify to user with all items! + Dispatcher.BeginInvoke(new Action(() => + { + DragCommand?.Execute(notifyItems); + })); + } + } + } + catch { } + } + else + { + Debug.WriteLine($"Not Found: {args.FullPath}"); + } + }; + + foreach (var drive in DriveInfo.GetDrives().Where(x => x.IsReady && (x.DriveType == DriveType.Fixed || x.DriveType == DriveType.Removable))) + { + FileSystemWatcher watcher = new FileSystemWatcher(drive.RootDirectory.FullName, "*.remote"); + watcher.IncludeSubdirectories = true; + watcher.EnableRaisingEvents = true; + watcher.Created += handler; + watchers.Add(watcher); + } + + string[] files = dropItems.Select(x => x.Item2.Path).ToArray(); + var ef = DragDrop.DoDragDrop(this, new DataObject(DataFormats.FileDrop, files, false), DragDropEffects.Copy); + + await Task.Delay(3000); + + foreach (var watcher in watchers) + { + watcher.Dispose(); + } + + if (dropItems.Count > 0) + { + //Notify about problem! + } + + _isMouseDown = false; + AllowDrop = true; + } + } + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/FileExplorerControlMode.cs b/Software/Visual_Studio/Tango.FileSystem/FileExplorerControlMode.cs new file mode 100644 index 000000000..b11c93845 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/FileExplorerControlMode.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem +{ + public enum FileExplorerControlMode + { + Large, + Details + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/FileItem.cs b/Software/Visual_Studio/Tango.FileSystem/FileItem.cs new file mode 100644 index 000000000..cbc90ce06 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/FileItem.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Tango.Core.IO; + +namespace Tango.FileSystem +{ + public class FileItem : FileSystemItem + { + private static Dictionary iconCache; + private static Dictionary smallIconCache; + private static Dictionary typeDescriptionCache; + + static FileItem() + { + iconCache = new Dictionary(); + smallIconCache = new Dictionary(); + typeDescriptionCache = new Dictionary(); + } + + public FileItem() + { + Type = FileSystemItemType.File; + } + + public String Extension + { + get { return System.IO.Path.GetExtension(Path); } + } + + private BitmapSource _icon; + public BitmapSource Icon + { + get + { + if (_icon == null) + { + _icon = GetFileIcon(); + } + + return _icon; + } + } + + private BitmapSource _smallIcon; + public BitmapSource SmallIcon + { + get + { + if (_smallIcon == null) + { + _smallIcon = GetSmallFileIcon(); + } + + return _smallIcon; + } + } + + public override string Description + { + get + { + if (typeDescriptionCache.ContainsKey(Extension)) + { + return typeDescriptionCache[Extension]; + } + else + { + var tempFile = TemporaryManager.Default.CreateFile(Extension); + var shellFile = Microsoft.WindowsAPICodePack.Shell.ShellFile.FromFilePath(tempFile); + var text = shellFile.Properties.System.ItemTypeText.Value.ToStringSafe(); + shellFile.Dispose(); + tempFile.Delete(); + typeDescriptionCache.Add(Extension, text); + return text; + } + } + } + + protected BitmapSource GetFileIcon() + { + if (iconCache.ContainsKey(Extension)) + { + return iconCache[Extension]; + } + else + { + var tempFile = TemporaryManager.Default.CreateFile(Extension); + var shellFile = Microsoft.WindowsAPICodePack.Shell.ShellFile.FromFilePath(tempFile); + var source = shellFile.Thumbnail.MediumBitmapSource; + shellFile.Dispose(); + tempFile.Delete(); + iconCache.Add(Extension, source); + return source; + } + } + + private BitmapSource GetSmallFileIcon() + { + if (smallIconCache.ContainsKey(Extension)) + { + return smallIconCache[Extension]; + } + else + { + var tempFile = TemporaryManager.Default.CreateFile(Extension); + var shellFile = Microsoft.WindowsAPICodePack.Shell.ShellFile.FromFilePath(tempFile); + var source = shellFile.Thumbnail.SmallBitmapSource; + shellFile.Dispose(); + tempFile.Delete(); + smallIconCache.Add(Extension, source); + return source; + } + } + + private BitmapSource IconToBitmapSource(Icon icon) + { + var imageSource = Imaging.CreateBitmapSourceFromHIcon( + icon.Handle, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + return imageSource; + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs new file mode 100644 index 000000000..6f8190f6c --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Tango.FileSystem.Network; + +namespace Tango.FileSystem +{ + public abstract class FileSystemItem + { + public String Path { get; set; } + + public FileSystemItemType Type { get; protected set; } + + public abstract String Description { get; } + + public DateTime DateModified { get; set; } + + public long Size { get; set; } + + public String Name + { + get { return OnGetName(); } + } + + public FileSystemItem() + { + DateModified = DateTime.Now; + Size = 1000 * 1000; + } + + protected virtual String OnGetName() + { + return System.IO.Path.GetFileName(Path); + } + + public override string ToString() + { + return Name; + } + + public static FileSystemItem FromDTO(FileSystemItemDTO dto) + { + FileSystemItem item = null; + + if (dto.Type == FileSystemItemType.Drive) + { + item = new DriveItem() + { + DriveType = dto.DriveType, + Label = dto.DriveLabel, + Items = dto.Items.Select(x => FromDTO(x)).ToObservableCollection() + }; + } + else if (dto.Type == FileSystemItemType.Folder) + { + item = new FolderItem() + { + Items = dto.Items.Select(x => FromDTO(x)).ToObservableCollection() + }; + } + else if (dto.Type == FileSystemItemType.File) + { + item = new FileItem(); + } + + item.DateModified = dto.DateModified; + item.Path = dto.Path; + item.Size = dto.Size; + item.Type = dto.Type; + + return item; + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemItemType.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemItemType.cs new file mode 100644 index 000000000..5af1858a9 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemItemType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem +{ + public enum FileSystemItemType + { + Drive, + Folder, + File, + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs new file mode 100644 index 000000000..9a051793d --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.FileSystem.Network; + +namespace Tango.FileSystem +{ + public class FileSystemManager + { + public FileSystemItemDTO GetRoot() + { + FileSystemItemDTO folder = new FileSystemItemDTO(); + folder.Path = "This PC"; + folder.IsRoot = true; + folder.Type = FileSystemItemType.Folder; + folder.Items = DriveInfo.GetDrives().Select(x => new FileSystemItemDTO() + { + Path = x.RootDirectory.FullName, + DriveType = x.DriveType, + DriveLabel = x.Name, + Type = FileSystemItemType.Drive, + }).Cast().ToList(); + + return folder; + } + + public FileSystemItemDTO GetFolder(String path) + { + List items = new List(); + + if (String.IsNullOrWhiteSpace(path)) + { + return GetRoot(); + } + + if (!Directory.Exists(path)) + { + throw new DirectoryNotFoundException("The specified directory could not be located."); + } + + foreach (var directory in Directory.GetDirectories(path)) + { + items.Add(new FileSystemItemDTO() + { + Path = directory, + Type = FileSystemItemType.Folder, + DateModified = Directory.GetLastWriteTimeUtc(directory), + }); + } + + foreach (var file in Directory.GetFiles(path)) + { + items.Add(new FileSystemItemDTO() + { + Path = file, + Type = FileSystemItemType.File, + DateModified = File.GetLastWriteTimeUtc(file), + Size = new FileInfo(file).Length + }); + } + + return new FileSystemItemDTO() + { + Path = path, + Type = path.Length == 3 ? FileSystemItemType.Drive : FileSystemItemType.Folder, + Items = items + }; + } + + public void Delete(String path) + { + if (Directory.Exists(path)) + { + Directory.Delete(path); + } + else if (File.Exists(path)) + { + File.Delete(path); + } + else + { + throw new FileNotFoundException("Could not locate the specified file or directory."); + } + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/FolderItem.cs b/Software/Visual_Studio/Tango.FileSystem/FolderItem.cs new file mode 100644 index 000000000..95b065a2d --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/FolderItem.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem +{ + public class FolderItem : FileSystemItem, IFileSystemContainer + { + public ObservableCollection Items { get; set; } + + public bool IsRoot { get; set; } + + public object Icon { get; set; } //Fake for binding error. + + public object SmallIcon { get; set; } //Fake for binding error. + + public FolderItem() + { + Type = FileSystemItemType.Folder; + Items = new ObservableCollection(); + } + + public override string Description + { + get + { + return "Folder"; + } + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/IFileSystemContainer.cs b/Software/Visual_Studio/Tango.FileSystem/IFileSystemContainer.cs new file mode 100644 index 000000000..a75764a14 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/IFileSystemContainer.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem +{ + public interface IFileSystemContainer + { + ObservableCollection Items { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Images/drive.png b/Software/Visual_Studio/Tango.FileSystem/Images/drive.png new file mode 100644 index 000000000..b0cda6195 Binary files /dev/null and b/Software/Visual_Studio/Tango.FileSystem/Images/drive.png differ diff --git a/Software/Visual_Studio/Tango.FileSystem/Images/folder.png b/Software/Visual_Studio/Tango.FileSystem/Images/folder.png new file mode 100644 index 000000000..800af7186 Binary files /dev/null and b/Software/Visual_Studio/Tango.FileSystem/Images/folder.png differ diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/FileSystemItemDTO.cs b/Software/Visual_Studio/Tango.FileSystem/Network/FileSystemItemDTO.cs new file mode 100644 index 000000000..900ba0628 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/FileSystemItemDTO.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class FileSystemItemDTO + { + public String Path { get; set; } + + public String DriveLabel { get; set; } + + public FileSystemItemType Type { get; set; } + + public DriveType DriveType { get; set; } + + public DateTime DateModified { get; set; } + + public long Size { get; set; } + + public bool IsRoot { get; set; } + + public List Items { get; set; } + + public FileSystemItemDTO() + { + Items = new List(); + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadRequest.cs new file mode 100644 index 000000000..50c35c584 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadRequest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class FileUploadRequest + { + public String Path { get; set; } + public byte[] Data { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadResponse.cs new file mode 100644 index 000000000..4f4bc0d52 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class FileUploadResponse + { + + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs new file mode 100644 index 000000000..f69c7bd98 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class GetFileSystemItemRequest + { + public String Path { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemResponse.cs new file mode 100644 index 000000000..8b82e41fc --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class GetFileSystemItemResponse + { + public FileSystemItemDTO FileSystemItem { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadRequest.cs new file mode 100644 index 000000000..aac03af38 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class StartFileDownloadRequest + { + public String Path { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadResponse.cs new file mode 100644 index 000000000..187d15254 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class StartFileDownloadResponse + { + public byte[] Data { get; set; } + public long Position { get; set; } + public long Length { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadRequest.cs new file mode 100644 index 000000000..e7989bd98 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class StartFolderDownloadRequest + { + public String Path { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadResponse.cs new file mode 100644 index 000000000..c48d4c04c --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class StartFolderDownloadResponse + { + public byte[] ZipData { get; set; } + public long Position { get; set; } + public long Length { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.FileSystem/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..6daf8a3bd --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Tango.FileSystem")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tango.FileSystem")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly:ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Visual_Studio/Tango.FileSystem/Properties/Resources.Designer.cs b/Software/Visual_Studio/Tango.FileSystem/Properties/Resources.Designer.cs new file mode 100644 index 000000000..10db63263 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Tango.FileSystem.Properties { + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Tango.FileSystem.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Properties/Resources.resx b/Software/Visual_Studio/Tango.FileSystem/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.FileSystem/Properties/Settings.Designer.cs b/Software/Visual_Studio/Tango.FileSystem/Properties/Settings.Designer.cs new file mode 100644 index 000000000..ddac2a111 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Tango.FileSystem.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Properties/Settings.settings b/Software/Visual_Studio/Tango.FileSystem/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj b/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj new file mode 100644 index 000000000..90dfd78d2 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj @@ -0,0 +1,122 @@ + + + + + Debug + AnyCPU + {C6EBBBBE-2123-44DC-AEF7-A0D47D736AC0} + library + Tango.FileSystem + Tango.FileSystem + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + 4.0 + + + + + + + + + + MSBuild:Compile + Designer + + + + Code + + + + + + + + + + + + + + + + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + 1.1.1 + + + + + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} + Tango.Core + + + {8491d07b-c1f6-4b62-a412-41b9fd2d6538} + Tango.SharedUI + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml b/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml new file mode 100644 index 000000000..c9e561e29 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Tango.FileSystem/VirtualFileDataObject.cs b/Software/Visual_Studio/Tango.FileSystem/VirtualFileDataObject.cs new file mode 100644 index 000000000..a4a065792 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/VirtualFileDataObject.cs @@ -0,0 +1,983 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Windows; + +namespace Tango.FileSystem +{ + /// + /// Class implementing drag/drop and clipboard support for virtual files. + /// Also offers an alternate interface to the IDataObject interface. + /// + public sealed class VirtualFileDataObject : System.Runtime.InteropServices.ComTypes.IDataObject, IAsyncOperation + { + /// + /// Gets or sets a value indicating whether the data object can be used asynchronously. + /// + public bool IsAsynchronous { get; set; } + + /// + /// Identifier for CFSTR_FILECONTENTS. + /// + private static short FILECONTENTS = (short)(DataFormats.GetDataFormat(NativeMethods.CFSTR_FILECONTENTS).Id); + + /// + /// Identifier for CFSTR_FILEDESCRIPTORW. + /// + private static short FILEDESCRIPTORW = (short)(DataFormats.GetDataFormat(NativeMethods.CFSTR_FILEDESCRIPTORW).Id); + + /// + /// Identifier for CFSTR_PASTESUCCEEDED. + /// + private static short PASTESUCCEEDED = (short)(DataFormats.GetDataFormat(NativeMethods.CFSTR_PASTESUCCEEDED).Id); + + /// + /// Identifier for CFSTR_PERFORMEDDROPEFFECT. + /// + private static short PERFORMEDDROPEFFECT = (short)(DataFormats.GetDataFormat(NativeMethods.CFSTR_PERFORMEDDROPEFFECT).Id); + + /// + /// Identifier for CFSTR_PREFERREDDROPEFFECT. + /// + private static short PREFERREDDROPEFFECT = (short)(DataFormats.GetDataFormat(NativeMethods.CFSTR_PREFERREDDROPEFFECT).Id); + + /// + /// In-order list of registered data objects. + /// + private List _dataObjects = new List(); + + /// + /// Tracks whether an asynchronous operation is ongoing. + /// + private bool _inOperation; + + /// + /// Stores the user-specified start action. + /// + private Action _startAction; + + /// + /// Stores the user-specified end action. + /// + private Action _endAction; + + /// + /// Initializes a new instance of the VirtualFileDataObject class. + /// + public VirtualFileDataObject() + { + IsAsynchronous = true; + } + + /// + /// Initializes a new instance of the VirtualFileDataObject class. + /// + /// Optional action to run at the start of the data transfer. + /// Optional action to run at the end of the data transfer. + public VirtualFileDataObject(Action startAction, Action endAction) + : this() + { + _startAction = startAction; + _endAction = endAction; + } + + #region IDataObject Members + // Explicit interface implementation hides the technical details from users of VirtualFileDataObject. + + /// + /// Creates a connection between a data object and an advisory sink. + /// + /// A FORMATETC structure that defines the format, target device, aspect, and medium that will be used for future notifications. + /// One of the ADVF values that specifies a group of flags for controlling the advisory connection. + /// A pointer to the IAdviseSink interface on the advisory sink that will receive the change notification. + /// When this method returns, contains a pointer to a DWORD token that identifies this connection. + /// HRESULT success code. + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")] + int System.Runtime.InteropServices.ComTypes.IDataObject.DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) + { + Marshal.ThrowExceptionForHR(NativeMethods.OLE_E_ADVISENOTSUPPORTED); + throw new NotImplementedException(); + } + + /// + /// Destroys a notification connection that had been previously established. + /// + /// A DWORD token that specifies the connection to remove. + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")] + void System.Runtime.InteropServices.ComTypes.IDataObject.DUnadvise(int connection) + { + Marshal.ThrowExceptionForHR(NativeMethods.OLE_E_ADVISENOTSUPPORTED); + throw new NotImplementedException(); + } + + /// + /// Creates an object that can be used to enumerate the current advisory connections. + /// + /// When this method returns, contains an IEnumSTATDATA that receives the interface pointer to the new enumerator object. + /// HRESULT success code. + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")] + int System.Runtime.InteropServices.ComTypes.IDataObject.EnumDAdvise(out IEnumSTATDATA enumAdvise) + { + Marshal.ThrowExceptionForHR(NativeMethods.OLE_E_ADVISENOTSUPPORTED); + throw new NotImplementedException(); + } + + /// + /// Creates an object for enumerating the FORMATETC structures for a data object. + /// + /// One of the DATADIR values that specifies the direction of the data. + /// IEnumFORMATETC interface. + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")] + IEnumFORMATETC System.Runtime.InteropServices.ComTypes.IDataObject.EnumFormatEtc(DATADIR direction) + { + if (direction == DATADIR.DATADIR_GET) + { + if (0 == _dataObjects.Count) + { + // Note: SHCreateStdEnumFmtEtc fails for a count of 0; throw helpful exception + throw new InvalidOperationException("VirtualFileDataObject requires at least one data object to enumerate."); + } + + // Create enumerator and return it + IEnumFORMATETC enumerator; + if (NativeMethods.SUCCEEDED(NativeMethods.SHCreateStdEnumFmtEtc((uint)(_dataObjects.Count), _dataObjects.Select(d => d.FORMATETC).ToArray(), out enumerator))) + { + return enumerator; + } + + // Returning null here can cause an AV in the caller; throw instead + Marshal.ThrowExceptionForHR(NativeMethods.E_FAIL); + } + throw new NotImplementedException(); + } + + /// + /// Provides a standard FORMATETC structure that is logically equivalent to a more complex structure. + /// + /// A pointer to a FORMATETC structure that defines the format, medium, and target device that the caller would like to use to retrieve data in a subsequent call such as GetData. + /// When this method returns, contains a pointer to a FORMATETC structure that contains the most general information possible for a specific rendering, making it canonically equivalent to formatetIn. + /// HRESULT success code. + int System.Runtime.InteropServices.ComTypes.IDataObject.GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) + { + throw new NotImplementedException(); + } + + /// + /// Obtains data from a source data object. + /// + /// A pointer to a FORMATETC structure that defines the format, medium, and target device to use when passing the data. + /// When this method returns, contains a pointer to the STGMEDIUM structure that indicates the storage medium containing the returned data through its tymed member, and the responsibility for releasing the medium through the value of its pUnkForRelease member. + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")] + void System.Runtime.InteropServices.ComTypes.IDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium) + { + medium = new STGMEDIUM(); + var hr = ((System.Runtime.InteropServices.ComTypes.IDataObject)this).QueryGetData(ref format); + if (NativeMethods.SUCCEEDED(hr)) + { + // Find the best match + var formatCopy = format; // Cannot use ref or out parameter inside an anonymous method, lambda expression, or query expression + var dataObject = _dataObjects + .Where(d => + (d.FORMATETC.cfFormat == formatCopy.cfFormat) && + (d.FORMATETC.dwAspect == formatCopy.dwAspect) && + (0 != (d.FORMATETC.tymed & formatCopy.tymed) && + (d.FORMATETC.lindex == formatCopy.lindex))) + .FirstOrDefault(); + if (dataObject != null) + { + if (!IsAsynchronous && (FILEDESCRIPTORW == dataObject.FORMATETC.cfFormat) && !_inOperation) + { + // Enter the operation and call the start action + _inOperation = true; + if (null != _startAction) + { + _startAction(this); + } + } + + // Populate the STGMEDIUM + medium.tymed = dataObject.FORMATETC.tymed; + var result = dataObject.GetData(); // Possible call to user code + hr = result.Item2; + if (NativeMethods.SUCCEEDED(hr)) + { + medium.unionmember = result.Item1; + } + } + else + { + // Couldn't find a match + hr = NativeMethods.DV_E_FORMATETC; + } + } + if (!NativeMethods.SUCCEEDED(hr)) // Not redundant; hr gets updated in the block above + { + Marshal.ThrowExceptionForHR(hr); + } + } + + /// + /// Obtains data from a source data object. + /// + /// A pointer to a FORMATETC structure that defines the format, medium, and target device to use when passing the data. + /// A STGMEDIUM that defines the storage medium containing the data being transferred. + void System.Runtime.InteropServices.ComTypes.IDataObject.GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) + { + throw new NotImplementedException(); + } + + /// + /// Determines whether the data object is capable of rendering the data described in the FORMATETC structure. + /// + /// A pointer to a FORMATETC structure that defines the format, medium, and target device to use for the query. + /// HRESULT success code. + int System.Runtime.InteropServices.ComTypes.IDataObject.QueryGetData(ref FORMATETC format) + { + var formatCopy = format; // Cannot use ref or out parameter inside an anonymous method, lambda expression, or query expression + var formatMatches = _dataObjects.Where(d => d.FORMATETC.cfFormat == formatCopy.cfFormat); + if (!formatMatches.Any()) + { + return NativeMethods.DV_E_FORMATETC; + } + var tymedMatches = formatMatches.Where(d => 0 != (d.FORMATETC.tymed & formatCopy.tymed)); + if (!tymedMatches.Any()) + { + return NativeMethods.DV_E_TYMED; + } + var aspectMatches = tymedMatches.Where(d => d.FORMATETC.dwAspect == formatCopy.dwAspect); + if (!aspectMatches.Any()) + { + return NativeMethods.DV_E_DVASPECT; + } + return NativeMethods.S_OK; + } + + /// + /// Transfers data to the object that implements this method. + /// + /// A FORMATETC structure that defines the format used by the data object when interpreting the data contained in the storage medium. + /// A STGMEDIUM structure that defines the storage medium in which the data is being passed. + /// true to specify that the data object called, which implements SetData, owns the storage medium after the call returns. + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")] + void System.Runtime.InteropServices.ComTypes.IDataObject.SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) + { + var handled = false; + if ((formatIn.dwAspect == DVASPECT.DVASPECT_CONTENT) && + (formatIn.tymed == TYMED.TYMED_HGLOBAL) && + (medium.tymed == formatIn.tymed)) + { + // Supported format; capture the data + var ptr = NativeMethods.GlobalLock(medium.unionmember); + if (IntPtr.Zero != ptr) + { + try + { + var length = NativeMethods.GlobalSize(ptr).ToInt32(); + var data = new byte[length]; + Marshal.Copy(ptr, data, 0, length); + // Store it in our own format + SetData(formatIn.cfFormat, data); + handled = true; + } + finally + { + NativeMethods.GlobalUnlock(medium.unionmember); + } + } + + // Release memory if we now own it + if (release) + { + Marshal.FreeHGlobal(medium.unionmember); + } + } + + // Handle synchronous mode + if (!IsAsynchronous && (PERFORMEDDROPEFFECT == formatIn.cfFormat) && _inOperation) + { + // Call the end action and exit the operation + if (null != _endAction) + { + _endAction(this); + } + _inOperation = false; + } + + // Throw if unhandled + if (!handled) + { + throw new NotImplementedException(); + } + } + + #endregion + + /// + /// Provides data for the specified data format (HGLOBAL). + /// + /// Data format. + /// Sequence of data. + public void SetData(short dataFormat, IEnumerable data) + { + _dataObjects.Add( + new DataObject + { + FORMATETC = new FORMATETC + { + cfFormat = dataFormat, + ptd = IntPtr.Zero, + dwAspect = DVASPECT.DVASPECT_CONTENT, + lindex = -1, + tymed = TYMED.TYMED_HGLOBAL + }, + GetData = () => + { + var dataArray = data.ToArray(); + var ptr = Marshal.AllocHGlobal(dataArray.Length); + Marshal.Copy(dataArray, 0, ptr, dataArray.Length); + return new Tuple(ptr, NativeMethods.S_OK); + }, + }); + } + + /// + /// Provides data for the specified data format and index (ISTREAM). + /// + /// Data format. + /// Index of data. + /// Action generating the data. + /// + /// Uses Stream instead of IEnumerable(T) because Stream is more likely + /// to be natural for the expected scenarios. + /// + public void SetData(short dataFormat, int index, Action streamData) + { + _dataObjects.Add( + new DataObject + { + FORMATETC = new FORMATETC + { + cfFormat = dataFormat, + ptd = IntPtr.Zero, + dwAspect = DVASPECT.DVASPECT_CONTENT, + lindex = index, + tymed = TYMED.TYMED_ISTREAM + }, + GetData = () => + { + // Create IStream for data + var ptr = IntPtr.Zero; + var iStream = NativeMethods.CreateStreamOnHGlobal(IntPtr.Zero, true); + if (streamData != null) + { + // Wrap in a .NET-friendly Stream and call provided code to fill it + using (var stream = new IStreamWrapper(iStream)) + { + streamData(stream); + } + } + // Return an IntPtr for the IStream + ptr = Marshal.GetComInterfaceForObject(iStream, typeof(IStream)); + Marshal.ReleaseComObject(iStream); + return new Tuple(ptr, NativeMethods.S_OK); + }, + }); + } + + /// + /// Provides data for the specified data format (FILEGROUPDESCRIPTOR/FILEDESCRIPTOR) + /// + /// Collection of virtual files. + public void SetData(IEnumerable fileDescriptors) + { + // Prepare buffer + var bytes = new List(); + // Add FILEGROUPDESCRIPTOR header + bytes.AddRange(StructureBytes(new NativeMethods.FILEGROUPDESCRIPTOR { cItems = (uint)(fileDescriptors.Count()) })); + // Add n FILEDESCRIPTORs + foreach (var fileDescriptor in fileDescriptors) + { + // Set required fields + var FILEDESCRIPTOR = new NativeMethods.FILEDESCRIPTOR + { + cFileName = fileDescriptor.Name, + }; + // Set optional timestamp + if (fileDescriptor.ChangeTimeUtc.HasValue) + { + FILEDESCRIPTOR.dwFlags |= NativeMethods.FD_CREATETIME | NativeMethods.FD_WRITESTIME; + var changeTime = fileDescriptor.ChangeTimeUtc.Value.ToLocalTime().ToFileTime(); + var changeTimeFileTime = new System.Runtime.InteropServices.ComTypes.FILETIME + { + dwLowDateTime = (int)(changeTime & 0xffffffff), + dwHighDateTime = (int)(changeTime >> 32), + }; + FILEDESCRIPTOR.ftLastWriteTime = changeTimeFileTime; + FILEDESCRIPTOR.ftCreationTime = changeTimeFileTime; + } + // Set optional length + if (fileDescriptor.Length.HasValue) + { + FILEDESCRIPTOR.dwFlags |= NativeMethods.FD_FILESIZE; + FILEDESCRIPTOR.nFileSizeLow = (uint)(fileDescriptor.Length & 0xffffffff); + FILEDESCRIPTOR.nFileSizeHigh = (uint)(fileDescriptor.Length >> 32); + } + // Add structure to buffer + bytes.AddRange(StructureBytes(FILEDESCRIPTOR)); + } + + // Set CFSTR_FILEDESCRIPTORW + SetData(FILEDESCRIPTORW, bytes); + // Set n CFSTR_FILECONTENTS + var index = 0; + foreach (var fileDescriptor in fileDescriptors) + { + SetData(FILECONTENTS, index, fileDescriptor.StreamContents); + index++; + } + } + + /// + /// Gets or sets the CFSTR_PASTESUCCEEDED value for the object. + /// + public DragDropEffects? PasteSucceeded + { + get { return GetDropEffect(PASTESUCCEEDED); } + set { SetData(PASTESUCCEEDED, BitConverter.GetBytes((UInt32)value)); } + } + + /// + /// Gets or sets the CFSTR_PERFORMEDDROPEFFECT value for the object. + /// + public DragDropEffects? PerformedDropEffect + { + get { return GetDropEffect(PERFORMEDDROPEFFECT); } + set { SetData(PERFORMEDDROPEFFECT, BitConverter.GetBytes((UInt32)value)); } + } + + /// + /// Gets or sets the CFSTR_PREFERREDDROPEFFECT value for the object. + /// + public DragDropEffects? PreferredDropEffect + { + get { return GetDropEffect(PREFERREDDROPEFFECT); } + set { SetData(PREFERREDDROPEFFECT, BitConverter.GetBytes((UInt32)value)); } + } + + /// + /// Gets the DragDropEffects value (if any) previously set on the object. + /// + /// Clipboard format. + /// DragDropEffects value or null. + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")] + private DragDropEffects? GetDropEffect(short format) + { + // Get the most recent setting + var dataObject = _dataObjects + .Where(d => + (format == d.FORMATETC.cfFormat) && + (DVASPECT.DVASPECT_CONTENT == d.FORMATETC.dwAspect) && + (TYMED.TYMED_HGLOBAL == d.FORMATETC.tymed)) + .LastOrDefault(); + if (null != dataObject) + { + // Read the value and return it + var result = dataObject.GetData(); + if (NativeMethods.SUCCEEDED(result.Item2)) + { + var ptr = NativeMethods.GlobalLock(result.Item1); + if (IntPtr.Zero != ptr) + { + try + { + var length = NativeMethods.GlobalSize(ptr).ToInt32(); + if (4 == length) + { + var data = new byte[length]; + Marshal.Copy(ptr, data, 0, length); + return (DragDropEffects)(BitConverter.ToUInt32(data, 0)); + } + } + finally + { + NativeMethods.GlobalUnlock(result.Item1); + } + } + } + } + return null; + } + + #region IAsyncOperation Members + // Explicit interface implementation hides the technical details from users of VirtualFileDataObject. + + /// + /// Called by a drop source to specify whether the data object supports asynchronous data extraction. + /// + /// A Boolean value that is set to VARIANT_TRUE to indicate that an asynchronous operation is supported, or VARIANT_FALSE otherwise. + void IAsyncOperation.SetAsyncMode(int fDoOpAsync) + { + IsAsynchronous = !(NativeMethods.VARIANT_FALSE == fDoOpAsync); + } + + /// + /// Called by a drop target to determine whether the data object supports asynchronous data extraction. + /// + /// A Boolean value that is set to VARIANT_TRUE to indicate that an asynchronous operation is supported, or VARIANT_FALSE otherwise. + void IAsyncOperation.GetAsyncMode(out int pfIsOpAsync) + { + pfIsOpAsync = IsAsynchronous ? NativeMethods.VARIANT_TRUE : NativeMethods.VARIANT_FALSE; + } + + /// + /// Called by a drop target to indicate that asynchronous data extraction is starting. + /// + /// Reserved. Set this value to NULL. + void IAsyncOperation.StartOperation(IBindCtx pbcReserved) + { + _inOperation = true; + if (null != _startAction) + { + _startAction(this); + } + } + + /// + /// Called by the drop source to determine whether the target is extracting data asynchronously. + /// + /// Set to VARIANT_TRUE if data extraction is being handled asynchronously, or VARIANT_FALSE otherwise. + void IAsyncOperation.InOperation(out int pfInAsyncOp) + { + pfInAsyncOp = _inOperation ? NativeMethods.VARIANT_TRUE : NativeMethods.VARIANT_FALSE; + } + + /// + /// Notifies the data object that that asynchronous data extraction has ended. + /// + /// An HRESULT value that indicates the outcome of the data extraction. Set to S_OK if successful, or a COM error code otherwise. + /// Reserved. Set to NULL. + /// A DROPEFFECT value that indicates the result of an optimized move. This should be the same value that would be passed to the data object as a CFSTR_PERFORMEDDROPEFFECT format with a normal data extraction operation. + void IAsyncOperation.EndOperation(int hResult, IBindCtx pbcReserved, uint dwEffects) + { + if (null != _endAction) + { + _endAction(this); + } + _inOperation = false; + } + + #endregion + + /// + /// Returns the in-memory representation of an interop structure. + /// + /// Structure to return. + /// In-memory representation of structure. + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")] + private static IEnumerable StructureBytes(object source) + { + // Set up for call to StructureToPtr + var size = Marshal.SizeOf(source.GetType()); + var ptr = Marshal.AllocHGlobal(size); + var bytes = new byte[size]; + try + { + Marshal.StructureToPtr(source, ptr, false); + // Copy marshalled bytes to buffer + Marshal.Copy(ptr, bytes, 0, size); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + return bytes; + } + + /// + /// Class representing a virtual file for use by drag/drop or the clipboard. + /// + [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Deliberate to provide obvious coupling.")] + public class FileDescriptor + { + /// + /// Gets or sets the name of the file. + /// + public string Name { get; set; } + + /// + /// Gets or sets the (optional) length of the file. + /// + public Int64? Length { get; set; } + + /// + /// Gets or sets the (optional) change time of the file. + /// + public DateTime? ChangeTimeUtc { get; set; } + + /// + /// Gets or sets an Action that returns the contents of the file. + /// + public Action StreamContents { get; set; } + } + + /// + /// Class representing the result of a SetData call. + /// + private class DataObject + { + /// + /// FORMATETC structure for the data. + /// + public FORMATETC FORMATETC { get; set; } + + /// + /// Func returning the data as an IntPtr and an HRESULT success code. + /// + public Func> GetData { get; set; } + } + + /// + /// Represents a 2-tuple, or pair. + /// + /// + /// Minimal implementation of the .NET 4 Tuple class; remove if running on .NET 4. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + private class Tuple + { + /// + /// Gets the value of the current Tuple(T1, T2) object's first component. + /// + public T1 Item1 { get; private set; } + + /// + /// Gets the value of the current Tuple(T1, T2) object's second component. + /// + public T2 Item2 { get; private set; } + + /// + /// Initializes a new instance of the Tuple(T1, T2) class. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + public Tuple(T1 item1, T2 item2) + { + Item1 = item1; + Item2 = item2; + } + } + + /// + /// Simple class that exposes a write-only IStream as a Stream. + /// + private class IStreamWrapper : Stream + { + /// + /// IStream instance being wrapped. + /// + private IStream _iStream; + + /// + /// Initializes a new instance of the IStreamWrapper class. + /// + /// IStream instance to wrap. + public IStreamWrapper(IStream iStream) + { + _iStream = iStream; + } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + public override bool CanRead + { + get { return false; } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + public override bool CanWrite + { + get { return true; } + } + + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + public override void Flush() + { + throw new NotImplementedException(); + } + + /// + /// Gets the length in bytes of the stream. + /// + public override long Length + { + get { throw new NotImplementedException(); } + } + + /// + /// Gets or sets the position within the current stream. + /// + public override long Position + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + /// + /// Sets the position within the current stream. + /// + /// A byte offset relative to the origin parameter. + /// A value of type SeekOrigin indicating the reference point used to obtain the new position. + /// The new position within the current stream. + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + /// + /// Sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + public override void Write(byte[] buffer, int offset, int count) + { + if (offset == 0) + { + // Optimize common case to avoid creating extra buffers + _iStream.Write(buffer, count, IntPtr.Zero); + } + else + { + // Easy way to provide the relevant byte[] + _iStream.Write(buffer.Skip(offset).ToArray(), count, IntPtr.Zero); + } + } + } + + /// + /// Initiates a drag-and-drop operation. + /// + /// A reference to the dependency object that is the source of the data being dragged. + /// A data object that contains the data being dragged. + /// One of the DragDropEffects values that specifies permitted effects of the drag-and-drop operation. + /// One of the DragDropEffects values that specifies the final effect that was performed during the drag-and-drop operation. + /// + /// Call this method instead of System.Windows.DragDrop.DoDragDrop because this method handles IDataObject better. + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "dragSource", Justification = "Parameter is present so the signature matches that of System.Windows.DragDrop.DoDragDrop.")] + public static DragDropEffects DoDragDrop(DependencyObject dragSource, System.Runtime.InteropServices.ComTypes.IDataObject dataObject, DragDropEffects allowedEffects) + { + int[] finalEffect = new int[1]; + try + { + NativeMethods.DoDragDrop(dataObject, new DropSource(), (int)allowedEffects, finalEffect); + } + finally + { + var virtualFileDataObject = dataObject as VirtualFileDataObject; + if ((null != virtualFileDataObject) && !virtualFileDataObject.IsAsynchronous && virtualFileDataObject._inOperation) + { + // Call the end action and exit the operation + if (null != virtualFileDataObject._endAction) + { + virtualFileDataObject._endAction(virtualFileDataObject); + } + virtualFileDataObject._inOperation = false; + } + } + return (DragDropEffects)(finalEffect[0]); + } + + /// + /// Contains the methods for generating visual feedback to the end user and for canceling or completing the drag-and-drop operation. + /// + private class DropSource : NativeMethods.IDropSource + { + /// + /// Determines whether a drag-and-drop operation should continue. + /// + /// Indicates whether the Esc key has been pressed since the previous call to QueryContinueDrag or to DoDragDrop if this is the first call to QueryContinueDrag. A TRUE value indicates the end user has pressed the escape key; a FALSE value indicates it has not been pressed. + /// The current state of the keyboard modifier keys on the keyboard. Possible values can be a combination of any of the flags MK_CONTROL, MK_SHIFT, MK_ALT, MK_BUTTON, MK_LBUTTON, MK_MBUTTON, and MK_RBUTTON. + /// This method returns S_OK/DRAGDROP_S_DROP/DRAGDROP_S_CANCEL on success. + public int QueryContinueDrag(int fEscapePressed, uint grfKeyState) + { + var escapePressed = (0 != fEscapePressed); + var keyStates = (DragDropKeyStates)grfKeyState; + if (escapePressed) + { + return NativeMethods.DRAGDROP_S_CANCEL; + } + else if (DragDropKeyStates.None == (keyStates & DragDropKeyStates.LeftMouseButton)) + { + return NativeMethods.DRAGDROP_S_DROP; + } + return NativeMethods.S_OK; + } + + /// + /// Gives visual feedback to an end user during a drag-and-drop operation. + /// + /// The DROPEFFECT value returned by the most recent call to IDropTarget::DragEnter, IDropTarget::DragOver, or IDropTarget::DragLeave. + /// This method returns S_OK on success. + public int GiveFeedback(uint dwEffect) + { + return NativeMethods.DRAGDROP_S_USEDEFAULTCURSORS; + } + } + + /// + /// Provides access to Win32-level constants, structures, and functions. + /// + private static class NativeMethods + { + public const int DRAGDROP_S_DROP = 0x00040100; + public const int DRAGDROP_S_CANCEL = 0x00040101; + public const int DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102; + public const int DV_E_DVASPECT = -2147221397; + public const int DV_E_FORMATETC = -2147221404; + public const int DV_E_TYMED = -2147221399; + public const int E_FAIL = -2147467259; + public const uint FD_CREATETIME = 0x00000008; + public const uint FD_WRITESTIME = 0x00000020; + public const uint FD_FILESIZE = 0x00000040; + public const int OLE_E_ADVISENOTSUPPORTED = -2147221501; + public const int S_OK = 0; + public const int S_FALSE = 1; + public const int VARIANT_FALSE = 0; + public const int VARIANT_TRUE = -1; + + public const string CFSTR_FILECONTENTS = "FileContents"; + public const string CFSTR_FILEDESCRIPTORW = "FileGroupDescriptorW"; + public const string CFSTR_PASTESUCCEEDED = "Paste Succeeded"; + public const string CFSTR_PERFORMEDDROPEFFECT = "Performed DropEffect"; + public const string CFSTR_PREFERREDDROPEFFECT = "Preferred DropEffect"; + + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Structure exists for interop.")] + [StructLayout(LayoutKind.Sequential)] + public struct FILEGROUPDESCRIPTOR + { + public UInt32 cItems; + // Followed by 0 or more FILEDESCRIPTORs + } + + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Structure exists for interop.")] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct FILEDESCRIPTOR + { + public UInt32 dwFlags; + public Guid clsid; + public Int32 sizelcx; + public Int32 sizelcy; + public Int32 pointlx; + public Int32 pointly; + public UInt32 dwFileAttributes; + public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime; + public UInt32 nFileSizeHigh; + public UInt32 nFileSizeLow; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string cFileName; + } + + [ComImport] + [Guid("00000121-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IDropSource + { + [PreserveSig] + int QueryContinueDrag(int fEscapePressed, uint grfKeyState); + [PreserveSig] + int GiveFeedback(uint dwEffect); + } + + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Win32 API.")] + [DllImport("shell32.dll")] + public static extern int SHCreateStdEnumFmtEtc(uint cfmt, FORMATETC[] afmt, out IEnumFORMATETC ppenumFormatEtc); + + [return: MarshalAs(UnmanagedType.Interface)] + [DllImport("ole32.dll", PreserveSig = false)] + public static extern IStream CreateStreamOnHGlobal(IntPtr hGlobal, [MarshalAs(UnmanagedType.Bool)] bool fDeleteOnRelease); + + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] + public static extern void DoDragDrop(System.Runtime.InteropServices.ComTypes.IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect); + + [DllImport("kernel32.dll")] + public static extern IntPtr GlobalLock(IntPtr hMem); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("kernel32.dll")] + public static extern bool GlobalUnlock(IntPtr hMem); + + [DllImport("kernel32.dll")] + public static extern IntPtr GlobalSize(IntPtr handle); + + /// + /// Returns true iff the HRESULT is a success code. + /// + /// HRESULT to check. + /// True iff a success code. + public static bool SUCCEEDED(int hr) + { + return (0 <= hr); + } + } + } + + /// + /// Definition of the IAsyncOperation COM interface. + /// + /// + /// Pseudo-public because VirtualFileDataObject implements it. + /// + [ComImport] + [Guid("3D8B0590-F691-11d2-8EA9-006097DF5BD4")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IAsyncOperation + { + void SetAsyncMode([In] Int32 fDoOpAsync); + void GetAsyncMode([Out] out Int32 pfIsOpAsync); + void StartOperation([In] IBindCtx pbcReserved); + void InOperation([Out] out Int32 pfInAsyncOp); + void EndOperation([In] Int32 hResult, [In] IBindCtx pbcReserved, [In] UInt32 dwEffects); + } +} -- cgit v1.3.1 From adabe4e1b99bc57f0381fb0a5bb3192ac0fdff18 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Mon, 16 Mar 2020 14:32:39 +0200 Subject: Working on FSE/PPC FileSystem Provider/Service. --- .../ViewModels/FileSystemViewVM.cs | 80 +++++++- .../Tango.FSE.PPCConsole/Views/FileSystemView.xaml | 73 +++++++- .../Controls/FileSystemControl.xaml | 34 ++-- .../FSE/Tango.FSE.Common/FSEViewModel.cs | 7 + .../FileSystem/FileSystemHandler.cs | 85 +++++++++ .../FileSystem/FileSystemHandlerStatus.cs | 18 ++ .../FileSystem/FileSystemHandlerType.cs | 17 ++ .../FileSystem/IFileSystemProvider.cs | 19 ++ .../FSE/Tango.FSE.Common/Tango.FSE.Common.csproj | 4 + .../FileSystem/DefaultFileSystemProvider.cs | 72 ++++++++ .../FSE/Tango.FSE.UI/Tango.FSE.UI.csproj | 5 + .../FSE/Tango.FSE.UI/ViewModelLocator.cs | 4 + .../FileSystem/DefaultFileSystemService.cs | 202 +++++++++++++++++++++ .../FileSystem/IFileSystemService.cs | 13 ++ .../PPC/Tango.PPC.Common/Tango.PPC.Common.csproj | 8 +- .../PPC/Tango.PPC.UI/ViewModelLocator.cs | 3 + .../Tango.FileSystem/FileSystemDataGrid.cs | 30 +++ .../Tango.FileSystem/FileSystemDataGridRow.cs | 40 ++++ .../Tango.FileSystem/FileSystemItem.cs | 5 +- .../Tango.FileSystem/FileSystemManager.cs | 23 ++- .../Network/AbortOperationRequest.cs | 13 ++ .../Network/AbortOperationResponse.cs | 12 ++ .../Network/ChunkDownloadRequest.cs | 15 ++ .../Network/ChunkDownloadResponse.cs | 13 ++ .../Tango.FileSystem/Network/ChunkUploadRequest.cs | 14 ++ .../Network/ChunkUploadResponse.cs | 13 ++ .../Network/FileDownloadRequest.cs | 13 ++ .../Network/FileDownloadResponse.cs | 14 ++ .../Tango.FileSystem/Network/FileUploadRequest.cs | 1 - .../Tango.FileSystem/Network/FileUploadResponse.cs | 2 +- .../Network/FolderDownloadRequest.cs | 13 ++ .../Network/FolderDownloadResponse.cs | 14 ++ .../Network/GetFileSystemItemRequest.cs | 2 + .../Network/StartFileDownloadRequest.cs | 13 -- .../Network/StartFileDownloadResponse.cs | 15 -- .../Network/StartFolderDownloadRequest.cs | 13 -- .../Network/StartFolderDownloadResponse.cs | 15 -- .../Tango.FileSystem/Tango.FileSystem.csproj | 16 +- .../Tango.FileSystem/Themes/Generic.xaml | 32 +--- 39 files changed, 850 insertions(+), 135 deletions(-) create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandler.cs create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandlerStatus.cs create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandlerType.cs create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/IFileSystemProvider.cs create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.UI/FileSystem/DefaultFileSystemProvider.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/FileSystemDataGrid.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/FileSystemDataGridRow.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/AbortOperationRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/AbortOperationResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/ChunkDownloadRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/ChunkDownloadResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/ChunkUploadRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/ChunkUploadResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/FileDownloadRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/FileDownloadResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/FolderDownloadRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/FolderDownloadResponse.cs delete mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadRequest.cs delete mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadResponse.cs delete mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadRequest.cs delete mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadResponse.cs (limited to 'Software/Visual_Studio/Tango.FileSystem') diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs index 44bd03c39..f9eff7e6f 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs @@ -1,15 +1,19 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; using Tango.Core.Commands; using Tango.FileSystem; using Tango.FSE.Common; +using Tango.FSE.Common.Connection; +using static Tango.SharedUI.Controls.NavigationControl; namespace Tango.FSE.PPCConsole.ViewModels { - public class FileSystemViewVM : FSEViewModel + public class FileSystemViewVM : FSEViewModel, INavigationViewModel { private FileSystemItem _currentItem; public FileSystemItem CurrentItem @@ -26,32 +30,92 @@ namespace Tango.FSE.PPCConsole.ViewModels } public RelayCommand NavigateCommand { get; set; } + public RelayCommand OpenItemCommand { get; set; } + public RelayCommand BackCommand { get; set; } public FileSystemViewVM() { - NavigateCommand = new RelayCommand(Navigate); + NavigateCommand = new RelayCommand(NavigateToCurrentPath); + OpenItemCommand = new RelayCommand(OpenFileSystemItem); + BackCommand = new RelayCommand(NavigateBack, () => !(CurrentItem is FolderItem) || !(CurrentItem as FolderItem).IsRoot); + } + + private async void NavigateBack() + { + if (CurrentItem.Path.Length == 3) + { + await Navigate(null); + } + else + { + String parent = Path.GetDirectoryName(CurrentItem.Path); + await Navigate(parent); + } + } + + public override void OnApplicationStarted() + { + base.OnApplicationStarted(); + MachineProvider.MachineConnected += MachineProvider_MachineConnected; } public override void OnApplicationReady() { base.OnApplicationReady(); + } + + private async void MachineProvider_MachineConnected(object sender, MachineConnectedEventArgs e) + { + await Navigate(null); + } + + private async void NavigateToCurrentPath() + { + await Navigate(CurrentPath); + } - FileSystemManager manager = new FileSystemManager(); - CurrentItem = FileSystemItem.FromDTO(manager.GetFolder(@"C:\")); + private async void OpenFileSystemItem(FileSystemItem item) + { + if (item != null) + { + await Navigate(item.Path); + } } - private void Navigate() + private async Task Navigate(String path) { - if (CurrentPath.IsNotNullOrEmpty()) + try + { + IsFree = false; + + Mouse.OverrideCursor = Cursors.AppStarting; + + if (path != null) + { + CurrentItem = await FileSystemProvider.GetFolder(path) as FileSystemItem; + } + else + { + CurrentItem = await FileSystemProvider.GetThisPC() as FileSystemItem; + } + } + catch (Exception ex) + { + IsFree = true; + Mouse.OverrideCursor = null; + await NotificationProvider.ShowError($"Error navigating to the specified path.\n{ex.FlattenMessage()}"); + } + finally { - FileSystemManager manager = new FileSystemManager(); - CurrentItem = FileSystemItem.FromDTO(manager.GetFolder(CurrentPath)); + IsFree = true; + Mouse.OverrideCursor = null; } } private void OnCurrentItemChanged() { CurrentPath = CurrentItem.Path; + InvalidateRelayCommands(); } } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml index ad503c7de..fc23ca42c 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml @@ -10,7 +10,7 @@ xmlns:material="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance Type=vm:FileSystemViewVM, IsDesignTimeCreatable=False}" DataContext="{x:Static global:ViewModelLocator.FileSystemViewVM}"> - + @@ -36,7 +36,7 @@ - @@ -45,17 +45,74 @@ - - - - - + + + + + + + + + + + + + + + + + + - + + + + + + + + + + Devices + + Local Disk 1 + Local Disk 2 + + + Computer + + Desktop + Downloads + Documents + Tango + + + + + + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml index ec490e893..7230d97fb 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml @@ -7,6 +7,14 @@ + + + + - + - + \ No newline at end of file diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs index 997eca676..b3e832b58 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs @@ -17,6 +17,7 @@ using Tango.FSE.BL; using Tango.FSE.Common.Authentication; using Tango.FSE.Common.Connection; using Tango.FSE.Common.Diagnostics; +using Tango.FSE.Common.FileSystem; using Tango.FSE.Common.FSEApplication; using Tango.FSE.Common.Gateway; using Tango.FSE.Common.Logging; @@ -118,6 +119,12 @@ namespace Tango.FSE.Common [TangoInject] public IResolutionService ResolutionService { get; set; } + /// + /// Gets or sets the file system provider. + /// + [TangoInject] + public IFileSystemProvider FileSystemProvider { get; set; } + /// /// Gets or sets the FSE service. /// diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandler.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandler.cs new file mode 100644 index 000000000..1b1e5f747 --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandler.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; + +namespace Tango.FSE.Common.FileSystem +{ + public class FileSystemHandler : ExtendedObject + { + private Action _abortAction; + + public FileSystemHandlerType Type { get; set; } + + private FileSystemHandlerStatus _status; + public FileSystemHandlerStatus Status + { + get { return _status; } + set + { + if (_status != value) + { + _status = value; + RaisePropertyChangedAuto(); + } + } + } + + private double _position; + public double Position + { + get { return _position; } + set { _position = value; RaisePropertyChangedAuto(); } + } + + private double _length; + public double Length + { + get { return _length; } + set { _length = value; RaisePropertyChangedAuto(); } + } + + private Exception _failedException; + public Exception FailedException + { + get { return _failedException; } + set { _failedException = value; RaisePropertyChangedAuto(); } + } + + public String Name { get; set; } + public String Destination { get; set; } + + public FileSystemHandler(String name, String destination, Action abortAction) + { + Name = name; + Destination = destination; + _abortAction = abortAction; + } + + internal void InvalidateProgress(double position, double length) + { + Position = position; + Length = length; + Status = (Type == FileSystemHandlerType.FileDownload || Type == FileSystemHandlerType.FolderDownload) ? FileSystemHandlerStatus.Downloading : FileSystemHandlerStatus.Uploading; + } + + internal void RaiseFailed(Exception exception) + { + Status = FileSystemHandlerStatus.Failed; + FailedException = exception; + } + + internal void RaiseCompleted() + { + Status = FileSystemHandlerStatus.Completed; + } + + public void Abort() + { + Status = FileSystemHandlerStatus.Aborted; + _abortAction?.Invoke(); + } + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandlerStatus.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandlerStatus.cs new file mode 100644 index 000000000..fe3a030a1 --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandlerStatus.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FSE.Common.FileSystem +{ + public enum FileSystemHandlerStatus + { + Pending, + Downloading, + Uploading, + Failed, + Aborted, + Completed + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandlerType.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandlerType.cs new file mode 100644 index 000000000..4c60cb828 --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandlerType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FSE.Common.FileSystem +{ + public enum FileSystemHandlerType + { + FileDownload, + FileUpload, + FolderDownload, + FolderUpload + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/IFileSystemProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/IFileSystemProvider.cs new file mode 100644 index 000000000..16099963b --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/IFileSystemProvider.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.FileSystem; +using static System.Environment; + +namespace Tango.FSE.Common.FileSystem +{ + public interface IFileSystemProvider + { + Task GetFolder(String path); + Task GetSpecialFolder(SpecialFolder specialFolder); + Task GetThisPC(); + Task Download(FileSystemItem item, String targetFolderPath); + Task Upload(String sourcePath, String targetPath); + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj b/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj index f61b59da3..8690b742a 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj @@ -105,6 +105,10 @@ + + + + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/FileSystem/DefaultFileSystemProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/FileSystem/DefaultFileSystemProvider.cs new file mode 100644 index 000000000..48da65f16 --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/FileSystem/DefaultFileSystemProvider.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.FileSystem; +using Tango.FileSystem.Network; +using Tango.FSE.Common.Connection; +using Tango.FSE.Common.FileSystem; +using Tango.Transport; + +namespace Tango.FSE.UI.FileSystem +{ + public class DefaultFileSystemProvider : IFileSystemProvider + { + private IMachineProvider _machineProvider; + + public DefaultFileSystemProvider(IMachineProvider machineProvider) + { + _machineProvider = machineProvider; + } + + public async Task GetFolder(string path) + { + var response = await _machineProvider.MachineOperator.SendGenericRequest(new GetFileSystemItemRequest() + { + Path = path + }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(30), + }); + + return FileSystemItem.FromDTO(response.FileSystemItem) as IFileSystemContainer; + } + + public async Task GetSpecialFolder(Environment.SpecialFolder specialFolder) + { + var response = await _machineProvider.MachineOperator.SendGenericRequest(new GetFileSystemItemRequest() + { + SpecialFolder = specialFolder + }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(30), + }); + + return FileSystemItem.FromDTO(response.FileSystemItem) as IFileSystemContainer; + } + + public async Task GetThisPC() + { + var response = await _machineProvider.MachineOperator.SendGenericRequest(new GetFileSystemItemRequest() + { + //No parameters at all + }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(30), + }); + + return FileSystemItem.FromDTO(response.FileSystemItem) as IFileSystemContainer; + } + + public Task Download(FileSystemItem item, string targetFolderPath) + { + throw new NotImplementedException(); + } + + public Task Upload(string sourcePath, string targetPath) + { + throw new NotImplementedException(); + } + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Tango.FSE.UI.csproj b/Software/Visual_Studio/FSE/Tango.FSE.UI/Tango.FSE.UI.csproj index 61f0f2e70..a1bdda00f 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Tango.FSE.UI.csproj +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Tango.FSE.UI.csproj @@ -133,6 +133,7 @@ + @@ -324,6 +325,10 @@ {63561e19-ff5a-414b-a5ef-e30711543e1d} Tango.Emulations + + {c6ebbbbe-2123-44dc-aef7-a0d47d736ac0} + Tango.FileSystem + {4206AC58-3B57-4699-8835-90BF6DB01A61} Tango.Integration diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModelLocator.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModelLocator.cs index f24a0cf99..6a3cf610a 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModelLocator.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModelLocator.cs @@ -10,6 +10,7 @@ using Tango.FSE.Common.Authentication; using Tango.FSE.Common.Connection; using Tango.FSE.Common.Console; using Tango.FSE.Common.Diagnostics; +using Tango.FSE.Common.FileSystem; using Tango.FSE.Common.FSEApplication; using Tango.FSE.Common.Gateway; using Tango.FSE.Common.Logging; @@ -26,6 +27,7 @@ using Tango.FSE.UI.Authentication; using Tango.FSE.UI.Connection; using Tango.FSE.UI.Console; using Tango.FSE.UI.Diagnostics; +using Tango.FSE.UI.FileSystem; using Tango.FSE.UI.FSEApplication; using Tango.FSE.UI.Gateway; using Tango.FSE.UI.Logging; @@ -61,6 +63,7 @@ namespace Tango.FSE.UI TangoIOC.Default.Unregister(); TangoIOC.Default.Unregister(); TangoIOC.Default.Unregister(); + TangoIOC.Default.Unregister(); //TangoIOC.Default.Unregister(); //TangoIOC.Default.Unregister(); //TangoIOC.Default.Unregister(); @@ -85,6 +88,7 @@ namespace Tango.FSE.UI TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); + TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs new file mode 100644 index 000000000..ffbba2e7a --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; +using Tango.Core.DI; +using Tango.FileSystem; +using Tango.FileSystem.Network; +using Tango.Integration.ExternalBridge; +using Tango.PPC.Common.ExternalBridge; +using Tango.Transport; + +namespace Tango.PPC.Common.FileSystem +{ + [TangoCreateWhenRegistered] + public class DefaultFileSystemService : ExtendedObject, IFileSystemService, IExternalBridgeRequestHandler + { + private enum FileSystemOperationMode + { + Upload, + Download + } + + private class FileSystemOperation + { + public FileSystemOperationMode Mode { get; set; } + public String Id { get; set; } + public String Path { get; set; } + + public FileSystemOperation(FileSystemOperationMode mode, String path) + { + Mode = mode; + Id = Guid.NewGuid().ToString(); + Path = path; + } + } + + private FileSystemManager _manager; + private Dictionary _operations; + + public bool Enabled { get; set; } = true; + + public DefaultFileSystemService(IPPCExternalBridgeService externalBridge) + { + _manager = new FileSystemManager(); + _operations = new Dictionary(); + externalBridge.RegisterRequestHandler(this); + } + + [ExternalBridgeRequestHandlerMethod(typeof(GetFileSystemItemRequest))] + public async void OnGetFileSystemItemRequest(GetFileSystemItemRequest request, String token, ExternalBridgeReceiver receiver) + { + try + { + FileSystemItemDTO dto = _manager.GetFolder(request); + await receiver.SendGenericResponse(new GetFileSystemItemResponse() { FileSystemItem = dto }, token); + } + catch (Exception ex) + { + await receiver.SendErrorResponse(ex, token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(FileUploadRequest))] + public async void OnFileUploadRequest(FileUploadRequest request, String token, ExternalBridgeReceiver receiver) + { + try + { + using (var stream = new FileStream(request.Path, FileMode.Create)) { } + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Upload, request.Path); + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FileUploadResponse() { OperationId = operation.Id }, token); + } + catch (Exception ex) + { + await receiver.SendErrorResponse(ex, token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(FileDownloadRequest))] + public async void OnFileDownloadRequest(FileDownloadRequest request, String token, ExternalBridgeReceiver receiver) + { + try + { + if (!File.Exists(request.Path)) + { + await receiver.SendErrorResponse(new FileNotFoundException("Could not find the specified file."), token); + return; + } + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Download, request.Path); + + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FileDownloadResponse() + { + OperationId = operation.Id, + Length = new FileInfo(request.Path).Length + }, token); + } + catch (Exception ex) + { + await receiver.SendErrorResponse(ex, token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(ChunkUploadRequest))] + public async void OnChunkUploadRequest(ChunkUploadRequest request, String token, ExternalBridgeReceiver receiver) + { + try + { + FileSystemOperation operation; + _operations.TryGetValue(request.OperationId, out operation); + + if (operation == null) + { + await receiver.SendErrorResponse(new ArgumentException("Invalid operation id."), token); + return; + } + + using (var stream = new FileStream(operation.Path, FileMode.Append)) + { + stream.Write(request.Data, 0, request.Data.Length); + } + + await receiver.SendGenericResponse(new ChunkUploadResponse(), token); + } + catch (Exception ex) + { + await receiver.SendErrorResponse(ex, token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(ChunkDownloadRequest))] + public async void OnChunkDownloadRequest(ChunkDownloadRequest request, String token, ExternalBridgeReceiver receiver) + { + FileSystemOperation operation; + _operations.TryGetValue(request.OperationId, out operation); + + if (operation == null) + { + await receiver.SendErrorResponse(new ArgumentException("Invalid operation id."), token); + return; + } + + using (FileStream stream = new FileStream(operation.Path, FileMode.Open)) + { + stream.Position = request.Position; + byte[] data = new byte[Math.Min(request.MaxChunkSize, stream.Length - stream.Position)]; + await stream.ReadAsync(data, 0, data.Length); + await receiver.SendGenericResponse(new ChunkDownloadResponse() + { + Data = data + }, token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(AbortOperationRequest))] + public async void OnAbortOperationRequest(AbortOperationRequest request, String token, ExternalBridgeReceiver receiver) + { + FileSystemOperation operation; + _operations.TryGetValue(request.OperationId, out operation); + + if (operation == null) + { + await receiver.SendErrorResponse(new ArgumentException("Invalid operation id."), token); + return; + } + + try + { + if (operation.Mode == FileSystemOperationMode.Upload) + { + if (File.Exists(operation.Path)) + { + File.Delete(operation.Path); + } + else if (Directory.Exists(operation.Path)) + { + Directory.Delete(operation.Path, true); + } + } + + await receiver.SendGenericResponse(new AbortOperationResponse(), token); + } + catch (Exception ex) + { + await receiver.SendErrorResponse(ex, token); + } + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs new file mode 100644 index 000000000..050bb1cd6 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.FileSystem +{ + public interface IFileSystemService + { + bool Enabled { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj b/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj index 4551fe427..664e9e228 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj @@ -152,6 +152,8 @@ + + @@ -368,6 +370,10 @@ {4399AF76-DB52-4CFB-8020-6F85BDB29FD5} Tango.Explorer + + {c6ebbbbe-2123-44dc-aef7-a0d47d736ac0} + Tango.FileSystem + {4206ac58-3b57-4699-8835-90bf6db01a61} Tango.Integration @@ -446,7 +452,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs b/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs index 9e150221d..edc9cb1cd 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs @@ -13,6 +13,7 @@ using Tango.PPC.Common.Console; using Tango.PPC.Common.Diagnostics; using Tango.PPC.Common.EventLogging; using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Common.FileSystem; using Tango.PPC.Common.HotSpot; using Tango.PPC.Common.MachineSetup; using Tango.PPC.Common.MachineUpdate; @@ -86,6 +87,7 @@ namespace Tango.PPC.UI TangoIOC.Default.Unregister(); TangoIOC.Default.Unregister(); TangoIOC.Default.Unregister(); + TangoIOC.Default.Unregister(); if (App.StartupArgs.Contains("-webDebug")) { @@ -123,6 +125,7 @@ namespace Tango.PPC.UI TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); + TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemDataGrid.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemDataGrid.cs new file mode 100644 index 000000000..c1c31cf95 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemDataGrid.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Tango.FileSystem +{ + public class FileSystemDataGrid : DataGrid + { + static FileSystemDataGrid() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(FileSystemDataGrid), new FrameworkPropertyMetadata(typeof(FileSystemDataGrid))); + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new FileSystemDataGridRow(); + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemDataGridRow.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemDataGridRow.cs new file mode 100644 index 000000000..4cc67f888 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemDataGridRow.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Tango.FileSystem +{ + public class FileSystemDataGridRow : DataGridRow + { + public ICommand DoubleClickCommand + { + get { return (ICommand)GetValue(DoubleClickCommandProperty); } + set { SetValue(DoubleClickCommandProperty, value); } + } + public static readonly DependencyProperty DoubleClickCommandProperty = + DependencyProperty.Register("DoubleClickCommand", typeof(ICommand), typeof(FileSystemDataGridRow), new PropertyMetadata(null)); + + protected override void OnPreviewMouseDoubleClick(MouseButtonEventArgs e) + { + base.OnPreviewMouseDoubleClick(e); + + DoubleClickCommand?.Execute(DataContext); + } + + static FileSystemDataGridRow() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(FileSystemDataGridRow), new FrameworkPropertyMetadata(typeof(FileSystemDataGridRow))); + } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs index 6f8190f6c..536409f63 100644 --- a/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs @@ -52,14 +52,15 @@ namespace Tango.FileSystem { DriveType = dto.DriveType, Label = dto.DriveLabel, - Items = dto.Items.Select(x => FromDTO(x)).ToObservableCollection() + Items = dto.Items?.Select(x => FromDTO(x)).ToObservableCollection() }; } else if (dto.Type == FileSystemItemType.Folder) { item = new FolderItem() { - Items = dto.Items.Select(x => FromDTO(x)).ToObservableCollection() + Items = dto.Items?.Select(x => FromDTO(x)).ToObservableCollection(), + IsRoot = dto.IsRoot, }; } else if (dto.Type == FileSystemItemType.File) diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs index 9a051793d..f0b86becf 100644 --- a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs @@ -16,7 +16,7 @@ namespace Tango.FileSystem folder.Path = "This PC"; folder.IsRoot = true; folder.Type = FileSystemItemType.Folder; - folder.Items = DriveInfo.GetDrives().Select(x => new FileSystemItemDTO() + folder.Items = DriveInfo.GetDrives().Where(x => x.DriveType == DriveType.Fixed || x.DriveType == DriveType.Removable || x.DriveType == DriveType.Network).Select(x => new FileSystemItemDTO() { Path = x.RootDirectory.FullName, DriveType = x.DriveType, @@ -27,21 +27,26 @@ namespace Tango.FileSystem return folder; } - public FileSystemItemDTO GetFolder(String path) + public FileSystemItemDTO GetFolder(GetFileSystemItemRequest request) { List items = new List(); - if (String.IsNullOrWhiteSpace(path)) + if (request.SpecialFolder.HasValue) + { + request.Path = Environment.GetFolderPath(request.SpecialFolder.Value); + } + + if (String.IsNullOrWhiteSpace(request.Path)) { return GetRoot(); } - if (!Directory.Exists(path)) + if (!Directory.Exists(request.Path)) { throw new DirectoryNotFoundException("The specified directory could not be located."); } - foreach (var directory in Directory.GetDirectories(path)) + foreach (var directory in Directory.GetDirectories(request.Path)) { items.Add(new FileSystemItemDTO() { @@ -51,7 +56,7 @@ namespace Tango.FileSystem }); } - foreach (var file in Directory.GetFiles(path)) + foreach (var file in Directory.GetFiles(request.Path)) { items.Add(new FileSystemItemDTO() { @@ -64,8 +69,8 @@ namespace Tango.FileSystem return new FileSystemItemDTO() { - Path = path, - Type = path.Length == 3 ? FileSystemItemType.Drive : FileSystemItemType.Folder, + Path = request.Path, + Type = request.Path.Length == 3 ? FileSystemItemType.Drive : FileSystemItemType.Folder, Items = items }; } @@ -74,7 +79,7 @@ namespace Tango.FileSystem { if (Directory.Exists(path)) { - Directory.Delete(path); + Directory.Delete(path, true); } else if (File.Exists(path)) { diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/AbortOperationRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/AbortOperationRequest.cs new file mode 100644 index 000000000..3e912b9f0 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/AbortOperationRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class AbortOperationRequest + { + public String OperationId { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/AbortOperationResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/AbortOperationResponse.cs new file mode 100644 index 000000000..c5669ad48 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/AbortOperationResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class AbortOperationResponse + { + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/ChunkDownloadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/ChunkDownloadRequest.cs new file mode 100644 index 000000000..16951930e --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/ChunkDownloadRequest.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class ChunkDownloadRequest + { + public String OperationId { get; set; } + public long Position { get; set; } + public long MaxChunkSize { get; set; } = 1024 * 1024; + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/ChunkDownloadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/ChunkDownloadResponse.cs new file mode 100644 index 000000000..df72e193f --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/ChunkDownloadResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class ChunkDownloadResponse + { + public byte[] Data { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/ChunkUploadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/ChunkUploadRequest.cs new file mode 100644 index 000000000..98a27efd2 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/ChunkUploadRequest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class ChunkUploadRequest + { + public String OperationId { get; set; } + public byte[] Data { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/ChunkUploadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/ChunkUploadResponse.cs new file mode 100644 index 000000000..5cd6a3f80 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/ChunkUploadResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class ChunkUploadResponse + { + + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/FileDownloadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/FileDownloadRequest.cs new file mode 100644 index 000000000..b63937f8b --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/FileDownloadRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class FileDownloadRequest + { + public String Path { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/FileDownloadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/FileDownloadResponse.cs new file mode 100644 index 000000000..c57a9bad6 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/FileDownloadResponse.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class FileDownloadResponse + { + public long Length { get; set; } + public String OperationId { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadRequest.cs index 50c35c584..f3cbc2d54 100644 --- a/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadRequest.cs +++ b/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadRequest.cs @@ -9,6 +9,5 @@ namespace Tango.FileSystem.Network public class FileUploadRequest { public String Path { get; set; } - public byte[] Data { get; set; } } } diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadResponse.cs index 4f4bc0d52..c0af1a797 100644 --- a/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadResponse.cs +++ b/Software/Visual_Studio/Tango.FileSystem/Network/FileUploadResponse.cs @@ -8,6 +8,6 @@ namespace Tango.FileSystem.Network { public class FileUploadResponse { - + public String OperationId { get; set; } } } diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/FolderDownloadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/FolderDownloadRequest.cs new file mode 100644 index 000000000..cb65a8942 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/FolderDownloadRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class FolderDownloadRequest + { + public String Path { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/FolderDownloadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/FolderDownloadResponse.cs new file mode 100644 index 000000000..239ff2e59 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/FolderDownloadResponse.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class FolderDownloadResponse + { + public long Length { get; set; } + public String OperationId { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs index f69c7bd98..1ed6c19e4 100644 --- a/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs +++ b/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs @@ -3,11 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using static System.Environment; namespace Tango.FileSystem.Network { public class GetFileSystemItemRequest { public String Path { get; set; } + public SpecialFolder? SpecialFolder { get; set; } } } diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadRequest.cs deleted file mode 100644 index aac03af38..000000000 --- a/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadRequest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tango.FileSystem.Network -{ - public class StartFileDownloadRequest - { - public String Path { get; set; } - } -} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadResponse.cs deleted file mode 100644 index 187d15254..000000000 --- a/Software/Visual_Studio/Tango.FileSystem/Network/StartFileDownloadResponse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tango.FileSystem.Network -{ - public class StartFileDownloadResponse - { - public byte[] Data { get; set; } - public long Position { get; set; } - public long Length { get; set; } - } -} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadRequest.cs deleted file mode 100644 index e7989bd98..000000000 --- a/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadRequest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tango.FileSystem.Network -{ - public class StartFolderDownloadRequest - { - public String Path { get; set; } - } -} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadResponse.cs deleted file mode 100644 index c48d4c04c..000000000 --- a/Software/Visual_Studio/Tango.FileSystem/Network/StartFolderDownloadResponse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tango.FileSystem.Network -{ - public class StartFolderDownloadResponse - { - public byte[] ZipData { get; set; } - public long Position { get; set; } - public long Length { get; set; } - } -} diff --git a/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj b/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj index 90dfd78d2..733493f02 100644 --- a/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj +++ b/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj @@ -50,6 +50,14 @@ + + + + + + + + MSBuild:Compile @@ -73,10 +81,10 @@ - - - - + + + + Code diff --git a/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml b/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml index c9e561e29..f793be947 100644 --- a/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml +++ b/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml @@ -7,6 +7,14 @@ + + + + - + - + + + + + + + + + + + + + - + - + @@ -94,20 +96,24 @@ Devices - Local Disk 1 - Local Disk 2 + + + + + + + Computer - Desktop - Downloads - Documents - Tango + Desktop + Documents + Application Data - + - + diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs index f0b86becf..44c8f1901 100644 --- a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Tango.FileSystem.Network; @@ -41,6 +42,21 @@ namespace Tango.FileSystem return GetRoot(); } + try + { + if (request.Path.Count(x => x == '%') == 2) + { + var variable = Regex.Match(request.Path, "(?<=%)(.*?)(?=%)").Value; + request.Path = request.Path.Replace($"%{variable}%", Environment.ExpandEnvironmentVariables($"%{variable}%")); + } + } + catch + { + throw new ArgumentException("Could not parse environment variable."); + } + + request.Path = Path.GetFullPath(request.Path); + if (!Directory.Exists(request.Path)) { throw new DirectoryNotFoundException("The specified directory could not be located."); -- cgit v1.3.1 From d48b2d23515d06a21ad241380986bf8f31773195 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Sun, 22 Mar 2020 00:04:44 +0200 Subject: Implemented WebRtcTransportAdapter. Implemented FileSystem via WebRTC. Improved FileSystemControl keyboard control. Implemented FileSystemControl context menu. Improved Transported custom request handler registration. Implemented FS copy/move/delete. Implemented InputBox. --- .../ViewModels/FileSystemViewVM.cs | 146 +++++++- .../ViewModels/RemoteDesktopViewVM.cs | 6 + .../Tango.FSE.PPCConsole/Views/FileSystemView.xaml | 61 ++-- .../Connection/MachineConnectedEventArgs.cs | 1 + .../Controls/FileSystemControl.xaml | 72 +++- .../FileSystem/FileSystemHandler.cs | 28 ++ .../FileSystem/IFileSystemProvider.cs | 6 + .../Notifications/INotificationProvider.cs | 26 +- .../Notifications/InputBoxResult.cs | 14 + .../Tango.FSE.Common/Notifications/InputBoxVM.cs | 34 ++ .../FSE/Tango.FSE.Common/Tango.FSE.Common.csproj | 2 + .../Connection/DefaultMachineProvider.cs | 5 + .../FileSystem/DefaultFileSystemProvider.cs | 195 ++++++++++- .../Navigation/DefaultNavigationManager.cs | 13 +- .../Notifications/DefaultNotificationProvider.cs | 65 ++++ .../RemoteDesktop/DefaultRemoteDesktopProvider.cs | 2 +- .../SystemInfo/DefaultSystemInfoProvider.cs | 2 +- .../FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs | 6 + .../FSE/Tango.FSE.UI/Views/LayoutView.xaml | 2 +- .../FSE/Tango.FSE.UI/Views/MainView.xaml | 71 ++++ .../FSE/Tango.FSE.UI/Views/MainView.xaml.cs | 45 +++ .../FileSystem/DefaultFileSystemService.cs | 117 ++++++- .../FileSystem/IFileSystemService.cs | 1 + .../SideChains/WebRtc.NET/src/conductor.cc | 2 +- .../Tango.FileSystem/FileExplorerControl.cs | 380 ++++++++++++++++++++- .../Tango.FileSystem/FileSystemItem.cs | 10 +- .../Tango.FileSystem/FileSystemManager.cs | 46 +++ .../Tango.FileSystem/Network/CopyRequest.cs | 14 + .../Tango.FileSystem/Network/CopyResponse.cs | 12 + .../Tango.FileSystem/Network/DeleteRequest.cs | 13 + .../Tango.FileSystem/Network/DeleteResponse.cs | 12 + .../Tango.FileSystem/Network/InitWebRtcRequest.cs | 13 + .../Tango.FileSystem/Network/InitWebRtcResponse.cs | 12 + .../Tango.FileSystem/Network/MoveRequest.cs | 14 + .../Tango.FileSystem/Network/MoveResponse.cs | 12 + .../Tango.FileSystem/Tango.FileSystem.csproj | 8 + .../Tango.FileSystem/Themes/Generic.xaml | 17 +- .../Tango.SharedUI/Controls/NavigationControl.cs | 6 +- .../Visual_Studio/Tango.SystemInfo/WMIReader.cs | 8 +- .../Visual_Studio/Tango.Transport/ITransporter.cs | 2 +- .../Tango.Transport/TransporterBase.cs | 10 +- .../Tango.WebRTC/Network/IceCandidateRequest.cs | 13 + .../Tango.WebRTC/Network/IceCandidateResponse.cs | 12 + .../Tango.WebRTC/Network/OfferRequest.cs | 13 + .../Tango.WebRTC/Network/OfferResponse.cs | 13 + .../Visual_Studio/Tango.WebRTC/Tango.WebRTC.csproj | 15 + .../Tango.WebRTC/WebRtcTransportAdapter.cs | 307 +++++++++++++++++ .../WebRtcTransportAdapterDisconnectedException.cs | 16 + .../Tango.WebRTC/WebRtcTransportAdapterMode.cs | 14 + 49 files changed, 1846 insertions(+), 68 deletions(-) create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/InputBoxResult.cs create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/InputBoxVM.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/CopyRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/CopyResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/DeleteRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/DeleteResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/InitWebRtcRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/InitWebRtcResponse.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/MoveRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/MoveResponse.cs create mode 100644 Software/Visual_Studio/Tango.WebRTC/Network/IceCandidateRequest.cs create mode 100644 Software/Visual_Studio/Tango.WebRTC/Network/IceCandidateResponse.cs create mode 100644 Software/Visual_Studio/Tango.WebRTC/Network/OfferRequest.cs create mode 100644 Software/Visual_Studio/Tango.WebRTC/Network/OfferResponse.cs create mode 100644 Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapter.cs create mode 100644 Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapterDisconnectedException.cs create mode 100644 Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapterMode.cs (limited to 'Software/Visual_Studio/Tango.FileSystem') diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs index cba25303e..f074294b2 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs @@ -1,4 +1,5 @@ -using System; +using MaterialDesignThemes.Wpf; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -53,6 +54,10 @@ namespace Tango.FSE.PPCConsole.ViewModels public RelayCommand DeleteFileSystemHandlerCommand { get; set; } public RelayCommand OpenFileSystemHandlerDestinationCommand { get; set; } public RelayCommand RetryFailedFileSystemHandlerCommand { get; set; } + public RelayCommand> CopyPasteCommand { get; set; } + public RelayCommand> CutPasteCommand { get; set; } + public RelayCommand> DownloadCommand { get; set; } + public RelayCommand RenameCommand { get; set; } public FileSystemViewVM() @@ -69,6 +74,10 @@ namespace Tango.FSE.PPCConsole.ViewModels 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); } private async void NavigateBack() @@ -97,7 +106,10 @@ namespace Tango.FSE.PPCConsole.ViewModels private async void MachineProvider_MachineConnected(object sender, MachineConnectedEventArgs e) { - await Navigate(null); + if (e.DifferentFromPrevious) + { + await Navigate(null); + } } private async void NavigateToCurrentPath() @@ -123,9 +135,41 @@ namespace Tango.FSE.PPCConsole.ViewModels { if (items != null && items.Count > 0) { - if (await NotificationProvider.ShowWarningQuestion("Are you sure you want to delete the selected files/folders?", "DELETE")) + if (await NotificationProvider.ShowWarningQuestion($"Are you sure you want to delete {(items.Count == 1 ? $"'{items.First().Name}'" : $"the {items.Count} selected files/folders")}?", "DELETE")) { - //TODO: Delete items + 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(); } } } @@ -135,6 +179,15 @@ namespace Tango.FSE.PPCConsole.ViewModels 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); } @@ -234,6 +287,91 @@ namespace Tango.FSE.PPCConsole.ViewModels } } + 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 void DownloadSelectedItems(List items) + { + + } + + 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 void OnCurrentItemChanged() { CurrentPath = CurrentItem.Path; diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/RemoteDesktopViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/RemoteDesktopViewVM.cs index 1708322bc..9c2b139d9 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/RemoteDesktopViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/RemoteDesktopViewVM.cs @@ -47,6 +47,12 @@ namespace Tango.FSE.PPCConsole.ViewModels base.OnApplicationStarted(); RemoteDesktopProvider.FrameReceived += RemoteDesktopProvider_FrameReceived; + MachineProvider.MachineConnected += MachineProvider_MachineConnected; + } + + private void MachineProvider_MachineConnected(object sender, Common.Connection.MachineConnectedEventArgs e) + { + Source = null; } private async void StartRemoteDesktop() diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml index 8038ea905..d1143ede4 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml @@ -17,7 +17,7 @@ - + @@ -92,7 +92,7 @@ - + Devices @@ -121,6 +121,7 @@ + DropCommand="{Binding DropCommand}" + CutPasteCommand="{Binding CutPasteCommand}" + CopyPasteCommand="{Binding CopyPasteCommand}" + DownloadCommand="{Binding DownloadCommand}" + BackCommand="{Binding BackCommand}" + RenameCommand="{Binding RenameCommand}"/> - - - - Transfer Queue - + + + + + Transfer Queue + () - - + + + + + Enable fast communication channel + + @@ -221,13 +233,13 @@ + + + + + + + @@ -239,11 +251,16 @@ - - - / - - + + + + / + + + + /s + + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Connection/MachineConnectedEventArgs.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/Connection/MachineConnectedEventArgs.cs index ae4d18538..003fe407b 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Connection/MachineConnectedEventArgs.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Connection/MachineConnectedEventArgs.cs @@ -10,5 +10,6 @@ namespace Tango.FSE.Common.Connection public class MachineConnectedEventArgs : EventArgs { public IExternalBridgeClient MachineOperator { get; set; } + public bool DifferentFromPrevious { get; set; } } } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml index 7230d97fb..62de7cf48 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml @@ -2,6 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:converters="clr-namespace:Tango.SharedUI.Converters;assembly=Tango.SharedUI" xmlns:local="clr-namespace:Tango.FileSystem;assembly=Tango.FileSystem" + xmlns:material="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:l="clr-namespace:Tango.FSE.Common.Controls"> @@ -9,6 +11,11 @@ @@ -123,7 +187,7 @@ - + @@ -149,7 +213,7 @@ - + @@ -158,7 +222,7 @@ - + @@ -167,7 +231,7 @@ - + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandler.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandler.cs index 48fb35c0f..a748a63cc 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandler.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/FileSystemHandler.cs @@ -12,6 +12,8 @@ namespace Tango.FSE.Common.FileSystem { private Action _abortAction; private FileSystemHandlerStatus _statusBeforePause; + private System.Timers.Timer _transferRateTimer; + private double _lastPosition; public FileSystemHandlerType Type { get; set; } @@ -65,6 +67,13 @@ namespace Tango.FSE.Common.FileSystem set { _length = value; RaisePropertyChangedAuto(); } } + private long _transferRate; + public long TransferRate + { + get { return _transferRate; } + set { _transferRate = value; RaisePropertyChangedAuto(); } + } + private Exception _failedException; public Exception FailedException { @@ -83,8 +92,27 @@ namespace Tango.FSE.Common.FileSystem _abortAction = abortAction; } + private void _transferRateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + if (Status == FileSystemHandlerStatus.Aborted || Status == FileSystemHandlerStatus.Completed || Status == FileSystemHandlerStatus.Failed) + { + _transferRateTimer.Dispose(); + return; + } + + TransferRate = (long)(Position - _lastPosition); + _lastPosition = Position; + } + internal void InvalidateProgress(double position, double length) { + if (_transferRateTimer == null) + { + _transferRateTimer = new System.Timers.Timer(1000); + _transferRateTimer.Elapsed += _transferRateTimer_Elapsed; + _transferRateTimer.Start(); + } + Position = position; Length = length; diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/IFileSystemProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/IFileSystemProvider.cs index c7e00610a..253bf801b 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/IFileSystemProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/FileSystem/IFileSystemProvider.cs @@ -10,10 +10,16 @@ namespace Tango.FSE.Common.FileSystem { public interface IFileSystemProvider { + bool EnableWebRTC { get; set; } + bool IsWebRtcAvailable { get; } Task GetFolder(String path); Task GetSpecialFolder(SpecialFolder specialFolder); Task GetThisPC(); Task Download(FileSystemItem item, String localTargetFolder); Task Upload(String sourcePath, String remoteTargetFolder); + Task Copy(FileSystemItem source, FileSystemItem target); + Task Move(FileSystemItem source, FileSystemItem target); + Task Rename(FileSystemItem source, String newName); + Task Delete(FileSystemItem item); } } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/INotificationProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/INotificationProvider.cs index 5985a687d..49402bd41 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/INotificationProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/INotificationProvider.cs @@ -1,4 +1,5 @@ -using System; +using MaterialDesignThemes.Wpf; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; @@ -72,6 +73,16 @@ namespace Tango.FSE.Common.Notifications /// bool HasMessageBox { get; } + /// + /// Gets the current input box. + /// + InputBoxVM CurrentInputBox { get; } + + /// + /// Gets a value indicating whether this instance has input box. + /// + bool HasInputBox { get; } + /// /// Gets the current dialog. /// @@ -111,6 +122,19 @@ namespace Tango.FSE.Common.Notifications /// The message. Task ShowSuccess(String message); + /// + /// Shows an input box. + /// + /// The title. + /// The message. + /// The icon. + /// The default input. + /// The input hint. + /// The ok text. + /// The cancel text. + /// + Task ShowInputBox(String title, String message, PackIconKind icon = PackIconKind.InfoOutline, String defaultInput = null, String inputHint = null, int? maxChars = null, String okText = null, String cancelText = null); + /// /// Shows a question message box. /// diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/InputBoxResult.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/InputBoxResult.cs new file mode 100644 index 000000000..1765dd655 --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/InputBoxResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FSE.Common.Notifications +{ + public class InputBoxResult + { + public bool Confirmed { get; set; } + public String Input { get; set; } + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/InputBoxVM.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/InputBoxVM.cs new file mode 100644 index 000000000..7744d04d0 --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/InputBoxVM.cs @@ -0,0 +1,34 @@ +using MaterialDesignThemes.Wpf; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FSE.Common.Notifications +{ + public class InputBoxVM : MessageBoxVM + { + public PackIconKind Icon { get; set; } + public String InputHint { get; set; } + + public int MaxCharacters { get; set; } + + private String _input; + public String Input + { + get { return _input; } + set { _input = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } + } + + public InputBoxVM() : base() + { + HasCancel = true; + } + + protected override bool CanOK() + { + return base.CanOK() && Input.IsNotNullOrEmpty(); + } + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj b/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj index e3ecb319d..02df7140f 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj @@ -124,6 +124,8 @@ + + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Connection/DefaultMachineProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/Connection/DefaultMachineProvider.cs index f4ee8c461..790d21d6c 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Connection/DefaultMachineProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Connection/DefaultMachineProvider.cs @@ -26,6 +26,7 @@ namespace Tango.FSE.UI.Connection public class DefaultMachineProvider : ExtendedObject, IMachineProvider { private List _eventRegistrations; + private String _lastMachineSerialNumber; private class EventRegistration { @@ -184,6 +185,7 @@ namespace Tango.FSE.UI.Connection IsBusy = true; String serial = vm.GetMachineSerialNumber(); + machine.SerialNumber = serial; using (var task = NotificationProvider.PushTaskItem($"Connecting to machine '{serial}'...")) { @@ -328,7 +330,10 @@ namespace Tango.FSE.UI.Connection MachineConnected?.Invoke(this, new MachineConnectedEventArgs() { MachineOperator = machineOperator, + DifferentFromPrevious = machineOperator.SerialNumber != _lastMachineSerialNumber, }); + + _lastMachineSerialNumber = machineOperator.SerialNumber; } protected virtual void OnMachineDisconnected(IExternalBridgeClient machineOperator, Exception exception) diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/FileSystem/DefaultFileSystemProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/FileSystem/DefaultFileSystemProvider.cs index bcc39d11d..1f6641f3a 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/FileSystem/DefaultFileSystemProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/FileSystem/DefaultFileSystemProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; @@ -13,16 +14,97 @@ using Tango.FileSystem.Network; using Tango.FSE.Common.Connection; using Tango.FSE.Common.FileSystem; using Tango.Transport; +using Tango.Transport.Transporters; +using Tango.WebRTC; namespace Tango.FSE.UI.FileSystem { public class DefaultFileSystemProvider : ExtendedObject, IFileSystemProvider { private IMachineProvider _machineProvider; + private BasicTransporter _webRtcTransporter; + private const string WEB_RTC_CHANNEL_NAME = "FileSystemChannel"; + private const long MAX_CHUNK_SIZE = 1024 * 10; + private const long MAX_CHUNK_SIZE_WEB_RTC = 1024 * 15; + private List _activeHandlers; + + private bool _enableWebRTC; + public bool EnableWebRTC + { + get { return _enableWebRTC; } + set { _enableWebRTC = value; RaisePropertyChangedAuto(); } + } + + private bool _isWebRtcAvailable; + public bool IsWebRtcAvailable + { + get { return _isWebRtcAvailable; } + private set { _isWebRtcAvailable = value; RaisePropertyChangedAuto(); } + } public DefaultFileSystemProvider(IMachineProvider machineProvider) { + _activeHandlers = new List(); + + EnableWebRTC = true; //TODO: From Settings.. _machineProvider = machineProvider; + _machineProvider.MachineConnected += _machineProvider_MachineConnected; + _machineProvider.MachineDisconnected += _machineProvider_MachineDisconnected; + } + + private void _machineProvider_MachineDisconnected(object sender, MachineDisconnectedEventArgs e) + { + IsWebRtcAvailable = false; + + foreach (var handler in _activeHandlers.ToList()) + { + try + { + handler.RaiseFailed(new TransporterDisconnectedException("Machine disconnected.")); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + + _activeHandlers.Clear(); + } + + private async void _machineProvider_MachineConnected(object sender, MachineConnectedEventArgs e) + { + if (EnableWebRTC) + { + try + { + IsWebRtcAvailable = false; + + await _machineProvider.MachineOperator.SendGenericRequest(new InitWebRtcRequest() + { + DataChannelName = WEB_RTC_CHANNEL_NAME + }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(60), + Priority = QueuePriority.Low + }); + + _webRtcTransporter = new BasicTransporter(new WebRtcTransportAdapter(_machineProvider.MachineOperator, WebRtcTransportAdapterMode.Active, WEB_RTC_CHANNEL_NAME)); + _webRtcTransporter.UseKeepAlive = false; + _webRtcTransporter.ComponentName = "File System Active WebRTC Transporter"; + await _webRtcTransporter.Connect(); + + IsWebRtcAvailable = true; + + LogManager.Log("FileSystem via WebRTC is ready."); + } + catch (Exception ex) + { + IsWebRtcAvailable = false; + EnableWebRTC = false; + + LogManager.Log(ex, "Error initializing FileSystem via WebRTC."); + } + } } public async Task GetFolder(string path) @@ -100,6 +182,8 @@ namespace Tango.FSE.UI.FileSystem } }); + _activeHandlers.Add(handler); + ThreadFactory.StartNew(async () => { try @@ -135,11 +219,13 @@ namespace Tango.FSE.UI.FileSystem } catch (Exception ex) { + _activeHandlers.Remove(handler); handler.RaiseFailed(ex); return; } long position = 0; + bool webRtcFailed = false; var tempFile = TemporaryManager.CreateFile(); @@ -153,13 +239,32 @@ namespace Tango.FSE.UI.FileSystem try { - var response = await _machineProvider.MachineOperator.SendGenericRequest( - new ChunkDownloadRequest() + ChunkDownloadResponse response = null; + ChunkDownloadRequest request = new ChunkDownloadRequest() { - MaxChunkSize = 1024 * 10, + MaxChunkSize = MAX_CHUNK_SIZE, OperationId = operationId, Position = position, - }, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(30), Priority = QueuePriority.Low }); + }; + + if (_webRtcTransporter != null && _webRtcTransporter.State == TransportComponentState.Connected && EnableWebRTC && !webRtcFailed) + { + try + { + request.MaxChunkSize = MAX_CHUNK_SIZE_WEB_RTC; + response = await _webRtcTransporter.SendGenericRequest(request, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(30), Priority = QueuePriority.Low }); + } + catch (Exception ex) + { + webRtcFailed = true; + LogManager.Log(ex, "WebRTC chunk download failed. Falling back to standard download..."); + continue; + } + } + else + { + response = await _machineProvider.MachineOperator.SendGenericRequest(request, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(30), Priority = QueuePriority.Low }); + } using (FileStream fs = new FileStream(tempFile, FileMode.Append)) { @@ -171,6 +276,7 @@ namespace Tango.FSE.UI.FileSystem } catch (Exception ex) { + _activeHandlers.Remove(handler); tempFile.Delete(); handler.RaiseFailed(ex); return; @@ -203,6 +309,8 @@ namespace Tango.FSE.UI.FileSystem { tempFile.Delete(); } + + _activeHandlers.Remove(handler); }); return Task.FromResult(handler); @@ -212,5 +320,84 @@ namespace Tango.FSE.UI.FileSystem { throw new NotImplementedException(); } + + public async Task Copy(FileSystemItem source, FileSystemItem target) + { + if (source.Type == FileSystemItemType.Drive) + { + throw new NotSupportedException("The source file system item is not supported for copying."); + } + if (target.Type == FileSystemItemType.File) + { + throw new NotSupportedException("The target file system item is not a valid container."); + } + + await _machineProvider.MachineOperator.SendGenericRequest(new CopyRequest() + { + + Source = source.Path, + Destination = Path.Combine(target.Path, source.Name) + + }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(120), + }); + } + + public async Task Move(FileSystemItem source, FileSystemItem target) + { + if (source.Type == FileSystemItemType.Drive) + { + throw new NotSupportedException("The source file system item is not supported for copying."); + } + if (target.Type == FileSystemItemType.File) + { + throw new NotSupportedException("The target file system item is not a valid container."); + } + + await _machineProvider.MachineOperator.SendGenericRequest(new MoveRequest() + { + + Source = source.Path, + Destination = Path.Combine(target.Path, source.Name) + + }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(120), + }); + } + + public async Task Rename(FileSystemItem source, string newName) + { + if (source.Type == FileSystemItemType.Drive) + { + throw new NotSupportedException("The source file system item is not supported for copying."); + } + if (newName.ToList().Exists(x => Path.GetInvalidFileNameChars().Contains(x))) + { + throw new ArgumentException("The new name contains invalid characters."); + } + + await _machineProvider.MachineOperator.SendGenericRequest(new MoveRequest() + { + + Source = source.Path, + Destination = Path.Combine(Path.GetDirectoryName(source.Path), newName) + + }); + } + + public async Task Delete(FileSystemItem item) + { + if (item.Type == FileSystemItemType.Drive) + { + throw new NotSupportedException("The source file system item is not supported for deletion."); + } + + await _machineProvider.MachineOperator.SendGenericRequest(new DeleteRequest() + { + Path = item.Path + }, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(120) }); + } } } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Navigation/DefaultNavigationManager.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/Navigation/DefaultNavigationManager.cs index cf0b9d895..cdb328de2 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Navigation/DefaultNavigationManager.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Navigation/DefaultNavigationManager.cs @@ -172,7 +172,7 @@ namespace Tango.FSE.UI.Navigation if (_currentVM != null && _currentVM is INavigationBlocker) { - if (!await(_currentVM as INavigationBlocker).OnNavigateOutRequest()) + if (!await (_currentVM as INavigationBlocker).OnNavigateOutRequest()) { return false; } @@ -273,7 +273,16 @@ namespace Tango.FSE.UI.Navigation _lastFullPath = fullPath; - MainView.Instance.NavigationControl.NavigateTo(NavigationView.LayoutView.ToString()); + if (MainView.Instance.NavigationControl.GetSelectedElementNavigationName() != NavigationView.LayoutView.ToString()) + { + NotifyOnBeforeNavigated(null, LayoutView.Instance.DataContext); + + MainView.Instance.NavigationControl.NavigateTo(NavigationView.LayoutView.ToString(), () => + { + NotifyOnNavigated(null, LayoutView.Instance.DataContext); + }); + } + var navigationControl = LayoutView.Instance.NavigationControl; CurrentModule = module; var moduleView = navigationControl.NavigateTo(module.Name); diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Notifications/DefaultNotificationProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/Notifications/DefaultNotificationProvider.cs index 40d2e58e7..666619b59 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Notifications/DefaultNotificationProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Notifications/DefaultNotificationProvider.cs @@ -127,6 +127,32 @@ namespace Tango.FSE.UI.Notifications } } + private InputBoxVM _currentInputBox; + /// + /// Gets the current input box. + /// + public InputBoxVM CurrentInputBox + { + get { return _currentInputBox; } + private set + { + _currentInputBox = value; + RaisePropertyChangedAuto(); + RaisePropertyChanged(nameof(HasInputBox)); + } + } + + /// + /// Gets a value indicating whether this instance has input box. + /// + public bool HasInputBox + { + get + { + return CurrentInputBox != null; + } + } + private FrameworkElement _currentDialog; /// /// Gets the current dialog if any. @@ -285,6 +311,45 @@ namespace Tango.FSE.UI.Notifications return source.Task; } + public Task ShowInputBox(string title, string message, PackIconKind icon = PackIconKind.InformationOutline, string defaultInput = null, string inputHint = null, int? maxChars = null, string okText = null, string cancelText = null) + { + TaskCompletionSource source = new TaskCompletionSource(); + + InputBoxVM vm = new InputBoxVM(); + vm.Title = title; + vm.Message = message; + vm.Icon = icon; + vm.Input = defaultInput; + vm.InputHint = inputHint; + if (maxChars != null) + { + vm.MaxCharacters = maxChars.Value; + } + if (okText != null) + { + vm.OKText = okText; + } + if (cancelText != null) + { + vm.CancelText = cancelText; + } + + vm.Accepted += () => + { + CurrentInputBox = null; + source.SetResult(new InputBoxResult() { Confirmed = true, Input = vm.Input }); + }; + vm.Canceled += () => + { + CurrentInputBox = null; + source.SetResult(new InputBoxResult() { Confirmed = false, Input = vm.Input }); + }; + + CurrentInputBox = vm; + + return source.Task; + } + /// /// Called when the message box has been closed. /// diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/RemoteDesktop/DefaultRemoteDesktopProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/RemoteDesktop/DefaultRemoteDesktopProvider.cs index 623b819d2..bb1fa9b58 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/RemoteDesktop/DefaultRemoteDesktopProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/RemoteDesktop/DefaultRemoteDesktopProvider.cs @@ -123,7 +123,7 @@ namespace Tango.FSE.UI.RemoteDesktop _machineProvider.MachineOperator.RegisterRequestHandler(OnIceCandidateRequestReceived); } - private async void OnIceCandidateRequestReceived(WebRtcIceCandidateRequest request, string token) + private async void OnIceCandidateRequestReceived(ITransporter transporter, WebRtcIceCandidateRequest request, string token) { LogManager.Log("Ice candidate request received from the remote peer."); await _machineProvider.MachineOperator.SendGenericResponse(new WebRtcIceCandidateResponse() { }, token); diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/SystemInfo/DefaultSystemInfoProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/SystemInfo/DefaultSystemInfoProvider.cs index 9b72b308a..e691939f0 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/SystemInfo/DefaultSystemInfoProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/SystemInfo/DefaultSystemInfoProvider.cs @@ -34,7 +34,7 @@ namespace Tango.FSE.UI.SystemInfo { var response = await MachineProvider.MachineOperator.SendGenericRequest(new GetMachineInformationRequest(), new Transport.TransportRequestConfig() { - Timeout = TimeSpan.FromSeconds(30) + Timeout = TimeSpan.FromSeconds(120) }); _package = response.Package; diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs index 4e6621b9e..9ed73afb2 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs @@ -170,6 +170,12 @@ namespace Tango.FSE.UI.ViewModels MachineProvider.MachineConnected += MachineProvider_MachineConnected; } + public override void OnNavigatedTo() + { + base.OnNavigatedTo(); + this.SetFocus(nameof(ToggleConnectionPaneCommand)); + } + private void DiagnosticsProvider_FrameReceived(object sender, Common.Diagnostics.DiagnosticsFrameReceivedEventArgs e) { Debug.WriteLine("Diagnostics Received..."); diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/LayoutView.xaml b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/LayoutView.xaml index cbe57e8dc..1c3cec748 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/LayoutView.xaml +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/LayoutView.xaml @@ -272,7 +272,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml.cs index ae56ff7db..3d29032b1 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml.cs @@ -60,5 +60,50 @@ namespace Tango.FSE.UI.Views } } } + + private async void GridInputBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (gridInputBox.IsVisible) + { + _previousFocusedElement = Keyboard.FocusedElement as UIElement; + await Task.Delay(100); + txtInput.Focus(); + Keyboard.Focus(txtInput); + txtInput.SelectAll(); + btnInputOK.IsDefault = true; + } + else + { + _previousFocusedElement?.Focus(); + } + } + + private void TxtInput_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + if (btnInputOK.Command != null) + { + if (btnInputOK.Command.CanExecute(null)) + { + btnInputOK.Command.Execute(null); + } + } + + e.Handled = true; + } + else if (e.Key == Key.Escape) + { + if (btnInputCancel.Command != null) + { + if (btnInputCancel.Command.CanExecute(null)) + { + btnInputCancel.Command.Execute(null); + } + } + + e.Handled = true; + } + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs index cf7a21e13..512935b50 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs @@ -13,6 +13,8 @@ using Tango.FileSystem.Network; using Tango.Integration.ExternalBridge; using Tango.PPC.Common.ExternalBridge; using Tango.Transport; +using Tango.Transport.Transporters; +using Tango.WebRTC; namespace Tango.PPC.Common.FileSystem { @@ -42,16 +44,61 @@ namespace Tango.PPC.Common.FileSystem private FileSystemManager _manager; private Dictionary _operations; + private Dictionary _webRtcClients; public bool Enabled { get; set; } = true; + public bool EnableWebRTC { get; set; } = true; public DefaultFileSystemService(IPPCExternalBridgeService externalBridge) { + _webRtcClients = new Dictionary(); _manager = new FileSystemManager(); _operations = new Dictionary(); externalBridge.RegisterRequestHandler(this); } + [ExternalBridgeRequestHandlerMethod(typeof(InitWebRtcRequest))] + public async void OnInitWebRtcRequest(InitWebRtcRequest request, String token, ExternalBridgeReceiver receiver) + { + try + { + if (!EnableWebRTC) + { + await receiver.SendErrorResponse(new InvalidOperationException("The file system service WebRTC channel is disabled on this machine."), token); + return; + } + + if (_webRtcClients.ContainsKey(receiver)) + { + _webRtcClients[receiver].Dispose(); + } + + var webRtcAdapter = new WebRtcTransportAdapter(receiver, WebRtcTransportAdapterMode.Passive, request.DataChannelName); + webRtcAdapter.Ready += (x, e) => + { + LogManager.Log("File System via WebRTC is ready."); + }; + + BasicTransporter webRtcTransporter = new BasicTransporter(webRtcAdapter); + webRtcTransporter = new BasicTransporter(webRtcAdapter); + webRtcTransporter.ComponentName = "File System Passive WebRTC Transporter"; + webRtcTransporter.UseKeepAlive = false; + webRtcTransporter.RegisterRequestHandler(WebRtcChunkDownloadRequestReceived); + await webRtcTransporter.Connect(); + await receiver.SendGenericResponse(new InitWebRtcResponse(), token); + _webRtcClients[receiver] = webRtcTransporter; + } + catch (Exception ex) + { + await receiver.SendErrorResponse(ex, token); + } + } + + private void WebRtcChunkDownloadRequestReceived(ITransporter transporter, ChunkDownloadRequest request, string token) + { + OnChunkDownloadRequest(request, token, transporter); + } + [ExternalBridgeRequestHandlerMethod(typeof(GetFileSystemItemRequest))] public async void OnGetFileSystemItemRequest(GetFileSystemItemRequest request, String token, ExternalBridgeReceiver receiver) { @@ -171,7 +218,7 @@ namespace Tango.PPC.Common.FileSystem } [ExternalBridgeRequestHandlerMethod(typeof(ChunkDownloadRequest))] - public async void OnChunkDownloadRequest(ChunkDownloadRequest request, String token, ExternalBridgeReceiver receiver) + public async void OnChunkDownloadRequest(ChunkDownloadRequest request, String token, ITransporter receiver) { FileSystemOperation operation; _operations.TryGetValue(request.OperationId, out operation); @@ -182,16 +229,26 @@ namespace Tango.PPC.Common.FileSystem return; } - using (FileStream stream = new FileStream(operation.Path, FileMode.Open)) + FileStream stream = null; + + try { + stream = new FileStream(operation.Path, FileMode.Open); stream.Position = request.Position; byte[] data = new byte[Math.Min(request.MaxChunkSize, stream.Length - stream.Position)]; await stream.ReadAsync(data, 0, data.Length); + stream.Dispose(); + stream = null; await receiver.SendGenericResponse(new ChunkDownloadResponse() { Data = data }, token, new TransportResponseConfig() { Priority = QueuePriority.Low }); } + catch (Exception ex) + { + stream?.Dispose(); + await receiver.SendErrorResponse(ex, token); + } } [ExternalBridgeRequestHandlerMethod(typeof(AbortOperationRequest))] @@ -235,9 +292,63 @@ namespace Tango.PPC.Common.FileSystem } } - public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + [ExternalBridgeRequestHandlerMethod(typeof(MoveRequest))] + public async void OnMoveRequest(MoveRequest request, String token, ExternalBridgeReceiver receiver) + { + try + { + _manager.Move(request); + await receiver.SendGenericResponse(new MoveResponse(), token); + } + catch (Exception ex) + { + await receiver.SendErrorResponse(ex, token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(CopyRequest))] + public async void OnCopyRequest(CopyRequest request, String token, ExternalBridgeReceiver receiver) + { + try + { + _manager.Copy(request); + await receiver.SendGenericResponse(new CopyResponse(), token); + } + catch (Exception ex) + { + await receiver.SendErrorResponse(ex, token); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(DeleteRequest))] + public async void OnDeleteRequest(DeleteRequest request, String token, ExternalBridgeReceiver receiver) { + try + { + _manager.Delete(request.Path); + await receiver.SendGenericResponse(new DeleteResponse(), token); + } + catch (Exception ex) + { + await receiver.SendErrorResponse(ex, token); + } + } + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + if (_webRtcClients.ContainsKey(receiver)) + { + try + { + var webRtcTransporter = _webRtcClients[receiver]; + _webRtcClients.Remove(receiver); + webRtcTransporter.Dispose(); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error disposing the WebRTC transporter."); + } + } } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs index 050bb1cd6..6cf3321a3 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs @@ -9,5 +9,6 @@ namespace Tango.PPC.Common.FileSystem public interface IFileSystemService { bool Enabled { get; set; } + bool EnableWebRTC { get; set; } } } diff --git a/Software/Visual_Studio/SideChains/WebRtc.NET/src/conductor.cc b/Software/Visual_Studio/SideChains/WebRtc.NET/src/conductor.cc index c7c4f3ba5..8a2efa438 100644 --- a/Software/Visual_Studio/SideChains/WebRtc.NET/src/conductor.cc +++ b/Software/Visual_Studio/SideChains/WebRtc.NET/src/conductor.cc @@ -516,7 +516,7 @@ namespace Native // A data buffer was successfully received. void Conductor::OnMessage(const webrtc::DataBuffer& buffer) { - LOG(INFO) << __FUNCTION__; + //LOG(INFO) << __FUNCTION__; //Causes low performance when debugging if (buffer.binary) { diff --git a/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs b/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs index 23cac7733..60061780b 100644 --- a/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs +++ b/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs @@ -16,6 +16,7 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using Tango.Core.Commands; using Tango.Core.IO; namespace Tango.FileSystem @@ -28,6 +29,41 @@ namespace Tango.FileSystem private Point _dragOutStartPoint; private bool _isMouseDown; private List _selectedItemsBeforeDrag; + private List _copyItems; + private bool _isCut; + private bool _isAfterContextMenu; + + #region IsCut Attached Property + + /// + /// Determines whether the draggable element is currently being dragged. + /// + public static readonly DependencyProperty IsCutProperty = + DependencyProperty.RegisterAttached("IsCut", + typeof(bool), typeof(FileExplorerControl), + new FrameworkPropertyMetadata(false)); + + /// + /// Sets the IsCut attached property. + /// + /// The element. + /// if set to true [value]. + public static void SetIsCut(FrameworkElement element, bool value) + { + element.SetValue(IsCutProperty, value); + } + + /// + /// Gets the is dragging attached property. + /// + /// The element. + /// + public static bool GetIsCut(FrameworkElement element) + { + return (bool)element.GetValue(IsCutProperty); + } + + #endregion public IFileSystemContainer CurrentItem { @@ -35,7 +71,7 @@ namespace Tango.FileSystem set { SetValue(CurrentItemProperty, value); } } public static readonly DependencyProperty CurrentItemProperty = - DependencyProperty.Register("CurrentItem", typeof(IFileSystemContainer), typeof(FileExplorerControl), new PropertyMetadata(null)); + DependencyProperty.Register("CurrentItem", typeof(IFileSystemContainer), typeof(FileExplorerControl), new PropertyMetadata(null, (d, e) => (d as FileExplorerControl).OnCurrentItemChanged())); public FileSystemItem SelectedItem { @@ -61,6 +97,14 @@ namespace Tango.FileSystem public static readonly DependencyProperty DeleteCommandProperty = DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + public ICommand DeleteCommandInternal + { + get { return (ICommand)GetValue(DeleteCommandInternalProperty); } + set { SetValue(DeleteCommandInternalProperty, value); } + } + public static readonly DependencyProperty DeleteCommandInternalProperty = + DependencyProperty.Register("DeleteCommandInternal", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + public ICommand DropCommand { get { return (ICommand)GetValue(DropCommandProperty); } @@ -77,6 +121,110 @@ namespace Tango.FileSystem public static readonly DependencyProperty DragCommandProperty = DependencyProperty.Register("DragCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + public ICommand CopyCommand + { + get { return (ICommand)GetValue(CopyCommandProperty); } + set { SetValue(CopyCommandProperty, value); } + } + public static readonly DependencyProperty CopyCommandProperty = + DependencyProperty.Register("CopyCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand CutCommand + { + get { return (ICommand)GetValue(CutCommandProperty); } + set { SetValue(CutCommandProperty, value); } + } + public static readonly DependencyProperty CutCommandProperty = + DependencyProperty.Register("CutCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand CopyPasteCommand + { + get { return (ICommand)GetValue(CopyPasteCommandProperty); } + set { SetValue(CopyPasteCommandProperty, value); } + } + public static readonly DependencyProperty CopyPasteCommandProperty = + DependencyProperty.Register("CopyPasteCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand CutPasteCommand + { + get { return (ICommand)GetValue(CutPasteCommandProperty); } + set { SetValue(CutPasteCommandProperty, value); } + } + public static readonly DependencyProperty CutPasteCommandProperty = + DependencyProperty.Register("CutPasteCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand PasteCommandInternal + { + get { return (ICommand)GetValue(PasteCommandInternalProperty); } + set { SetValue(PasteCommandInternalProperty, value); } + } + public static readonly DependencyProperty PasteCommandInternalProperty = + DependencyProperty.Register("PasteCommandInternal", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand SelectAllCommand + { + get { return (ICommand)GetValue(SelectAllCommandProperty); } + set { SetValue(SelectAllCommandProperty, value); } + } + public static readonly DependencyProperty SelectAllCommandProperty = + DependencyProperty.Register("SelectAllCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand DownloadCommandInternal + { + get { return (ICommand)GetValue(DownloadCommandInternalProperty); } + set { SetValue(DownloadCommandInternalProperty, value); } + } + public static readonly DependencyProperty DownloadCommandInternalProperty = + DependencyProperty.Register("DownloadCommandInternal", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand DownloadCommand + { + get { return (ICommand)GetValue(DownloadCommandProperty); } + set { SetValue(DownloadCommandProperty, value); } + } + public static readonly DependencyProperty DownloadCommandProperty = + DependencyProperty.Register("DownloadCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand OpenCommand + { + get { return (ICommand)GetValue(OpenCommandProperty); } + set { SetValue(OpenCommandProperty, value); } + } + public static readonly DependencyProperty OpenCommandProperty = + DependencyProperty.Register("OpenCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand BackCommand + { + get { return (ICommand)GetValue(BackCommandProperty); } + set { SetValue(BackCommandProperty, value); } + } + public static readonly DependencyProperty BackCommandProperty = + DependencyProperty.Register("BackCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand RenameCommand + { + get { return (ICommand)GetValue(RenameCommandProperty); } + set { SetValue(RenameCommandProperty, value); } + } + public static readonly DependencyProperty RenameCommandProperty = + DependencyProperty.Register("RenameCommand", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public ICommand RenameCommandInternal + { + get { return (ICommand)GetValue(RenameCommandInternalProperty); } + set { SetValue(RenameCommandInternalProperty, value); } + } + public static readonly DependencyProperty RenameCommandInternalProperty = + DependencyProperty.Register("RenameCommandInternal", typeof(ICommand), typeof(FileExplorerControl), new PropertyMetadata(null)); + + public bool IsContextMenuOpened + { + get { return (bool)GetValue(IsContextMenuOpenedProperty); } + set { SetValue(IsContextMenuOpenedProperty, value); } + } + public static readonly DependencyProperty IsContextMenuOpenedProperty = + DependencyProperty.Register("IsContextMenuOpened", typeof(bool), typeof(FileExplorerControl), new PropertyMetadata(false, (d, e) => (d as FileExplorerControl).OnIsContextMenuOpenedChanged())); + public ImageSource DriveIcon { get { return (ImageSource)GetValue(DriveIconProperty); } @@ -124,7 +272,138 @@ namespace Tango.FileSystem public FileExplorerControl() { + Focusable = true; + + _copyItems = new List(); _selectedItemsBeforeDrag = new List(); + + CopyCommand = new RelayCommand(() => + { + ResetItemsCut(); + _copyItems.Clear(); + _copyItems.AddRange(SelectedItems.ToList()); + _isCut = false; + }, () => SelectedItems != null && SelectedItems.Count > 0); + + CutCommand = new RelayCommand(() => + { + _copyItems.Clear(); + _copyItems.AddRange(SelectedItems.ToList()); + CutSelectedItems(); + _isCut = true; + + }, () => SelectedItems != null && SelectedItems.Count > 0); + + PasteCommandInternal = new RelayCommand(() => + { + if (_isCut) + { + CutPasteCommand?.Execute(_copyItems.ToList()); + } + else + { + CopyPasteCommand?.Execute(_copyItems.ToList()); + } + + ResetItemsCut(); + _copyItems.Clear(); + }, () => _copyItems.Count > 0); + + SelectAllCommand = new RelayCommand(() => + { + ResetItemsCut(); + _copyItems.Clear(); + SelectedItems.Clear(); + + if (CurrentItem != null) + { + foreach (var item in CurrentItem.Items) + { + SelectedItems.Add(item); + } + } + }); + + DownloadCommandInternal = new RelayCommand(() => + { + + DownloadCommand?.Execute(SelectedItems.ToList()); + + }, () => SelectedItems != null && SelectedItems.Count > 0 && SelectedItems.All(x => x.Type != FileSystemItemType.Drive)); + + OpenCommand = new RelayCommand(() => + { + ItemDoubleClickedCommand?.Execute(SelectedItems.FirstOrDefault()); + }, () => SelectedItems != null && SelectedItems.Count == 1); + + DeleteCommandInternal = new RelayCommand(() => + { + + DeleteCommand?.Execute(SelectedItems.ToList()); + + }, () => SelectedItems != null && SelectedItems.Count > 1 && SelectedItems.All(x => x.Type != FileSystemItemType.Drive)); + + RenameCommandInternal = new RelayCommand(() => + { + + RenameCommand?.Execute(SelectedItems.FirstOrDefault()); + + }, () => SelectedItems != null && SelectedItems.Count == 1 && SelectedItems.All(x => x.Type != FileSystemItemType.Drive)); + } + + private void OnIsContextMenuOpenedChanged() + { + _isMouseDown = false; + (PasteCommandInternal as RelayCommand)?.RaiseCanExecuteChanged(); + (CutCommand as RelayCommand)?.RaiseCanExecuteChanged(); + (CopyCommand as RelayCommand)?.RaiseCanExecuteChanged(); + (DownloadCommandInternal as RelayCommand)?.RaiseCanExecuteChanged(); + (OpenCommand as RelayCommand)?.RaiseCanExecuteChanged(); + (DeleteCommandInternal as RelayCommand)?.RaiseCanExecuteChanged(); + (RenameCommandInternal as RelayCommand)?.RaiseCanExecuteChanged(); + + if (IsContextMenuOpened) + { + _isAfterContextMenu = true; + } + } + + private void ResetItemsCut() + { + foreach (var item in _listBox.Items) + { + var element = _listBox.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement; + if (element != null) + { + SetIsCut(element, false); + } + + element = _datagrid.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement; + if (element != null) + { + SetIsCut(element, false); + } + } + } + + private void CutSelectedItems() + { + ResetItemsCut(); + + foreach (var item in SelectedItems.ToList()) + { + var element = _listBox.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement; + if (element != null) + { + SetIsCut(element, true); + } + + element = _datagrid.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement; + if (element != null) + { + SetIsCut(element, true); + } + } } public override void OnApplyTemplate() @@ -138,17 +417,77 @@ namespace Tango.FileSystem _datagrid.SelectionChanged += _datagrid_SelectionChanged; } - protected override void OnPreviewKeyUp(KeyEventArgs e) + protected override void OnPreviewKeyDown(KeyEventArgs e) { - base.OnPreviewKeyUp(e); + base.OnPreviewKeyDown(e); if (e.Key == Key.Delete) { - if (SelectedItems != null && SelectedItems.Count > 0) + if (DeleteCommandInternal != null && DeleteCommandInternal.CanExecute(null)) + { + DeleteCommandInternal?.Execute(null); + } + } + else if (e.Key == Key.Return) + { + if (OpenCommand != null && OpenCommand.CanExecute(null)) + { + OpenCommand?.Execute(null); + } + } + else if (e.Key == Key.C && Keyboard.IsKeyDown(Key.LeftCtrl)) + { + if (CopyCommand != null && CopyCommand.CanExecute(null)) { - DeleteCommand?.Execute(SelectedItems); + CopyCommand.Execute(null); } } + else if (e.Key == Key.X && Keyboard.IsKeyDown(Key.LeftCtrl)) + { + if (CutCommand != null && CutCommand.CanExecute(null)) + { + CutCommand.Execute(null); + } + } + else if (e.Key == Key.V && Keyboard.IsKeyDown(Key.LeftCtrl)) + { + if (PasteCommandInternal != null && PasteCommandInternal.CanExecute(null)) + { + PasteCommandInternal.Execute(null); + } + } + else if (e.Key == Key.A && Keyboard.IsKeyDown(Key.LeftCtrl)) + { + if (SelectAllCommand != null && SelectAllCommand.CanExecute(null)) + { + SelectAllCommand.Execute(null); + } + } + else if (e.Key == Key.D && Keyboard.IsKeyDown(Key.LeftCtrl)) + { + if (DownloadCommandInternal != null && DownloadCommandInternal.CanExecute(null)) + { + DownloadCommandInternal.Execute(null); + } + } + else if (e.Key == Key.F2) + { + if (RenameCommandInternal != null && RenameCommandInternal.CanExecute(null)) + { + RenameCommandInternal.Execute(null); + } + } + else if (e.Key == Key.Down) + { + if (SelectedItems != null && SelectedItems.Count == 0 && CurrentItem != null && CurrentItem.Items.Count > 0) + { + SelectedItems.Add(CurrentItem.Items.FirstOrDefault()); + } + } + else if (e.Key == Key.Back) + { + BackCommand?.Execute(null); + } } private void _datagrid_SelectionChanged(object sender, SelectionChangedEventArgs e) @@ -279,6 +618,12 @@ namespace Tango.FileSystem { base.OnPreviewMouseLeftButtonDown(e); + if (_isAfterContextMenu) + { + _isAfterContextMenu = false; + return; + } + if (!AllowDrag) return; if (e.OriginalSource is FrameworkElement) @@ -309,7 +654,7 @@ namespace Tango.FileSystem { base.OnPreviewMouseMove(e); - if (_isMouseDown) + if (_isMouseDown && !IsContextMenuOpened) { Point mpos = e.GetPosition(null); Vector diff = this._dragOutStartPoint - mpos; @@ -375,7 +720,7 @@ namespace Tango.FileSystem } //Notify to user with all items! - Dispatcher.BeginInvoke(new Action(() => + Dispatcher.BeginInvoke(new Action(() => { DragCommand?.Execute(notifyItems); })); @@ -400,7 +745,16 @@ namespace Tango.FileSystem } string[] files = dropItems.Select(x => x.Item2.Path).ToArray(); - var ef = DragDrop.DoDragDrop(this, new DataObject(DataFormats.FileDrop, files, false), DragDropEffects.Copy); + + try + { + var ef = DragDrop.DoDragDrop(this, new DataObject(DataFormats.FileDrop, files, false), DragDropEffects.Copy); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + Debugger.Break(); + } await Task.Delay(3000); @@ -419,5 +773,15 @@ namespace Tango.FileSystem } } } + + private async void OnCurrentItemChanged() + { + if (IsVisible) + { + await Task.Delay(100); + this.Focus(); + Keyboard.Focus(this); + } + } } } diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs index 536409f63..c8b2fce32 100644 --- a/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemItem.cs @@ -5,13 +5,19 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Media.Imaging; +using Tango.Core; using Tango.FileSystem.Network; namespace Tango.FileSystem { - public abstract class FileSystemItem + public abstract class FileSystemItem : ExtendedObject { - public String Path { get; set; } + private String _path; + public String Path + { + get { return _path; } + set { _path = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(Name)); } + } public FileSystemItemType Type { get; protected set; } diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs index 44c8f1901..b8e59c322 100644 --- a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Tango.Core.Helpers; using Tango.FileSystem.Network; namespace Tango.FileSystem @@ -106,5 +107,50 @@ namespace Tango.FileSystem throw new FileNotFoundException("Could not locate the specified file or directory."); } } + + public void Move(MoveRequest request) + { + if (Directory.Exists(request.Destination)) + { + throw new IOException($"'{Path.GetFileName(request.Destination)}' already exists on the target folder."); + } + + if (File.Exists(request.Source)) + { + File.Move(request.Source, request.Destination); + } + else if (Directory.Exists(request.Source)) + { + Directory.Move(request.Source, request.Destination); + } + else + { + throw new FileNotFoundException("Could not locate the source file or folder."); + } + } + + public void Copy(CopyRequest request) + { + if (File.Exists(request.Source)) + { + if (request.Source == request.Destination) + { + while (File.Exists(request.Destination)) + { + request.Destination = Path.Combine(Path.GetDirectoryName(request.Destination), Path.GetFileNameWithoutExtension(request.Destination)) + " copy" + Path.GetExtension(request.Destination); + } + } + File.Copy(request.Source, request.Destination, true); + } + else if (Directory.Exists(request.Source)) + { + Directory.CreateDirectory(Path.GetDirectoryName(request.Destination)); + PathHelper.CopyDirectory(request.Source, request.Destination, true); + } + else + { + throw new FileNotFoundException("Could not locate the source file or folder."); + } + } } } diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/CopyRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/CopyRequest.cs new file mode 100644 index 000000000..2e7b8a406 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/CopyRequest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class CopyRequest + { + public String Source { get; set; } + public String Destination { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/CopyResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/CopyResponse.cs new file mode 100644 index 000000000..e22ce6542 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/CopyResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class CopyResponse + { + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/DeleteRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/DeleteRequest.cs new file mode 100644 index 000000000..300acdb09 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/DeleteRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class DeleteRequest + { + public String Path { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/DeleteResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/DeleteResponse.cs new file mode 100644 index 000000000..37afdab0b --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/DeleteResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class DeleteResponse + { + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/InitWebRtcRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/InitWebRtcRequest.cs new file mode 100644 index 000000000..5d8f1eb3a --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/InitWebRtcRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class InitWebRtcRequest + { + public String DataChannelName { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/InitWebRtcResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/InitWebRtcResponse.cs new file mode 100644 index 000000000..3425a9096 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/InitWebRtcResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class InitWebRtcResponse + { + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/MoveRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/MoveRequest.cs new file mode 100644 index 000000000..0d9f593d3 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/MoveRequest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class MoveRequest + { + public String Source { get; set; } + public String Destination { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/MoveResponse.cs b/Software/Visual_Studio/Tango.FileSystem/Network/MoveResponse.cs new file mode 100644 index 000000000..05d78c573 --- /dev/null +++ b/Software/Visual_Studio/Tango.FileSystem/Network/MoveResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FileSystem.Network +{ + public class MoveResponse + { + } +} diff --git a/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj b/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj index 733493f02..a1218f12d 100644 --- a/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj +++ b/Software/Visual_Studio/Tango.FileSystem/Tango.FileSystem.csproj @@ -58,6 +58,14 @@ + + + + + + + + MSBuild:Compile diff --git a/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml b/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml index f793be947..9cc27c7c6 100644 --- a/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml +++ b/Software/Visual_Studio/Tango.FileSystem/Themes/Generic.xaml @@ -21,6 +21,13 @@ + + + + + + + @@ -123,7 +130,7 @@ - + @@ -149,7 +156,7 @@ - + @@ -158,7 +165,7 @@ - + @@ -167,7 +174,7 @@ - + diff --git a/Software/Visual_Studio/Tango.SharedUI/Controls/NavigationControl.cs b/Software/Visual_Studio/Tango.SharedUI/Controls/NavigationControl.cs index ce1fca7ac..c2d544042 100644 --- a/Software/Visual_Studio/Tango.SharedUI/Controls/NavigationControl.cs +++ b/Software/Visual_Studio/Tango.SharedUI/Controls/NavigationControl.cs @@ -728,6 +728,11 @@ namespace Tango.SharedUI.Controls return element; } + public String GetSelectedElementNavigationName() + { + return GetNavigationName(SelectedElement); + } + /// /// This method needs to be called in order for // the element to print visibly at the correct size. @@ -750,7 +755,6 @@ namespace Tango.SharedUI.Controls catch { } } } - #endregion } } diff --git a/Software/Visual_Studio/Tango.SystemInfo/WMIReader.cs b/Software/Visual_Studio/Tango.SystemInfo/WMIReader.cs index d7d909089..2d8c95aae 100644 --- a/Software/Visual_Studio/Tango.SystemInfo/WMIReader.cs +++ b/Software/Visual_Studio/Tango.SystemInfo/WMIReader.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Text; using System.Management; +using System.Diagnostics; namespace Tango.SystemInfo { @@ -45,13 +46,16 @@ namespace Tango.SystemInfo Value = item[property].ToString() }); } - catch (SystemException) { /* ignore error */ } + catch (SystemException) + { + //Debug.WriteLine($"System Exception on {className}, {property}"); + } } } } catch (ManagementException e) { - /* Do Nothing */ + //Debug.WriteLine($"Management Exception on {className}"); } return hardwareList; diff --git a/Software/Visual_Studio/Tango.Transport/ITransporter.cs b/Software/Visual_Studio/Tango.Transport/ITransporter.cs index af80f6b1e..1187b2684 100644 --- a/Software/Visual_Studio/Tango.Transport/ITransporter.cs +++ b/Software/Visual_Studio/Tango.Transport/ITransporter.cs @@ -13,7 +13,7 @@ using System.Collections.ObjectModel; namespace Tango.Transport { - public delegate void RequestHandlerCallbackDelegate(Request request, String token); + public delegate void RequestHandlerCallbackDelegate(ITransporter transporter, Request request, String token); /// /// Represents a transportation engine which can send and receive message using a Transport adapter. diff --git a/Software/Visual_Studio/Tango.Transport/TransporterBase.cs b/Software/Visual_Studio/Tango.Transport/TransporterBase.cs index 3672baf63..11ce20b0a 100644 --- a/Software/Visual_Studio/Tango.Transport/TransporterBase.cs +++ b/Software/Visual_Studio/Tango.Transport/TransporterBase.cs @@ -37,7 +37,7 @@ namespace Tango.Transport private class RequestHandler { public Type RequestType { get; set; } - public Action Callback { get; set; } + public Action Callback { get; set; } } private const int MESSAGE_TOKEN_LENGTH = 36; @@ -316,7 +316,7 @@ namespace Tango.Transport { try { - handler.Callback.Invoke(request, container.Token); + handler.Callback.Invoke(this, request, container.Token); } catch { @@ -339,7 +339,7 @@ namespace Tango.Transport { try { - handler.Callback.Invoke(innerRequest, container.Token); + handler.Callback.Invoke(this, innerRequest, container.Token); } catch { @@ -1216,9 +1216,9 @@ namespace Tango.Transport { RequestHandler handler = new RequestHandler(); handler.RequestType = typeof(Request); - handler.Callback = (obj, token) => + handler.Callback = (transporter, obj, token) => { - callback?.Invoke(obj as Request, token); + callback?.Invoke(transporter, obj as Request, token); }; _requestHandlers.Add(handler); diff --git a/Software/Visual_Studio/Tango.WebRTC/Network/IceCandidateRequest.cs b/Software/Visual_Studio/Tango.WebRTC/Network/IceCandidateRequest.cs new file mode 100644 index 000000000..b65a8d6d4 --- /dev/null +++ b/Software/Visual_Studio/Tango.WebRTC/Network/IceCandidateRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.WebRTC.Network +{ + public class IceCandidateRequest + { + public IceCandidate IceCandidate { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.WebRTC/Network/IceCandidateResponse.cs b/Software/Visual_Studio/Tango.WebRTC/Network/IceCandidateResponse.cs new file mode 100644 index 000000000..a9daad732 --- /dev/null +++ b/Software/Visual_Studio/Tango.WebRTC/Network/IceCandidateResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.WebRTC.Network +{ + public class IceCandidateResponse + { + } +} diff --git a/Software/Visual_Studio/Tango.WebRTC/Network/OfferRequest.cs b/Software/Visual_Studio/Tango.WebRTC/Network/OfferRequest.cs new file mode 100644 index 000000000..0d82310db --- /dev/null +++ b/Software/Visual_Studio/Tango.WebRTC/Network/OfferRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.WebRTC.Network +{ + public class OfferRequest + { + public Offer Offer { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.WebRTC/Network/OfferResponse.cs b/Software/Visual_Studio/Tango.WebRTC/Network/OfferResponse.cs new file mode 100644 index 000000000..2207c31a6 --- /dev/null +++ b/Software/Visual_Studio/Tango.WebRTC/Network/OfferResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.WebRTC.Network +{ + public class OfferResponse + { + public Answer Answer { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.WebRTC/Tango.WebRTC.csproj b/Software/Visual_Studio/Tango.WebRTC/Tango.WebRTC.csproj index d9cea42dc..5edff1e71 100644 --- a/Software/Visual_Studio/Tango.WebRTC/Tango.WebRTC.csproj +++ b/Software/Visual_Studio/Tango.WebRTC/Tango.WebRTC.csproj @@ -48,11 +48,18 @@ + + + + + + + @@ -63,6 +70,14 @@ {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} Tango.Core + + {BC932DBD-7CDB-488C-99E4-F02CF441F55E} + Tango.Logging + + + {74e700b0-1156-4126-be40-ee450d3c3026} + Tango.Transport + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapter.cs b/Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapter.cs new file mode 100644 index 000000000..850ddb3de --- /dev/null +++ b/Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapter.cs @@ -0,0 +1,307 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tango.Core; +using Tango.Core.Threading; +using Tango.Transport; +using Tango.Transport.Adapters; +using Tango.WebRTC.Network; + +namespace Tango.WebRTC +{ + public class WebRtcTransportAdapter : TransportAdapterBase + { + private WebRtcClient _client; + private bool _answerReceived; + private List _queuedIceCandidates; + + public event EventHandler Ready; + + public ITransporter SignalingTransporter { get; set; } + + public WebRtcTransportAdapterMode Mode { get; set; } + + public String DataChannelName { get; set; } + + public WebRtcTransportAdapter(ITransporter signalingTransporter, WebRtcTransportAdapterMode mode) : this(signalingTransporter, mode, null) + { + + } + + public WebRtcTransportAdapter(ITransporter signalingTransporter, WebRtcTransportAdapterMode mode, String dataChannelName) + { + SignalingTransporter = signalingTransporter; + Mode = mode; + DataChannelName = dataChannelName; + Address = dataChannelName; + ComponentName = $"WebRTC Adapter {_component_counter++}"; + + SignalingTransporter.RegisterRequestHandler(OnIceCandidateRequestReceived); + SignalingTransporter.RegisterRequestHandler(OnOfferRequestReceived); + } + + public override void Write(byte[] data, bool immidiate = false) + { + ThrowIfDisposed(); + + try + { + _client.SendBinary(data); + } + catch (Exception ex) + { + OnFailed(ex); + } + } + + public override Task Connect() + { + ThrowIfDisposed(); + + TaskCompletionSource completionSource = new TaskCompletionSource(); + bool completed = false; + + _queuedIceCandidates = new List(); + _answerReceived = false; + + ThreadFactory.StartNew(async () => + { + if (State != TransportComponentState.Connected) + { + try + { + _client = new WebRtcClient(); + + if (DataChannelName != null) + { + _client.DataChannelName = DataChannelName; + } + + Address = _client.DataChannelName; + + _client.NewIceCandidate += WebRtc_NewIceCandidate; + _client.Disconnected += WebRtc_Disconnected; + _client.BinaryMessageReceived += WebRtc_BinaryMessageReceived; + _client.Ready += (x, e) => + { + if (!completed) + { + LogManager.Log("WebRTC Active Transport Adapter is ready."); + completed = true; + State = TransportComponentState.Connected; + completionSource.SetResult(true); + Ready?.Invoke(this, new EventArgs()); + } + + if (Mode == WebRtcTransportAdapterMode.Passive) + { + LogManager.Log("WebRTC Passive Transport Adapter is ready."); + Ready?.Invoke(this, new EventArgs()); + } + }; + + LogManager.Log("Initializing WebRTC client..."); + await _client.Init(); + + if (Mode == WebRtcTransportAdapterMode.Active) + { + LogManager.Log("Creating WebRTC offer..."); + var offer = await _client.CreateOffer(); + + LogManager.Log("Sending WebRTC offer via signaling transporter..."); + var response = await SignalingTransporter.SendGenericRequest(new OfferRequest() { Offer = offer }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(30), + ShouldLog = true + }); + + LogManager.Log("WebRTC offer sent and responded with an answer. Setting WebRTC answer..."); + _client.SetAnswer(response.Answer); + _answerReceived = true; + + foreach (var ice in _queuedIceCandidates.ToList()) + { + LogManager.Log("Sending existing ice candidate..."); + + try + { + await SignalingTransporter.SendGenericRequest(new IceCandidateRequest() { IceCandidate = ice }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(30), + ShouldLog = true + }); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error sending ice candidate."); + } + } + } + else + { + LogManager.Log("Waiting for offer..."); + completed = true; + State = TransportComponentState.Connected; + completionSource.SetResult(true); + } + } + catch (Exception ex) + { + completionSource.SetException(ex); + } + } + else + { + completionSource.SetResult(true); + } + }); + + if (Mode == WebRtcTransportAdapterMode.Active) + { + TimeoutTask.StartNew(() => + { + if (!completed) + { + completed = true; + completionSource.SetException(new TimeoutException("Could not reach the remote peer using the WebRTC adapter.")); + } + + }, TimeSpan.FromSeconds(30)); + } + + return completionSource.Task; + } + + private void WebRtc_BinaryMessageReceived(object sender, DataMessageReceivedEventArgs e) + { + OnDataAvailable(e.Data); + } + + private async void OnOfferRequestReceived(ITransporter transporter, OfferRequest request, string token) + { + if (Mode == WebRtcTransportAdapterMode.Passive) + { + var answer = await _client.CreateAnswer(request.Offer); + await SignalingTransporter.SendGenericResponse(new OfferResponse() { Answer = answer }, token); + _answerReceived = true; + + foreach (var ice in _queuedIceCandidates.ToList()) + { + LogManager.Log("Sending existing ice candidate..."); + + try + { + await SignalingTransporter.SendGenericRequest(new IceCandidateRequest() { IceCandidate = ice }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(30), + ShouldLog = true + }); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error sending ice candidate to remote peer."); + } + } + } + } + + private async void WebRtc_NewIceCandidate(object sender, NewIceCandidateEventArgs e) + { + try + { + if (_answerReceived) + { + LogManager.Log("New WebRTC candidate available. Sending ice to remote peer..."); + + await SignalingTransporter.SendGenericRequest(new IceCandidateRequest() { IceCandidate = e.IceCandidate }, new TransportRequestConfig() + { + Timeout = TimeSpan.FromSeconds(30), + ShouldLog = true + }); + } + else + { + if (Mode == WebRtcTransportAdapterMode.Active) + { + LogManager.Log("New WebRTC candidate available. Will be sent after an answer is received..."); + } + else + { + LogManager.Log("New WebRTC candidate available. Will be sent after an offer is received..."); + } + + _queuedIceCandidates.Add(e.IceCandidate); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error sending ice candidate to remote peer."); + } + } + + private async void OnIceCandidateRequestReceived(ITransporter transporter, IceCandidateRequest request, string token) + { + try + { + LogManager.Log("Ice candidate request received from the remote peer."); + await SignalingTransporter.SendGenericResponse(new IceCandidateResponse() { }, token); + + LogManager.Log("Adding ice candidate..."); + _client.AddIceCandidate(request.IceCandidate); + LogManager.Log("Ice candidate added."); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error occurred on ice candidate received handling."); + } + } + + private void WebRtc_Disconnected(object sender, EventArgs e) + { + OnFailed(new WebRtcTransportAdapterDisconnectedException("WebRtc Transport Adapter RTC client has disconnected.")); + } + + public override Task Disconnect() + { + TaskCompletionSource completionSource = new TaskCompletionSource(); + + ThreadFactory.StartNew(() => + { + if (State != TransportComponentState.Disconnected) + { + if (_client != null) + { + LogManager.Log("Disposing WebRTC client..."); + + _client.NewIceCandidate -= WebRtc_NewIceCandidate; + _client.Disconnected -= WebRtc_Disconnected; + _client.BinaryMessageReceived -= WebRtc_BinaryMessageReceived; + + try + { + _client.Dispose(); + _client = null; + LogManager.Log("WebRTC client disposed."); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error disposing WebRTC client."); + } + } + + State = TransportComponentState.Disconnected; + completionSource.SetResult(true); + } + else + { + completionSource.SetResult(true); + } + }); + + return completionSource.Task; + } + } +} diff --git a/Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapterDisconnectedException.cs b/Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapterDisconnectedException.cs new file mode 100644 index 000000000..bd82a3233 --- /dev/null +++ b/Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapterDisconnectedException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.WebRTC +{ + public class WebRtcTransportAdapterDisconnectedException : Exception + { + public WebRtcTransportAdapterDisconnectedException(String message) : base(message) + { + + } + } +} diff --git a/Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapterMode.cs b/Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapterMode.cs new file mode 100644 index 000000000..8068697f5 --- /dev/null +++ b/Software/Visual_Studio/Tango.WebRTC/WebRtcTransportAdapterMode.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.WebRTC +{ + public enum WebRtcTransportAdapterMode + { + Active, + Passive + } +} -- cgit v1.3.1 From 5a5b63afd5c4ff1d6a1dbd6996ed0a5a494387d0 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Sun, 22 Mar 2020 14:53:04 +0200 Subject: Working on storage provider. --- .../ViewModels/FileSystemViewVM.cs | 8 +- .../Controls/FileSystemControl.xaml | 4 +- .../FSE/Tango.FSE.Common/FSEViewModel.cs | 7 + .../Tango.FSE.Common/Storage/IStorageProvider.cs | 36 ++ .../FSE/Tango.FSE.Common/Storage/IStorageResult.cs | 13 + .../Tango.FSE.Common/Storage/MultiStorageResult.cs | 24 ++ .../Storage/SingleStorageResult.cs | 19 + .../FSE/Tango.FSE.Common/Tango.FSE.Common.csproj | 4 + .../Tango.FSE.UI/Storage/DefaultStorageProvider.cs | 389 +++++++++++++++++++++ .../FSE/Tango.FSE.UI/Tango.FSE.UI.csproj | 8 + .../FSE/Tango.FSE.UI/ViewModelLocator.cs | 4 + .../FSE/Tango.FSE.UI/ViewModels/LoginViewVM.cs | 5 + .../FSE/Tango.FSE.UI/Views/MainView.xaml | 178 ++++++++++ .../FSE/Tango.FSE.UI/Views/MainView.xaml.cs | 13 + .../Visual_Studio/FSE/Tango.FSE.UI/packages.config | 2 + .../Tango.FileSystem/FileSystemManager.cs | 22 ++ 16 files changed, 732 insertions(+), 4 deletions(-) create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/IStorageProvider.cs create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/IStorageResult.cs create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/MultiStorageResult.cs create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/SingleStorageResult.cs create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.UI/Storage/DefaultStorageProvider.cs (limited to 'Software/Visual_Studio/Tango.FileSystem') diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs index f074294b2..4e2ca1882 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs @@ -336,9 +336,13 @@ namespace Tango.FSE.PPCConsole.ViewModels } } - private void DownloadSelectedItems(List items) + private async void DownloadSelectedItems(List items) { - + var result = await StorageProvider.SelectFolder("Select download destination folder"); + if (result) + { + Debug.WriteLine($"Download to {result.SelectedItem}"); + } } private async void RenameFileSystemItem(FileSystemItem item) diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml index 62de7cf48..fcea26001 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml @@ -93,7 +93,7 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Devices + + + + + + + + + + + Computer + + Desktop + Documents + Application Data + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml.cs index 3d29032b1..b98ad1a52 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml.cs @@ -21,6 +21,7 @@ namespace Tango.FSE.UI.Views public partial class MainView : UserControl { private UIElement _previousFocusedElement; + private object _lastSelectedItem; public static MainView Instance { get; set; } @@ -105,5 +106,17 @@ namespace Tango.FSE.UI.Views e.Handled = true; } } + + private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (listView.SelectedItem != null) + { + _lastSelectedItem = listView.SelectedItem; + } + else + { + listView.SelectedItem = _lastSelectedItem; + } + } } } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/packages.config b/Software/Visual_Studio/FSE/Tango.FSE.UI/packages.config index c795da787..fda2f4d3f 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/packages.config +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/packages.config @@ -14,4 +14,6 @@ + + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs index b8e59c322..c18df97c9 100644 --- a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs @@ -92,6 +92,28 @@ namespace Tango.FileSystem }; } + public Task GetFolder(String path) + { + return Task.Factory.StartNew(() => + { + return FileSystemItem.FromDTO(GetFolder(new GetFileSystemItemRequest() + { + Path = path, + })); + }); + } + + public Task GetFolder(Environment.SpecialFolder specialFolder) + { + return Task.Factory.StartNew(() => + { + return FileSystemItem.FromDTO(GetFolder(new GetFileSystemItemRequest() + { + SpecialFolder = specialFolder + })); + }); + } + public void Delete(String path) { if (Directory.Exists(path)) -- cgit v1.3.1 From 912e8423fbf63a025dac977d5749e60d4c57df68 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Tue, 24 Mar 2020 18:31:31 +0200 Subject: Working on storage. --- .../Controls/FileSystemControl.xaml | 11 +++-- .../Tango.FSE.Common/Storage/IStorageProvider.cs | 5 +++ .../FSE/Tango.FSE.Common/Storage/StorageMode.cs | 16 +++++++ .../FSE/Tango.FSE.Common/Tango.FSE.Common.csproj | 1 + .../Tango.FSE.UI/Storage/DefaultStorageProvider.cs | 50 ++++++++++++++++++--- .../FSE/Tango.FSE.UI/ViewModels/LoginViewVM.cs | 4 +- .../FSE/Tango.FSE.UI/Views/MainView.xaml | 4 +- .../Tango.FileSystem/FileExplorerControl.cs | 51 ++++++++++++++-------- .../Tango.FileSystem/FileSystemManager.cs | 27 +++++++----- .../Network/GetFileSystemItemRequest.cs | 2 + 10 files changed, 131 insertions(+), 40 deletions(-) create mode 100644 Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/StorageMode.cs (limited to 'Software/Visual_Studio/Tango.FileSystem') diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml index fcea26001..eb1007609 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml @@ -93,7 +93,7 @@ - + - + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/IStorageProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/IStorageProvider.cs index 00ec312a5..602e6057c 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/IStorageProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/IStorageProvider.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Controls; using Tango.Core.Commands; using Tango.FileSystem; @@ -19,6 +20,10 @@ namespace Tango.FSE.Common.Storage FileSystemItem CurrentItem { get; set; } String CurrentPath { get; set; } ObservableCollection SelectedItems { get; set; } + FileSystemItem SelectedItem { get; set; } + SelectionMode SelectionMode { get; set; } + StorageMode StorageMode { get; set; } + String Filter { get; set; } List Drives { get; } RelayCommand OKCommand { get; } RelayCommand CancelCommand { get; } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/StorageMode.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/StorageMode.cs new file mode 100644 index 000000000..d2d519b84 --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Storage/StorageMode.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.FSE.Common.Storage +{ + public enum StorageMode + { + OpenFile, + OpenFiles, + SaveFile, + SelectFolder, + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj b/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj index 8c4165245..ce56e7f69 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj @@ -144,6 +144,7 @@ + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Storage/DefaultStorageProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/Storage/DefaultStorageProvider.cs index c37400cf6..9a40888a3 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Storage/DefaultStorageProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Storage/DefaultStorageProvider.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; +using System.Windows.Controls; using System.Windows.Input; using Tango.Core; using Tango.Core.Commands; @@ -65,6 +66,8 @@ namespace Tango.FSE.UI.Storage set { _currentPath = value; RaisePropertyChangedAuto(); } } + public String Filter { get; set; } = "*.*"; + private bool _isBusy; public bool IsBusy { @@ -81,6 +84,27 @@ namespace Tango.FSE.UI.Storage public ObservableCollection SelectedItems { get; set; } + private FileSystemItem _selectedItem; + public FileSystemItem SelectedItem + { + get { return _selectedItem; } + set { _selectedItem = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } + } + + private SelectionMode _selectionMode; + public SelectionMode SelectionMode + { + get { return _selectionMode; } + set { _selectionMode = value; RaisePropertyChangedAuto(); } + } + + private StorageMode _storageMode; + public StorageMode StorageMode + { + get { return _storageMode; } + set { _storageMode = value; RaisePropertyChangedAuto(); } + } + public RelayCommand OKCommand { get; } public RelayCommand CancelCommand { get; } public RelayCommand BackCommand { get; set; } @@ -99,7 +123,7 @@ namespace Tango.FSE.UI.Storage BackCommand = new RelayCommand(NavigateBack, () => !(CurrentItem is FolderItem) || !(CurrentItem as FolderItem).IsRoot); NavigateSpecialFolderCommand = new RelayCommand(NavigateToSpecialFolder); NavigateToFolderCommand = new RelayCommand(async (x) => await Navigate(x)); - OKCommand = new RelayCommand(OnAccept); + OKCommand = new RelayCommand(OnAccept, CanOK); CancelCommand = new RelayCommand(OnCancel); } @@ -152,7 +176,11 @@ namespace Tango.FSE.UI.Storage var root = await _manager.GetFolder(String.Empty); Drives = (root as IFileSystemContainer).Items.Cast().ToList(); - CurrentItem = await _manager.GetFolder(Environment.CurrentDirectory); + StorageMode = StorageMode.SelectFolder; + SelectionMode = SelectionMode.Single; + + await Navigate(Environment.CurrentDirectory); + IsOpened = true; _okCompletionAction = () => @@ -160,7 +188,7 @@ namespace Tango.FSE.UI.Storage SingleStorageResult result = new SingleStorageResult() { Confirmed = true, - SelectedItem = SelectedItems.First().Path, + SelectedItem = CurrentItem.Path, }; _completionSource.SetResult(result); @@ -237,15 +265,15 @@ namespace Tango.FSE.UI.Storage if (path != null) { - CurrentItem = await _manager.GetFolder(path) as FileSystemItem; + CurrentItem = await _manager.GetFolder(path, StorageMode == StorageMode.SelectFolder, Filter) as FileSystemItem; } else if (specialFolder != null) { - CurrentItem = await _manager.GetFolder(specialFolder.Value) as FileSystemItem; + CurrentItem = await _manager.GetFolder(specialFolder.Value, StorageMode == StorageMode.SelectFolder, Filter) as FileSystemItem; } else { - CurrentItem = await _manager.GetFolder(String.Empty) as FileSystemItem; + CurrentItem = await _manager.GetFolder(String.Empty, StorageMode == StorageMode.SelectFolder, Filter) as FileSystemItem; } } catch (Exception ex) @@ -267,6 +295,16 @@ namespace Tango.FSE.UI.Storage InvalidateRelayCommands(); } + private bool CanOK() + { + if (StorageMode == StorageMode.SelectFolder && CurrentItem.Type == FileSystemItemType.Drive) + { + return false; + } + + return true; + } + #endregion #region Native diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LoginViewVM.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LoginViewVM.cs index 8b14ac69e..8622daab2 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LoginViewVM.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LoginViewVM.cs @@ -131,9 +131,9 @@ namespace Tango.FSE.UI.ViewModels try { - //var s = await StorageProvider.SelectFolder("Select download destination folder"); + var s = await StorageProvider.SelectFolder("Select download destination folder"); - //return; + return; if (!Validate()) { diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml index 6a386947f..6941b830a 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml @@ -233,7 +233,7 @@ - + @@ -341,6 +341,8 @@ Mode="{Binding ElementName=listView,Path=SelectedItem.Tag}" CurrentItem="{Binding StorageProvider.CurrentItem,Mode=TwoWay}" SelectedItems="{Binding StorageProvider.SelectedItems}" + SelectedItem="{Binding StorageProvider.SelectedItem}" + SelectionMode="{Binding StorageProvider.SelectionMode}" BackCommand="{Binding StorageProvider.BackCommand}" ItemDoubleClickedCommand="{Binding StorageProvider.OpenCommand}" OpenCommand="{Binding StorageProvider.OpenCommand}"/> diff --git a/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs b/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs index 60061780b..c624178a0 100644 --- a/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs +++ b/Software/Visual_Studio/Tango.FileSystem/FileExplorerControl.cs @@ -265,6 +265,15 @@ namespace Tango.FileSystem public static readonly DependencyProperty AllowDragProperty = DependencyProperty.Register("AllowDrag", typeof(bool), typeof(FileExplorerControl), new PropertyMetadata(null)); + public SelectionMode SelectionMode + { + get { return (SelectionMode)GetValue(SelectionModeProperty); } + set { SetValue(SelectionModeProperty, value); } + } + public static readonly DependencyProperty SelectionModeProperty = + DependencyProperty.Register("SelectionMode", typeof(SelectionMode), typeof(FileExplorerControl), new PropertyMetadata(SelectionMode.Extended)); + + static FileExplorerControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(FileExplorerControl), new FrameworkPropertyMetadata(typeof(FileExplorerControl))); @@ -492,39 +501,45 @@ namespace Tango.FileSystem private void _datagrid_SelectionChanged(object sender, SelectionChangedEventArgs e) { - if (!_preventSynchronization) + if (SelectionMode == SelectionMode.Extended) { - _preventSynchronization = true; + if (!_preventSynchronization) + { + _preventSynchronization = true; - _listBox.SelectedItems.Clear(); + _listBox.SelectedItems.Clear(); - foreach (var item in _datagrid.SelectedItems) - { - _listBox.SelectedItems.Add(item); - } + foreach (var item in _datagrid.SelectedItems) + { + _listBox.SelectedItems.Add(item); + } - SynchronizeSelectedItems(_listBox.SelectedItems); + SynchronizeSelectedItems(_listBox.SelectedItems); - _preventSynchronization = false; + _preventSynchronization = false; + } } } private void _listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { - if (!_preventSynchronization) + if (SelectionMode == SelectionMode.Extended) { - _preventSynchronization = true; + if (!_preventSynchronization) + { + _preventSynchronization = true; - _datagrid.SelectedItems.Clear(); + _datagrid.SelectedItems.Clear(); - foreach (var item in _listBox.SelectedItems) - { - _datagrid.SelectedItems.Add(item); - } + foreach (var item in _listBox.SelectedItems) + { + _datagrid.SelectedItems.Add(item); + } - SynchronizeSelectedItems(_listBox.SelectedItems); + SynchronizeSelectedItems(_listBox.SelectedItems); - _preventSynchronization = false; + _preventSynchronization = false; + } } } diff --git a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs index c18df97c9..f72785f81 100644 --- a/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs +++ b/Software/Visual_Studio/Tango.FileSystem/FileSystemManager.cs @@ -73,15 +73,18 @@ namespace Tango.FileSystem }); } - foreach (var file in Directory.GetFiles(request.Path)) + if (!request.FoldersOnly) { - items.Add(new FileSystemItemDTO() + foreach (var file in Directory.GetFiles(request.Path, request.Filter != null ? request.Filter : "*.*")) { - Path = file, - Type = FileSystemItemType.File, - DateModified = File.GetLastWriteTimeUtc(file), - Size = new FileInfo(file).Length - }); + items.Add(new FileSystemItemDTO() + { + Path = file, + Type = FileSystemItemType.File, + DateModified = File.GetLastWriteTimeUtc(file), + Size = new FileInfo(file).Length + }); + } } return new FileSystemItemDTO() @@ -92,24 +95,28 @@ namespace Tango.FileSystem }; } - public Task GetFolder(String path) + public Task GetFolder(String path, bool foldersOnly = false, String filter = "*.*") { return Task.Factory.StartNew(() => { return FileSystemItem.FromDTO(GetFolder(new GetFileSystemItemRequest() { Path = path, + FoldersOnly = foldersOnly, + Filter = filter, })); }); } - public Task GetFolder(Environment.SpecialFolder specialFolder) + public Task GetFolder(Environment.SpecialFolder specialFolder, bool foldersOnly = false, String filter = "*.*") { return Task.Factory.StartNew(() => { return FileSystemItem.FromDTO(GetFolder(new GetFileSystemItemRequest() { - SpecialFolder = specialFolder + SpecialFolder = specialFolder, + FoldersOnly = foldersOnly, + Filter = filter, })); }); } diff --git a/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs b/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs index 1ed6c19e4..3cf91e869 100644 --- a/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs +++ b/Software/Visual_Studio/Tango.FileSystem/Network/GetFileSystemItemRequest.cs @@ -11,5 +11,7 @@ namespace Tango.FileSystem.Network { public String Path { get; set; } public SpecialFolder? SpecialFolder { get; set; } + public bool FoldersOnly { get; set; } + public String Filter { get; set; } = "*.*"; } } -- cgit v1.3.1 From 42c06402ff6648c356fba8315958283762ed2542 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Wed, 25 Mar 2020 00:26:47 +0200 Subject: Added Download menu implementation to file system. Added several stun and turn servers to web rtc. --- .../ViewModels/FileSystemViewVM.cs | 53 ++++++++++++++++++++++ .../Tango.FSE.PPCConsole/Views/FileSystemView.xaml | 17 ++++++- .../Controls/FileSystemControl.xaml | 11 ++++- .../FileSystem/IFileSystemProvider.cs | 1 + .../FileSystem/DefaultFileSystemProvider.cs | 11 +++++ .../Tango.FSE.UI/Storage/DefaultStorageProvider.cs | 2 +- .../FSE/Tango.FSE.UI/ViewModels/LoginViewVM.cs | 4 +- .../FileSystem/DefaultFileSystemService.cs | 14 ++++++ .../Visual_Studio/PPC/Tango.PPC.UI/app.manifest | 2 +- .../Tango.FileSystem/FileExplorerControl.cs | 53 ++++++++++++++++++++-- .../Tango.FileSystem/FileSystemManager.cs | 17 +++++++ .../Network/CreateFolderRequest.cs | 14 ++++++ .../Network/CreateFolderResponse.cs | 13 ++++++ .../Tango.FileSystem/Tango.FileSystem.csproj | 2 + .../Visual_Studio/Tango.WebRTC/WebRtcClient.cs | 17 +++++++ 15 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/CreateFolderRequest.cs create mode 100644 Software/Visual_Studio/Tango.FileSystem/Network/CreateFolderResponse.cs (limited to 'Software/Visual_Studio/Tango.FileSystem') diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs index 4e2ca1882..e10cc0ad1 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/FileSystemViewVM.cs @@ -58,6 +58,7 @@ namespace Tango.FSE.PPCConsole.ViewModels public RelayCommand> CutPasteCommand { get; set; } public RelayCommand> DownloadCommand { get; set; } public RelayCommand RenameCommand { get; set; } + public RelayCommand NewFolderCommand { get; set; } public FileSystemViewVM() @@ -78,6 +79,7 @@ namespace Tango.FSE.PPCConsole.ViewModels CutPasteCommand = new RelayCommand>((items) => PasteItems(items, true)); DownloadCommand = new RelayCommand>(DownloadSelectedItems); RenameCommand = new RelayCommand(RenameFileSystemItem); + NewFolderCommand = new RelayCommand(CreateNewFolder); } private async void NavigateBack() @@ -341,7 +343,24 @@ namespace Tango.FSE.PPCConsole.ViewModels var result = await StorageProvider.SelectFolder("Select download destination folder"); if (result) { + + String destination = result.SelectedItem; + Debug.WriteLine($"Download to {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); + } } } @@ -376,6 +395,40 @@ namespace Tango.FSE.PPCConsole.ViewModels } } + 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; diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml index d1143ede4..67f1dc1c5 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/FileSystemView.xaml @@ -121,6 +121,7 @@ + RenameCommand="{Binding RenameCommand}" + NewFolderCommand="{Binding NewFolderCommand}"/> @@ -163,7 +165,18 @@ - + + + + + diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml index eb1007609..5bc75ca54 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Controls/FileSystemControl.xaml @@ -49,6 +49,13 @@ + + + + + + + @@ -93,7 +100,7 @@ - + - +