diff options
| author | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-03-22 00:04:44 +0200 |
|---|---|---|
| committer | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-03-22 00:04:44 +0200 |
| commit | d48b2d23515d06a21ad241380986bf8f31773195 (patch) | |
| tree | ebbb6b2bc874773ec58a4c999a1f6eb61a572592 /Software | |
| parent | 8c094ceeaa538fdb5dc1d69b6ac73f8574cecb66 (diff) | |
| download | Tango-d48b2d23515d06a21ad241380986bf8f31773195.tar.gz Tango-d48b2d23515d06a21ad241380986bf8f31773195.zip | |
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.
Diffstat (limited to 'Software')
49 files changed, 1846 insertions, 68 deletions
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<FileSystemHandler> DeleteFileSystemHandlerCommand { get; set; } public RelayCommand<FileSystemHandler> OpenFileSystemHandlerDestinationCommand { get; set; } public RelayCommand<FileSystemHandler> RetryFailedFileSystemHandlerCommand { get; set; } + public RelayCommand<List<FileSystemItem>> CopyPasteCommand { get; set; } + public RelayCommand<List<FileSystemItem>> CutPasteCommand { get; set; } + public RelayCommand<List<FileSystemItem>> DownloadCommand { get; set; } + public RelayCommand<FileSystemItem> RenameCommand { get; set; } public FileSystemViewVM() @@ -69,6 +74,10 @@ namespace Tango.FSE.PPCConsole.ViewModels DeleteFileSystemHandlerCommand = new RelayCommand<FileSystemHandler>(DeleteFileSystemHandler); OpenFileSystemHandlerDestinationCommand = new RelayCommand<FileSystemHandler>(OpenFileSystemHandlerDestination); RetryFailedFileSystemHandlerCommand = new RelayCommand<FileSystemHandler>(RetryFailedFileSystemHandler); + CopyPasteCommand = new RelayCommand<List<FileSystemItem>>((items) => PasteItems(items, false)); + CutPasteCommand = new RelayCommand<List<FileSystemItem>>((items) => PasteItems(items, true)); + DownloadCommand = new RelayCommand<List<FileSystemItem>>(DownloadSelectedItems); + RenameCommand = new RelayCommand<FileSystemItem>(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<FileSystemItem> 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<FileSystemItem> 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 @@ <RowDefinition Height="1*" /> </Grid.RowDefinitions> - <DockPanel> + <DockPanel IsEnabled="{Binding MachineProvider.IsConnected}"> <ListBox x:Name="listView" SelectedIndex="1" Background="{StaticResource FSE_PrimaryBackgroundLightBrush}" SelectionChanged="ListView_SelectionChanged" Style="{StaticResource MaterialDesignToolToggleListBox}" Margin="50 0 0 0" DockPanel.Dock="Right"> <ListBox.Resources> <SolidColorBrush x:Key="MaterialDesignDivider" Color="{StaticResource FSE_PrimaryBackgroundDarkColor}"/> @@ -92,7 +92,7 @@ <ColumnDefinition Width="1*" /> </Grid.ColumnDefinitions> - <Border Background="{StaticResource FSE_PrimaryBackgroundDarkBrush}" BorderBrush="{StaticResource FSE_PrimaryBackgroundLightBrush}" BorderThickness="3" CornerRadius="3" Padding="10 5"> + <Border IsEnabled="{Binding MachineProvider.IsConnected}" Background="{StaticResource FSE_PrimaryBackgroundDarkBrush}" BorderBrush="{StaticResource FSE_PrimaryBackgroundLightBrush}" BorderThickness="3" CornerRadius="3" Padding="10 5"> <StackPanel> <controls:TextIconButton HorizontalContentAlignment="Left" Padding="5" Icon="Computer" Background="Transparent" BorderThickness="0" FocusVisualStyle="{x:Null}">Devices</controls:TextIconButton> <StackPanel Margin="30 0 0 0"> @@ -121,6 +121,7 @@ <RowDefinition Height="100" MinHeight="0" /> </Grid.RowDefinitions> <controls:FileSystemControl + IsEnabled="{Binding MachineProvider.IsConnected}" AllowDrag="True" AllowDrop="True" CurrentItem="{Binding CurrentItem}" @@ -129,20 +130,31 @@ DeleteCommand="{Binding DeleteCommand}" SelectedItems="{Binding SelectedItems}" DragCommand="{Binding DragCommand}" - DropCommand="{Binding DropCommand}"/> + DropCommand="{Binding DropCommand}" + CutPasteCommand="{Binding CutPasteCommand}" + CopyPasteCommand="{Binding CopyPasteCommand}" + DownloadCommand="{Binding DownloadCommand}" + BackCommand="{Binding BackCommand}" + RenameCommand="{Binding RenameCommand}"/> <GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Center" /> <Grid Grid.Row="2"> <DockPanel> - <StackPanel DockPanel.Dock="Top" Margin="5" Orientation="Horizontal"> - <material:PackIcon Kind="Download" /> - <material:PackIcon Margin="-2 0 0 0" Kind="Upload" /> - <TextBlock Margin="5 0 0 0" Foreground="{StaticResource FSE_PrimaryAccentBrush}" FontSize="{StaticResource FSE_SmallFontSize}">Transfer Queue</TextBlock> - <TextBlock Margin="10 0 0 0" Foreground="{StaticResource FSE_GrayBrush}" FontSize="{StaticResource FSE_SmallFontSize}"> + <Grid DockPanel.Dock="Top" > + <StackPanel Margin="5" Orientation="Horizontal" HorizontalAlignment="Left"> + <material:PackIcon Kind="Download" /> + <material:PackIcon Margin="-2 0 0 0" Kind="Upload" /> + <TextBlock Margin="5 0 0 0" Foreground="{StaticResource FSE_PrimaryAccentBrush}" FontSize="{StaticResource FSE_SmallFontSize}">Transfer Queue</TextBlock> + <TextBlock Margin="10 0 0 0" Foreground="{StaticResource FSE_GrayBrush}" FontSize="{StaticResource FSE_SmallFontSize}"> <Run>(</Run><Run Text="{Binding FileSystemHandlers.Count,Mode=OneWay}"></Run><Run>)</Run> - </TextBlock> - </StackPanel> + </TextBlock> + </StackPanel> + <StackPanel VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 10 0" Orientation="Horizontal"> + <material:PackIcon ToolTip="Fast communication channel is available" Visibility="{Binding FileSystemProvider.IsWebRtcAvailable,Converter={StaticResource BooleanToVisibilityConverter}}" Kind="LightningBoltCircle" HorizontalAlignment="Right" Width="18" Height="18" Foreground="{StaticResource FSE_GreenBrush}" /> + <CheckBox Margin="5 0 0 0" IsEnabled="{Binding FileSystemProvider.IsWebRtcAvailable}" FontSize="{StaticResource FSE_SmallFontSize}" IsChecked="{Binding FileSystemProvider.EnableWebRTC}">Enable fast communication channel</CheckBox> + </StackPanel> + </Grid> <Border CornerRadius="3" BorderThickness="3" BorderBrush="{StaticResource FSE_PrimaryBackgroundLightBrush}" Background="{StaticResource FSE_PrimaryBackgroundDarkBrush}"> <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> <ItemsControl ItemsSource="{Binding FileSystemHandlers}" HorizontalContentAlignment="Stretch" FontSize="10" Background="Transparent"> @@ -221,13 +233,13 @@ <controls:IconButton Icon="Restart" Cursor="Hand" Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.RetryFailedFileSystemHandlerCommand}" CommandParameter="{Binding}"> <controls:IconButton.Style> <Style TargetType="controls:IconButton" BasedOn="{StaticResource {x:Type controls:IconButton}}"> - <Setter Property="Visibility" Value="Hidden"></Setter> - <Style.Triggers> - <DataTrigger Binding="{Binding Status}" Value="Failed"> - <Setter Property="Visibility" Value="Visible"></Setter> - </DataTrigger> - </Style.Triggers> - </Style> + <Setter Property="Visibility" Value="Hidden"></Setter> + <Style.Triggers> + <DataTrigger Binding="{Binding Status}" Value="Failed"> + <Setter Property="Visibility" Value="Visible"></Setter> + </DataTrigger> + </Style.Triggers> + </Style> </controls:IconButton.Style> </controls:IconButton> </Grid> @@ -239,11 +251,16 @@ <Grid Margin="10 0 0 0"> <DockPanel> <TextBlock FontSize="{StaticResource FSE_SmallFontSize}" DockPanel.Dock="Top" HorizontalAlignment="Left" Text="{Binding FileSystemItem.Name}" TextTrimming="CharacterEllipsis"></TextBlock> - <TextBlock Foreground="{StaticResource FSE_GrayBrush}" Margin="0 2 0 0" DockPanel.Dock="Top" HorizontalAlignment="Left" TextTrimming="CharacterEllipsis"> - <Run Text="{Binding Position,Mode=OneWay,Converter={StaticResource ByteArrayToFileSizeConverter}}"></Run> - <Run>/</Run> - <Run Text="{Binding Length,Mode=OneWay,Converter={StaticResource ByteArrayToFileSizeConverter}}"></Run> - </TextBlock> + <DockPanel Width="140" TextElement.Foreground="{StaticResource FSE_GrayBrush}" Margin="0 2 0 0" DockPanel.Dock="Top" HorizontalAlignment="Left"> + <TextBlock TextTrimming="CharacterEllipsis"> + <Run Text="{Binding Position,Mode=OneWay,Converter={StaticResource ByteArrayToFileSizeConverter}}"></Run> + <Run>/</Run> + <Run Text="{Binding Length,Mode=OneWay,Converter={StaticResource ByteArrayToFileSizeConverter}}"></Run> + </TextBlock> + <TextBlock HorizontalAlignment="Right"> + <Run Text="{Binding TransferRate,Mode=OneWay,Converter={StaticResource ByteArrayToFileSizeConverter}}"></Run><Run>/s</Run> + </TextBlock> + </DockPanel> <Border VerticalAlignment="Bottom" Height="5" Background="{StaticResource FSE_PrimaryBackgroundBrush}"> <ProgressBar Height="Auto" Minimum="0" Maximum="{Binding Length}" Value="{Binding Position}"> 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"> <converters:ByteArrayToFileSizeConverter x:Key="ByteArrayToFileSizeConverter" /> @@ -9,6 +11,11 @@ <Style TargetType="{x:Type local:FileSystemDataGridRow}" BasedOn="{StaticResource {x:Type DataGridRow}}"> <Setter Property="DoubleClickCommand" Value="{Binding RelativeSource={RelativeSource AncestorType=local:FileExplorerControl},Path=ItemDoubleClickedCommand}"></Setter> + <Style.Triggers> + <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=(local:FileExplorerControl.IsCut)}" Value="True"> + <Setter Property="Opacity" Value="0.3"></Setter> + </DataTrigger> + </Style.Triggers> </Style> <Style TargetType="{x:Type local:FileSystemDataGrid}" BasedOn="{StaticResource {x:Type DataGrid}}"> @@ -27,6 +34,7 @@ <Setter Property="Mode" Value="Details"></Setter> <Setter Property="FocusVisualStyle" Value="{x:Null}"></Setter> <Setter Property="Focusable" Value="True"></Setter> + <Setter Property="Background" Value="Transparent"></Setter> <Setter Property="AllowDrop" Value="True"></Setter> <Setter Property="Template"> <Setter.Value> @@ -34,6 +42,55 @@ <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> + <Border.ContextMenu> + <ContextMenu Width="250" IsOpen="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=IsContextMenuOpened,Mode=TwoWay}"> + <MenuItem Header="Open" InputGestureText="Enter" Command="{TemplateBinding OpenCommand}"> + <MenuItem.Icon> + <material:PackIcon Kind="SubdirectoryArrowRight" /> + </MenuItem.Icon> + </MenuItem> + <MenuItem Header="Copy" InputGestureText="Ctrl+C" Command="{TemplateBinding CopyCommand}"> + <MenuItem.Icon> + <material:PackIcon Kind="ContentCopy" /> + </MenuItem.Icon> + </MenuItem> + <MenuItem Header="Cut" InputGestureText="Ctrl+X" Command="{TemplateBinding CutCommand}"> + <MenuItem.Icon> + <material:PackIcon Kind="ContentCut" /> + </MenuItem.Icon> + </MenuItem> + <Separator/> + <MenuItem Header="Paste" InputGestureText="Ctrl+V" Command="{TemplateBinding PasteCommandInternal}"> + <MenuItem.Icon> + <material:PackIcon Kind="ContentPaste" /> + </MenuItem.Icon> + </MenuItem> + <Separator/> + <MenuItem Header="Rename" InputGestureText="F2" Command="{TemplateBinding RenameCommandInternal}"> + <MenuItem.Icon> + <material:PackIcon Kind="Rename" /> + </MenuItem.Icon> + </MenuItem> + <Separator/> + <MenuItem Header="Delete" InputGestureText="DEL" Command="{TemplateBinding DeleteCommandInternal}"> + <MenuItem.Icon> + <material:PackIcon Kind="DeleteForever" /> + </MenuItem.Icon> + </MenuItem> + <Separator/> + <MenuItem Header="Download" InputGestureText="Ctrl+D" Command="{TemplateBinding DownloadCommandInternal}"> + <MenuItem.Icon> + <material:PackIcon Kind="Download" /> + </MenuItem.Icon> + </MenuItem> + <Separator/> + <MenuItem Header="Select All" InputGestureText="Ctrl+A" Command="{TemplateBinding SelectAllCommand}"> + <MenuItem.Icon> + <material:PackIcon Kind="SelectAll" /> + </MenuItem.Icon> + </MenuItem> + </ContextMenu> + </Border.ContextMenu> <Grid Background="Transparent"> <ListBox x:Name="PART_listbox" Background="Transparent" SelectionMode="Extended" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" ItemsSource="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=CurrentItem.Items}" SelectedItem="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=SelectedItem,Mode=TwoWay}"> @@ -49,8 +106,15 @@ </ListBox.Style> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}"> + <Setter Property="Opacity" Value="1"></Setter> <Setter Property="Padding" Value="5"></Setter> <Setter Property="Margin" Value="0 10 20 10"></Setter> + <Setter Property="FocusVisualStyle" Value="{x:Null}"></Setter> + <Style.Triggers> + <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=(local:FileExplorerControl.IsCut)}" Value="True"> + <Setter Property="Opacity" Value="0.3"></Setter> + </DataTrigger> + </Style.Triggers> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemsPanel> @@ -123,7 +187,7 @@ </DataGrid.Style> <DataGrid.Columns> - <DataGridTemplateColumn Header="NAME" Width="300*"> + <DataGridTemplateColumn Header="NAME" Width="300*" SortMemberPath="Name"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DockPanel Background="Transparent"> @@ -149,7 +213,7 @@ </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> - <DataGridTemplateColumn Header="DATE MODIFIED" Width="170*"> + <DataGridTemplateColumn Header="DATE MODIFIED" Width="170*" SortMemberPath="DateModified"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DockPanel Background="Transparent"> @@ -158,7 +222,7 @@ </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> - <DataGridTemplateColumn Header="TYPE" Width="140*"> + <DataGridTemplateColumn Header="TYPE" Width="140*" SortMemberPath="Type"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DockPanel Background="Transparent"> @@ -167,7 +231,7 @@ </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> - <DataGridTemplateColumn Header="SIZE" Width="100*"> + <DataGridTemplateColumn Header="SIZE" Width="100*" SortMemberPath="Size"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DockPanel Background="Transparent"> 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<IFileSystemContainer> GetFolder(String path); Task<IFileSystemContainer> GetSpecialFolder(SpecialFolder specialFolder); Task<IFileSystemContainer> GetThisPC(); Task<FileSystemHandler> Download(FileSystemItem item, String localTargetFolder); Task<FileSystemHandler> 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; @@ -73,6 +74,16 @@ namespace Tango.FSE.Common.Notifications bool HasMessageBox { get; } /// <summary> + /// Gets the current input box. + /// </summary> + InputBoxVM CurrentInputBox { get; } + + /// <summary> + /// Gets a value indicating whether this instance has input box. + /// </summary> + bool HasInputBox { get; } + + /// <summary> /// Gets the current dialog. /// </summary> FrameworkElement CurrentDialog { get; } @@ -112,6 +123,19 @@ namespace Tango.FSE.Common.Notifications Task ShowSuccess(String message); /// <summary> + /// Shows an input box. + /// </summary> + /// <param name="title">The title.</param> + /// <param name="message">The message.</param> + /// <param name="icon">The icon.</param> + /// <param name="defaultInput">The default input.</param> + /// <param name="inputHint">The input hint.</param> + /// <param name="okText">The ok text.</param> + /// <param name="cancelText">The cancel text.</param> + /// <returns></returns> + Task<InputBoxResult> ShowInputBox(String title, String message, PackIconKind icon = PackIconKind.InfoOutline, String defaultInput = null, String inputHint = null, int? maxChars = null, String okText = null, String cancelText = null); + + /// <summary> /// Shows a question message box. /// </summary> /// <param name="message">The message.</param> 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 @@ <Compile Include="Notifications\AppBarItem.cs" /> <Compile Include="Notifications\AppButton.cs" /> <Compile Include="Notifications\INotificationProvider.cs" /> + <Compile Include="Notifications\InputBoxResult.cs" /> + <Compile Include="Notifications\InputBoxVM.cs" /> <Compile Include="Notifications\ItemBase.cs" /> <Compile Include="Notifications\MessageBoxVM.cs" /> <Compile Include="Notifications\MessageType.cs" /> 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<EventRegistration> _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<FileSystemHandler> _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<FileSystemHandler>(); + + 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<InitWebRtcRequest, InitWebRtcResponse>(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<IFileSystemContainer> 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<ChunkDownloadRequest, ChunkDownloadResponse>( - 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<ChunkDownloadRequest, ChunkDownloadResponse>(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<ChunkDownloadRequest, ChunkDownloadResponse>(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<CopyRequest, CopyResponse>(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<MoveRequest, MoveResponse>(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<MoveRequest, MoveResponse>(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<DeleteRequest, DeleteResponse>(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; + /// <summary> + /// Gets the current input box. + /// </summary> + public InputBoxVM CurrentInputBox + { + get { return _currentInputBox; } + private set + { + _currentInputBox = value; + RaisePropertyChangedAuto(); + RaisePropertyChanged(nameof(HasInputBox)); + } + } + + /// <summary> + /// Gets a value indicating whether this instance has input box. + /// </summary> + public bool HasInputBox + { + get + { + return CurrentInputBox != null; + } + } + private FrameworkElement _currentDialog; /// <summary> /// Gets the current dialog if any. @@ -285,6 +311,45 @@ namespace Tango.FSE.UI.Notifications return source.Task; } + public Task<InputBoxResult> 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<InputBoxResult> source = new TaskCompletionSource<InputBoxResult>(); + + 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; + } + /// <summary> /// Called when the message box has been closed. /// </summary> 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<WebRtcIceCandidateRequest>(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<GetMachineInformationRequest, GetMachineInformationResponse>(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 @@ </StackPanel> </Button> - <Grid HorizontalAlignment="Right" Margin="0 0 25 0"> + <Grid HorizontalAlignment="Right" Margin="0 0 25 0" ToolTip="{Binding MachineProvider.MachineOperator.Status,Converter={StaticResource EnumToDescriptionConverter}}"> <Ellipse Width="42" Height="42" Stroke="{StaticResource FSE_PrimaryBackgroundLightBrush}" StrokeThickness="4"> <Ellipse.Style> <Style TargetType="Ellipse"> 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 3961d303b..07f40d18c 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml @@ -274,6 +274,77 @@ </Grid> <!--TASK ITEMS--> + <!--INPUT BOX--> + <Grid x:Name="gridInputBox" IsVisibleChanged="GridInputBox_IsVisibleChanged" Background="{StaticResource FSE_SemiTransparentBrush}" Visibility="{Binding NotificationProvider.HasInputBox,Converter={StaticResource BooleanToVisibilityConverter}}"> + <Grid MinWidth="650" MaxWidth="800" MaxHeight="400" MinHeight="280" HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5" Grid.Column="1" Grid.Row="1"> + <Grid.Style> + <Style TargetType="Grid"> + <Setter Property="RenderTransform"> + <Setter.Value> + <ScaleTransform ScaleX="0" ScaleY="0" /> + </Setter.Value> + </Setter> + <Style.Triggers> + <DataTrigger Binding="{Binding NotificationProvider.HasInputBox}" Value="True"> + <DataTrigger.EnterActions> + <BeginStoryboard> + <Storyboard> + <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" From="0" To="1" Duration="00:00:0.1" /> + <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" From="0" To="1" Duration="00:00:0.1" /> + </Storyboard> + </BeginStoryboard> + </DataTrigger.EnterActions> + <DataTrigger.ExitActions> + <BeginStoryboard> + <Storyboard> + <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" From="1" To="0" Duration="00:00:0.1" /> + <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" From="1" To="0" Duration="00:00:0.1" /> + </Storyboard> + </BeginStoryboard> + </DataTrigger.ExitActions> + </DataTrigger> + </Style.Triggers> + </Style> + </Grid.Style> + + <Border DataContext="{Binding NotificationProvider.CurrentInputBox}" Background="{StaticResource FSE_PrimaryBackgroundBrush}" CornerRadius="5" Margin="10"> + <Border.Effect> + <DropShadowEffect BlurRadius="10" /> + </Border.Effect> + <Grid ClipToBounds="True"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="48" /> + <RowDefinition Height="80*" /> + <RowDefinition Height="60" /> + </Grid.RowDefinitions> + <Grid> + <Border CornerRadius="5 5 0 0" Background="{StaticResource FSE_PrimaryBackgroundLightBrush}"></Border> + <Border CornerRadius="5 5 0 0" BorderBrush="{StaticResource FSE_BorderBrush}" BorderThickness="0 0 0 1"> + <StackPanel VerticalAlignment="Center" Margin="10 0 0 0" Orientation="Horizontal" HorizontalAlignment="Left"> + <material:PackIcon Width="24" Height="24" Margin="0 0 10 0" Kind="{Binding Icon}" ></material:PackIcon> + <TextBlock VerticalAlignment="Center" Text="{Binding Title}" FontSize="{StaticResource FSE_MessageBoxTitleFontSize}"></TextBlock> + </StackPanel> + </Border> + <commonControls:IconButton Icon="Close" Padding="8" Margin="0 0 5 0" Command="{Binding CloseCommand}" CommandParameter="{Binding}" HorizontalAlignment="Right" Width="{Binding RelativeSource={RelativeSource Self},Path=ActualHeight}"/> + </Grid> + + <StackPanel Grid.Row="1" Margin="40" VerticalAlignment="Top"> + <TextBlock Foreground="{StaticResource FSE_PrimaryForegroundBrush}" Text="{Binding Message}" TextWrapping="Wrap"></TextBlock> + <TextBox x:Name="txtInput" KeyDown="TxtInput_KeyDown" Margin="0 20 0 0" Text="{Binding Input,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" MaxLength="{Binding MaxCharacters}" material:HintAssist.Hint="{Binding InputHint}" /> + </StackPanel> + + <StackPanel HorizontalAlignment="Right" Grid.Row="2" Margin="10" Orientation="Horizontal"> + <Button MinWidth="150" x:Name="btnInputCancel" Height="Auto" Style="{StaticResource FSE_RaisedButton_Dark_Hover}" Margin="0 0 10 0" Command="{Binding CloseCommand}" Visibility="{Binding HasCancel,Converter={StaticResource BooleanToVisibilityConverter}}" Content="{Binding CancelText}"></Button> + <Button MinWidth="150" x:Name="btnInputOK" IsDefault="True" Style="{StaticResource FSE_RaisedButton_Dark_Hover}" Height="Auto" Command="{Binding OKCommand}" Content="{Binding OKText}"></Button> + </StackPanel> + </Grid> + </Grid> + </Border> + </Grid> + </Grid> + <!--INPUT BOX--> + <!--MESSAGE BOXES--> <Grid x:Name="gridMessageBox" IsVisibleChanged="GridMessageBox_IsVisibleChanged" Background="{StaticResource FSE_SemiTransparentBrush}" Visibility="{Binding NotificationProvider.HasMessageBox,Converter={StaticResource BooleanToVisibilityConverter}}"> <Grid MinWidth="650" MaxWidth="800" MaxHeight="400" MinHeight="280" HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5" Grid.Column="1" Grid.Row="1"> 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<String, FileSystemOperation> _operations; + private Dictionary<ExternalBridgeReceiver, BasicTransporter> _webRtcClients; public bool Enabled { get; set; } = true; + public bool EnableWebRTC { get; set; } = true; public DefaultFileSystemService(IPPCExternalBridgeService externalBridge) { + _webRtcClients = new Dictionary<ExternalBridgeReceiver, BasicTransporter>(); _manager = new FileSystemManager(); _operations = new Dictionary<string, FileSystemOperation>(); 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<ChunkDownloadRequest>(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<FileSystemItem> _selectedItemsBeforeDrag; + private List<FileSystemItem> _copyItems; + private bool _isCut; + private bool _isAfterContextMenu; + + #region IsCut Attached Property + + /// <summary> + /// Determines whether the draggable element is currently being dragged. + /// </summary> + public static readonly DependencyProperty IsCutProperty = + DependencyProperty.RegisterAttached("IsCut", + typeof(bool), typeof(FileExplorerControl), + new FrameworkPropertyMetadata(false)); + + /// <summary> + /// Sets the IsCut attached property. + /// </summary> + /// <param name="element">The element.</param> + /// <param name="value">if set to <c>true</c> [value].</param> + public static void SetIsCut(FrameworkElement element, bool value) + { + element.SetValue(IsCutProperty, value); + } + + /// <summary> + /// Gets the is dragging attached property. + /// </summary> + /// <param name="element">The element.</param> + /// <returns></returns> + 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<FileSystemItem>(); _selectedItemsBeforeDrag = new List<FileSystemItem>(); + + 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)) { - DeleteCommand?.Execute(SelectedItems); + OpenCommand?.Execute(null); } } + else if (e.Key == Key.C && Keyboard.IsKeyDown(Key.LeftCtrl)) + { + if (CopyCommand != null && CopyCommand.CanExecute(null)) + { + 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 @@ <Compile Include="Network\ChunkUploadResponse.cs" /> <Compile Include="Network\ChunkDownloadRequest.cs" /> <Compile Include="Network\ChunkDownloadResponse.cs" /> + <Compile Include="Network\CopyRequest.cs" /> + <Compile Include="Network\CopyResponse.cs" /> + <Compile Include="Network\DeleteRequest.cs" /> + <Compile Include="Network\DeleteResponse.cs" /> + <Compile Include="Network\InitWebRtcRequest.cs" /> + <Compile Include="Network\InitWebRtcResponse.cs" /> + <Compile Include="Network\MoveRequest.cs" /> + <Compile Include="Network\MoveResponse.cs" /> <Compile Include="VirtualFileDataObject.cs" /> <Page Include="Themes\Generic.xaml"> <Generator>MSBuild:Compile</Generator> 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 @@ <Setter Property="VerticalContentAlignment" Value="Center"></Setter> </Style> + <ContextMenu x:Key="ItemContextMenu"> + <MenuItem Header="Copy"/> + <MenuItem Header="Paste"/> + <MenuItem Header="Delete"/> + <MenuItem Header="Select All"/> + </ContextMenu> + <Style TargetType="{x:Type local:FileExplorerControl}"> <Setter Property="DriveIcon" Value="pack://application:,,,/Tango.FileSystem;component/Images/drive.png"></Setter> <Setter Property="FolderIcon" Value="pack://application:,,,/Tango.FileSystem;component/Images/folder.png"></Setter> @@ -34,7 +41,6 @@ <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> - <Grid Background="Transparent"> <ListBox x:Name="PART_listbox" Background="Transparent" SelectionMode="Extended" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" ItemsSource="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=CurrentItem.Items}" SelectedItem="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=SelectedItem,Mode=TwoWay}"> <ListBox.Style> @@ -51,6 +57,7 @@ <Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}"> <Setter Property="Padding" Value="5"></Setter> <Setter Property="Margin" Value="10"></Setter> + <Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}"></Setter> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemsPanel> @@ -123,7 +130,7 @@ </DataGrid.Style> <DataGrid.Columns> - <DataGridTemplateColumn Header="Name" Width="300*"> + <DataGridTemplateColumn Header="Name" Width="300*" SortMemberPath="Name"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DockPanel Background="Transparent"> @@ -149,7 +156,7 @@ </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> - <DataGridTemplateColumn Header="Date Modified" Width="170*"> + <DataGridTemplateColumn Header="Date Modified" Width="170*" SortMemberPath="DateModified"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DockPanel Background="Transparent"> @@ -158,7 +165,7 @@ </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> - <DataGridTemplateColumn Header="Type" Width="140*"> + <DataGridTemplateColumn Header="Type" Width="140*" SortMemberPath="Type"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DockPanel Background="Transparent"> @@ -167,7 +174,7 @@ </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> - <DataGridTemplateColumn Header="Size" Width="100*"> + <DataGridTemplateColumn Header="Size" Width="100*" SortMemberPath="Size"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DockPanel Background="Transparent"> 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); + } + /// <remarks> /// 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 request, String token); + public delegate void RequestHandlerCallbackDelegate<Request>(ITransporter transporter, Request request, String token); /// <summary> /// Represents a transportation engine which can send and receive <see cref="TangoMessage{T}"/> message using a <see cref="ITransportAdapter">Transport adapter</see>. 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<Object, String> Callback { get; set; } + public Action<ITransporter, Object, String> 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 @@ <Compile Include="DataMessageReceivedEventArgs.cs" /> <Compile Include="ErrorEventArgs.cs" /> <Compile Include="IceCandidate.cs" /> + <Compile Include="Network\IceCandidateRequest.cs" /> + <Compile Include="Network\IceCandidateResponse.cs" /> + <Compile Include="Network\OfferRequest.cs" /> + <Compile Include="Network\OfferResponse.cs" /> <Compile Include="NewIceCandidateEventArgs.cs" /> <Compile Include="Offer.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="VideoFrameReceivedEventArgs.cs" /> <Compile Include="WebRtcClient.cs" /> + <Compile Include="WebRtcTransportAdapter.cs" /> + <Compile Include="WebRtcTransportAdapterDisconnectedException.cs" /> + <Compile Include="WebRtcTransportAdapterMode.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\SideChains\WebRtc.NET\WebRtc.NET.vcxproj"> @@ -63,6 +70,14 @@ <Project>{a34ee0f0-649d-41c8-8489-b6f1cc6924ee}</Project> <Name>Tango.Core</Name> </ProjectReference> + <ProjectReference Include="..\Tango.Logging\Tango.Logging.csproj"> + <Project>{BC932DBD-7CDB-488C-99E4-F02CF441F55E}</Project> + <Name>Tango.Logging</Name> + </ProjectReference> + <ProjectReference Include="..\Tango.Transport\Tango.Transport.csproj"> + <Project>{74e700b0-1156-4126-be40-ee450d3c3026}</Project> + <Name>Tango.Transport</Name> + </ProjectReference> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project>
\ 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<IceCandidate> _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<IceCandidateRequest>(OnIceCandidateRequestReceived); + SignalingTransporter.RegisterRequestHandler<OfferRequest>(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<object> completionSource = new TaskCompletionSource<object>(); + bool completed = false; + + _queuedIceCandidates = new List<IceCandidate>(); + _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<OfferRequest, OfferResponse>(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<IceCandidateRequest, IceCandidateResponse>(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<byte[]> 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<IceCandidateRequest, IceCandidateResponse>(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<IceCandidateRequest, IceCandidateResponse>(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<object> completionSource = new TaskCompletionSource<object>(); + + 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 + } +} |
