diff options
Diffstat (limited to 'Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem')
4 files changed, 518 insertions, 0 deletions
diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs new file mode 100644 index 000000000..8272ea34d --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/DefaultFileSystemService.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; +using Tango.Core.DI; +using Tango.Core.IO; +using Tango.FileSystem; +using Tango.FileSystem.Network; +using Tango.Integration.ExternalBridge; +using Tango.Integration.Operation; +using Tango.Logging; +using Tango.PPC.Common.ExternalBridge; +using Tango.PPC.Shared.Logs; +using Tango.Settings; +using Tango.Transport; +using Tango.Transport.Transporters; +using Tango.WebRTC; + +namespace Tango.PPC.Common.FileSystem +{ + /// <summary> + /// Represents the <see cref="IFileSystemService"/> default implementation. + /// </summary> + /// <seealso cref="Tango.Core.ExtendedObject" /> + /// <seealso cref="Tango.PPC.Common.FileSystem.IFileSystemService" /> + /// <seealso cref="Tango.Integration.ExternalBridge.IExternalBridgeRequestHandler" /> + [TangoCreateWhenRegistered] + public class DefaultFileSystemService : ExtendedObject, IFileSystemService, IExternalBridgeRequestHandler + { + private FileSystemManager _manager; + private Dictionary<String, FileSystemOperation> _operations; + private Dictionary<ExternalBridgeReceiver, BasicTransporter> _webRtcClients; + private PPCSettings _settings; + + 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); + _settings = SettingsManager.Default.GetOrCreate<PPCSettings>(); + } + + [ExternalBridgeRequestHandlerMethod(typeof(InitWebRtcRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnInitWebRtcRequest(InitWebRtcRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + try + { + if (!EnableWebRTC) + { + await receiver.SendErrorResponse(new InvalidOperationException("The file system service WebRTC channel is disabled on this machine."), token); + return; + } + + LogManager.Log("Initializing WebRTC channel for file system service."); + + if (_webRtcClients.ContainsKey(receiver)) + { + _webRtcClients[receiver].Dispose(); + } + + LogManager.Log("Initializing WebRTC transport adapter on 'Passive' mode."); + var webRtcAdapter = new WebRtcTransportAdapter(receiver, WebRtcTransportAdapterMode.Passive, request.DataChannelName) + { + EnableCompression = receiver.Adapter.EnableCompression + }; + webRtcAdapter.Ready += (x, e) => + { + LogManager.Log("The file system service WebRTC channel is ready."); + }; + + BasicTransporter webRtcTransporter = new BasicTransporter(webRtcAdapter); + webRtcTransporter.GenericProtocol = receiver.GenericProtocol; + webRtcTransporter.ComponentName = "File System Passive WebRTC Transporter"; + webRtcTransporter.UseKeepAlive = false; + webRtcTransporter.RegisterRequestHandler<ChunkDownloadRequest>(WebRtcChunkDownloadRequestReceived); + webRtcTransporter.RegisterRequestHandler<ChunkUploadRequest>(WebRtcChunkUploadRequestReceived); + await webRtcTransporter.Connect(); + + LogManager.Log("Sending WebRTC initialization response..."); + + await receiver.SendGenericResponse(new InitWebRtcResponse(), token); + _webRtcClients[receiver] = webRtcTransporter; + } + catch (Exception ex) + { + LogManager.Log(ex, "Error initializing WebRTC channel for file system service."); + await receiver.SendErrorResponse(ex, token); + } + } + + private async void WebRtcChunkDownloadRequestReceived(ITransporter transporter, ChunkDownloadRequest request, string token) + { + await OnChunkDownloadRequest(request, token, transporter); + } + + private async void WebRtcChunkUploadRequestReceived(ITransporter transporter, ChunkUploadRequest request, string token) + { + await OnChunkUploadRequest(request, token, transporter); + } + + [ExternalBridgeRequestHandlerMethod(typeof(GetFileSystemItemRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnGetFileSystemItemRequest(GetFileSystemItemRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + FileSystemItemDTO dto = _manager.GetFolder(request); + await receiver.SendGenericResponse(new GetFileSystemItemResponse() { FileSystemItem = dto }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(FileUploadRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnFileUploadRequest(FileUploadRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + var tempFile = TemporaryManager.CreateFile(); + using (var stream = new FileStream(tempFile, FileMode.Create)) { } + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Upload, tempFile) { UploadPostPath = request.Path }; + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FileUploadResponse() { OperationId = operation.Id }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(FolderUploadRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnFolderUploadRequest(FolderUploadRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + var tempFile = TemporaryManager.CreateFile(); + using (var stream = new FileStream(tempFile, FileMode.Create)) { } + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Upload, tempFile) { UploadPostPath = request.Path, IsPathTempZip = true }; + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FolderUploadResponse() { OperationId = operation.Id }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(FileDownloadRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnFileDownloadRequest(FileDownloadRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + if (!File.Exists(request.Path)) + { + throw new FileNotFoundException("Could not find the specified file."); + } + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Download, request.Path); + + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FileDownloadResponse() + { + OperationId = operation.Id, + Length = new FileInfo(request.Path).Length + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(FolderDownloadRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnFolderDownloadRequest(FolderDownloadRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + if (!Directory.Exists(request.Path)) + { + throw new FileNotFoundException("Could not find the specified directory."); + } + + var tempFile = TemporaryManager.CreateImaginaryFile(); + + ZipFile.CreateFromDirectory(request.Path, tempFile); + + FileSystemOperation operation = new FileSystemOperation(FileSystemOperationMode.Download, tempFile); + operation.IsPathTempZip = true; + + _operations.Add(operation.Id, operation); + + await receiver.SendGenericResponse(new FolderDownloadResponse() + { + OperationId = operation.Id, + Length = new FileInfo(tempFile).Length + }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(ChunkUploadRequest))] + public async Task OnChunkUploadRequest(ChunkUploadRequest request, String token, ITransporter receiver) + { + this.ThrowIfDisabled(); + + FileSystemOperation operation; + _operations.TryGetValue(request.OperationId, out operation); + + if (operation == null) + { + throw new ArgumentException("Invalid operation id."); + } + + using (var stream = new FileStream(operation.Path, FileMode.Append)) + { + stream.Write(request.Data, 0, request.Data.Length); + } + + if (request.IsCompleted) + { + if (!operation.IsPathTempZip) + { + File.Copy(operation.Path, operation.UploadPostPath, true); + try + { + File.Delete(operation.Path); + } + catch { } + } + else + { + using (Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile(operation.Path)) + { + zip.ExtractAll(operation.UploadPostPath, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently); + } + + try + { + File.Delete(operation.Path); + } + catch { } + } + } + + await receiver.SendGenericResponse(new ChunkUploadResponse(), token, new TransportResponseConfig() { Priority = QueuePriority.Low }); + } + + [ExternalBridgeRequestHandlerMethod(typeof(ChunkDownloadRequest))] + public async Task OnChunkDownloadRequest(ChunkDownloadRequest request, String token, ITransporter receiver) + { + this.ThrowIfDisabled(); + + FileSystemOperation operation; + _operations.TryGetValue(request.OperationId, out operation); + + if (operation == null) + { + throw new ArgumentException("Invalid operation id."); + } + + FileStream stream = null; + bool removeTempZipFile = false; + + try + { + stream = new FileStream(operation.Path, FileMode.Open); + stream.Position = request.Position; + byte[] data = new byte[Math.Min(request.MaxChunkSize, stream.Length - stream.Position)]; + + if (stream.Position + data.Length == stream.Length) + { + removeTempZipFile = true; + } + + 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(); + throw ex; + } + finally + { + if (operation.IsPathTempZip && removeTempZipFile) + { + try + { + if (File.Exists(operation.Path)) + { + File.Delete(operation.Path); + } + } + catch { } + } + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(AbortOperationRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnAbortOperationRequest(AbortOperationRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + FileSystemOperation operation; + _operations.TryGetValue(request.OperationId, out operation); + + if (operation == null) + { + throw new ArgumentException("Invalid operation id."); + } + + if (operation.Mode == FileSystemOperationMode.Upload) + { + if (File.Exists(operation.Path)) + { + File.Delete(operation.Path); + } + } + else if (operation.IsPathTempZip) + { + if (File.Exists(operation.Path)) + { + File.Delete(operation.Path); + } + } + + await receiver.SendGenericResponse(new AbortOperationResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(MoveRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnMoveRequest(MoveRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + _manager.Move(request); + await receiver.SendGenericResponse(new MoveResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(CopyRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnCopyRequest(CopyRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + _manager.Copy(request); + await receiver.SendGenericResponse(new CopyResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(DeleteRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnDeleteRequest(DeleteRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + _manager.Delete(request.Path); + await receiver.SendGenericResponse(new DeleteResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(CreateFolderRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnCreateFolderRequest(CreateFolderRequest request, String token, ExternalBridgeReceiver receiver) + { + this.ThrowIfDisabled(); + + var dto = _manager.CreateFolder(request.Path, request.FolderName); + await receiver.SendGenericResponse(new CreateFolderResponse() { FolderItem = dto }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(PerformDiskSpaceOptimizationRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnPerformDiskSpaceOptimizationRequest(PerformDiskSpaceOptimizationRequest request, String token, ExternalBridgeReceiver receiver) + { + var deletedBytes = _manager.PerformDiskSpaceOptimization(); + await receiver.SendGenericResponse(new PerformDiskSpaceOptimizationResponse() { DeletedBytes = deletedBytes }, token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(GetLogFilesRequest), RequestHandlerLoggingMode.LogRequestNameAndContent)] + public async Task OnGetLogFilesRequest(GetLogFilesRequest request, String token, ExternalBridgeReceiver receiver) + { + FolderItem folder = null; + + if (request.LogFileType == RemoteLogFileType.Application) + { + var fileLogger = LogManager.RegisteredLoggers.SingleOrDefault(x => x.GetType() == typeof(FileLogger)) as FileLogger; + + if (fileLogger == null) + { + throw new InvalidOperationException("Could not locate the application file logger."); + } + + folder = await _manager.GetFolder(fileLogger.Folder, false, "*.log") as FolderItem; + } + else + { + if (MachineOperator.EmbeddedLogsFolder == null) + { + throw new InvalidOperationException("The firmware file logger folder could not be read."); + } + + folder = await _manager.GetFolder(MachineOperator.EmbeddedLogsFolder, false, "*.log") as FolderItem; + } + + GetLogFilesResponse response = new GetLogFilesResponse(); + + foreach (var file in folder.Items.OfType<FileItem>().OrderByDescending(x => x.DateCreated).DistinctBy(x => x.Name)) + { + response.LogFiles.Add(new RemoteLogFile() + { + DateModified = file.DateModified, + DateCreated = file.DateCreated, + Name = file.Name, + Path = file.Path, + Length = new FileInfo(file.Path).Length + }); + } + + await receiver.SendGenericResponse(response, token); + } + + public void OnReceiverDisconnected(ExternalBridgeReceiver receiver) + { + if (_webRtcClients.ContainsKey(receiver)) + { + try + { + LogManager.Log("External bridge receiver disconnected. Disposing file system service WebRTC channel..."); + var webRtcTransporter = _webRtcClients[receiver]; + _webRtcClients.Remove(receiver); + webRtcTransporter.Dispose(); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error disposing the WebRTC channel."); + } + } + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperation.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperation.cs new file mode 100644 index 000000000..9fba7a874 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperation.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.FileSystem +{ + /// <summary> + /// Represents an active file system file/folder download/upload operation + /// </summary> + public class FileSystemOperation + { + /// <summary> + /// Gets or sets the operation mode. + /// </summary> + public FileSystemOperationMode Mode { get; set; } + + /// <summary> + /// Gets or sets the operation identifier. + /// </summary> + public String Id { get; set; } + + /// <summary> + /// Gets or sets the path for the operation. + /// </summary> + public String Path { get; set; } + + /// <summary> + /// Should be set to true when the <see cref="Path"/> is a temporary zip file when performing download of a folder. + /// </summary> + public bool IsPathTempZip { get; set; } + + /// <summary> + /// Gets or sets the actual path to extract the zip file when uploading folder. + /// </summary> + public String UploadPostPath { get; set; } + + /// <summary> + /// Initializes a new instance of the <see cref="FileSystemOperation"/> class. + /// </summary> + /// <param name="mode">The mode.</param> + /// <param name="path">The path.</param> + public FileSystemOperation(FileSystemOperationMode mode, String path) + { + Mode = mode; + Id = Guid.NewGuid().ToString(); + Path = path; + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperationMode.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperationMode.cs new file mode 100644 index 000000000..e28843bce --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/FileSystemOperationMode.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.FileSystem +{ + public enum FileSystemOperationMode + { + Upload, + Download + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs new file mode 100644 index 000000000..7a80db9c7 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/FileSystem/IFileSystemService.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.FileSystem +{ + /// <summary> + /// Represents a PPC file system remote service. + /// </summary> + /// <seealso cref="Tango.PPC.Common.IPPCService" /> + public interface IFileSystemService : IPPCService + { + /// <summary> + /// Gets or sets a value indicating whether to enable the WebRTC transport channel. + /// </summary> + bool EnableWebRTC { get; set; } + } +} |
