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 { /// /// Represents the default implementation. /// /// /// /// [TangoCreateWhenRegistered] public class DefaultFileSystemService : ExtendedObject, IFileSystemService, IExternalBridgeRequestHandler { private FileSystemManager _manager; private Dictionary _operations; private Dictionary _webRtcClients; private PPCSettings _settings; public bool Enabled { get; set; } = true; public bool EnableWebRTC { get; set; } = true; public DefaultFileSystemService(IPPCExternalBridgeService externalBridge) { _webRtcClients = new Dictionary(); _manager = new FileSystemManager(); _operations = new Dictionary(); externalBridge.RegisterRequestHandler(this); _settings = SettingsManager.Default.GetOrCreate(); } [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(WebRtcChunkDownloadRequestReceived); webRtcTransporter.RegisterRequestHandler(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().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."); } } } } }