Skip to content

Commit

Permalink
Add WebRtc file transfer.
Browse files Browse the repository at this point in the history
  • Loading branch information
bitbound committed Apr 20, 2020
1 parent 76d4c8f commit a8df693
Show file tree
Hide file tree
Showing 33 changed files with 489 additions and 187 deletions.
1 change: 1 addition & 0 deletions Desktop.Linux/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ private void BuildServices()
serviceCollection.AddSingleton<Conductor>();
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerLinux>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IFileDownloadService, FileDownloadService>();


ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
Expand Down
1 change: 1 addition & 0 deletions Desktop.Win/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ private void BuildServices()
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerWin>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IWebRtcSessionFactory, WebRtcSessionFactory>();
serviceCollection.AddScoped<IFileDownloadService, FileDownloadService>();


ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
Expand Down
18 changes: 12 additions & 6 deletions ScreenCast.Core/Communication/CasterSocket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ public class CasterSocket
public CasterSocket(
IKeyboardMouseInput keyboardMouseInput,
IScreenCaster screenCastService,
IAudioCapturer audioCapturer)
IAudioCapturer audioCapturer,
IFileDownloadService fileDownloadService)
{
KeyboardMouseInput = keyboardMouseInput;
AudioCapturer = audioCapturer;
ScreenCaster = screenCastService;
FileDownloadService = fileDownloadService;
}

public HubConnection Connection { get; private set; }
public bool IsConnected => Connection?.State == HubConnectionState.Connected;
public IScreenCaster ScreenCaster { get; }

private IScreenCaster ScreenCaster { get; }
private IFileDownloadService FileDownloadService { get; }
private IAudioCapturer AudioCapturer { get; }

private IClipboardService ClipboardService { get; }
Expand Down Expand Up @@ -149,7 +151,7 @@ private void ApplyConnectionHandlers()
{
try
{
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.RtcSession.AddIceCandidate(sdpMid, sdpMlineIndex, candidate);
}
Expand All @@ -165,7 +167,7 @@ private void ApplyConnectionHandlers()
{
try
{
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.RtcSession.SetRemoteDescription("answer", sdp);
}
Expand Down Expand Up @@ -347,6 +349,11 @@ private void ApplyConnectionHandlers()
}
});

Connection.On("ReceiveFile", async (byte[] buffer, string fileName, string messageId, bool endOfFile, bool startOfFile) =>
{
await FileDownloadService.ReceiveFile(buffer, fileName, messageId, endOfFile, startOfFile);
});

Connection.On("ToggleAudio", (bool toggleOn, string viewerID) =>
{
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
Expand All @@ -362,7 +369,6 @@ private void ApplyConnectionHandlers()
}
});


Connection.On("TouchDown", (string viewerID) =>
{
if (conductor.Viewers.TryGetValue(viewerID, out var viewer) && viewer.HasControl)
Expand Down
1 change: 1 addition & 0 deletions ScreenCast.Core/ScreenCast.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="3.1.3" />
<PackageReference Include="Microsoft.MixedReality.WebRTC" Version="1.0.3" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.7.0" />
</ItemGroup>

<ItemGroup>
Expand Down
137 changes: 137 additions & 0 deletions ScreenCast.Core/Services/FileDownloadService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using Remotely.Shared.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Security.AccessControl;
using System.Security.Principal;
using Microsoft.Extensions.Caching.Memory;
using System.Threading;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;

namespace Remotely.ScreenCast.Core.Services
{
public interface IFileDownloadService
{
string GetBaseDirectory();

Task ReceiveFile(byte[] buffer, string fileName, string messageId, bool endOfFile, bool startOfFile);
}

public class FileDownloadService : IFileDownloadService
{
private static readonly ConcurrentDictionary<string, FileStream> _partialDownloads = new ConcurrentDictionary<string, FileStream>();

private static readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1);

private ILogger<FileDownloadService> _logger;

public FileDownloadService(ILogger<FileDownloadService> logger)
{
_logger = logger;
}

public string GetBaseDirectory()
{
string baseDir;
var programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
if (Directory.Exists(programDataPath))
{
baseDir = Directory.CreateDirectory(Path.Combine(programDataPath, "Remotely", "Shared")).FullName;
SetFolderPermissions(baseDir);
}
else
{
// Use Temp dir if ProgramData doesn't exist (e.g. non-Windows OS).
baseDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "Remotely_Shared")).FullName;
}
return baseDir;
}

public async Task ReceiveFile(byte[] buffer, string fileName, string messageId, bool endOfFile, bool startOfFile)
{
try
{
await _writeLock.WaitAsync();

var baseDir = GetBaseDirectory();

if (startOfFile)
{
var filePath = Path.Combine(baseDir, fileName);

if (File.Exists(filePath))
{
var count = 0;
var ext = Path.GetExtension(fileName);
var fileWithoutExt = Path.GetFileNameWithoutExtension(fileName);
while (File.Exists(filePath))
{
filePath = Path.Combine(baseDir, $"{fileWithoutExt}-{count}{ext}");
count++;
}
}

var fs = new FileStream(filePath, FileMode.OpenOrCreate);
_partialDownloads.AddOrUpdate(messageId, fs, (k,v) => fs);
}

var fileStream = _partialDownloads[messageId];

if (buffer?.Length > 0)
{
await fileStream.WriteAsync(buffer, 0, buffer.Length);

}

if (endOfFile)
{
fileStream.Close();
_partialDownloads.Remove(messageId, out _);
if (EnvironmentHelper.IsWindows)
{
Process.Start("explorer.exe", baseDir);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error receiving file.");
}
finally
{
_writeLock.Release();
}
}

private void SetFolderPermissions(string baseDir)
{
var sid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
var account = (NTAccount)sid.Translate(typeof(NTAccount));
var aclSections = AccessControlSections.Access | AccessControlSections.Group | AccessControlSections.Owner;
var ds = new DirectorySecurity(baseDir, aclSections);

var accessAlreadySet = false;

foreach (FileSystemAccessRule rule in ds.GetAccessRules(true, true, typeof(NTAccount)))
{
if (rule.IdentityReference == account &&
rule.FileSystemRights.HasFlag(FileSystemRights.Modify) &&
rule.AccessControlType == AccessControlType.Allow)
{
accessAlreadySet = true;
break;
}
}

if (!accessAlreadySet)
{
ds.AddAccessRule(new FileSystemAccessRule(account, FileSystemRights.Modify, AccessControlType.Allow));
new DirectoryInfo(baseDir).SetAccessControl(ds);
}
}
}
}
17 changes: 16 additions & 1 deletion ScreenCast.Core/Services/RtcMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,22 @@ public RtcMessageHandler(Viewer viewer,
CasterSocket casterSocket,
IKeyboardMouseInput keyboardMouseInput,
IAudioCapturer audioCapturer,
IClipboardService clipboardService)
IClipboardService clipboardService,
IFileDownloadService fileDownloadService)
{
Viewer = viewer;
CasterSocket = casterSocket;
KeyboardMouseInput = keyboardMouseInput;
AudioCapturer = audioCapturer;
ClipboardService = clipboardService;
FileDownloadService = fileDownloadService;
}

private CasterSocket CasterSocket { get; }
private IKeyboardMouseInput KeyboardMouseInput { get; }
private IAudioCapturer AudioCapturer { get; }
private IClipboardService ClipboardService { get; }
private IFileDownloadService FileDownloadService { get; }

public async Task ParseMessage(byte[] message)
{
Expand Down Expand Up @@ -115,6 +118,9 @@ public async Task ParseMessage(byte[] message)
case BinaryDtoType.QualityChange:
QualityChange(message);
break;
case BinaryDtoType.File:
await DownloadFile(message);
break;
default:
break;
}
Expand Down Expand Up @@ -151,6 +157,15 @@ private void ClipboardTransfer(byte[] message)
ClipboardService.SetText(dto.Text);
}
}
private async Task DownloadFile(byte[] message)
{
var dto = MessagePackSerializer.Deserialize<FileDto>(message);
await FileDownloadService.ReceiveFile(dto.Buffer,
dto.FileName,
dto.MessageId,
dto.EndOfFile,
dto.StartOfFile);
}

private void ToggleBlockInput(byte[] message)
{
Expand Down
21 changes: 15 additions & 6 deletions ScreenCast.Core/Services/WebRtcSessionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,35 @@ public class WebRtcSessionFactory : IWebRtcSessionFactory
public WebRtcSessionFactory(CasterSocket casterSocket,
IKeyboardMouseInput keyboardMouseInput,
IAudioCapturer audioCapturer,
IClipboardService clipboardService)
IClipboardService clipboardService,
IFileDownloadService fileDownloadService)
{
CasterSocket = casterSocket;
KeyboardMouseInput = keyboardMouseInput;
AudioCapturer = audioCapturer;
ClipboardService = clipboardService;
FileDownloadService = fileDownloadService;
}
private IAudioCapturer AudioCapturer { get; }

private CasterSocket CasterSocket { get; }

private IClipboardService ClipboardService { get; }

private IFileDownloadService FileDownloadService { get; }

private IKeyboardMouseInput KeyboardMouseInput { get; }

public WebRtcSession GetNewSession(Viewer viewer)
{
var messageHandler = new RtcMessageHandler(viewer,
CasterSocket,
KeyboardMouseInput,
AudioCapturer,
ClipboardService);
ClipboardService,
FileDownloadService);

return new WebRtcSession(messageHandler);
}
private CasterSocket CasterSocket { get; }
private IKeyboardMouseInput KeyboardMouseInput { get; }
private IAudioCapturer AudioCapturer { get; }
private IClipboardService ClipboardService { get; }
}
}
1 change: 1 addition & 0 deletions ScreenCast.Linux/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ private static void BuildServices()
serviceCollection.AddSingleton<ChatHostService>();
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerLinux>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IFileDownloadService, FileDownloadService>();

ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}
Expand Down
1 change: 1 addition & 0 deletions ScreenCast.Win/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ private static void BuildServices()
serviceCollection.AddTransient<IScreenCapturer, ScreenCapturerWin>();
serviceCollection.AddTransient<Viewer>();
serviceCollection.AddScoped<IWebRtcSessionFactory, WebRtcSessionFactory>();
serviceCollection.AddScoped<IFileDownloadService, FileDownloadService>();

ServiceContainer.Instance = serviceCollection.BuildServiceProvider();
}
Expand Down
4 changes: 2 additions & 2 deletions Server/Pages/RemoteControl.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@
</div>

<div id="fileTransferDiv" hidden="hidden">
<span>File Transfer:</span>
<progress id="fileTransferProgress"></progress>
<span id="fileTransferNameSpan" class="mr-1" style="vertical-align: middle;"></span>
<progress id="fileTransferProgress" style="vertical-align: middle;"></progress>
</div>

<footer>
Expand Down
1 change: 1 addition & 0 deletions Server/wwwroot/scripts/Enums/BinaryDtoType.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Server/wwwroot/scripts/Enums/BinaryDtoType.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Server/wwwroot/scripts/Enums/BinaryDtoType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
ToggleBlockInput = 18,
ClipboardTransfer = 19,
KeyPress = 20,
QualityChange = 21
QualityChange = 21,
File = 22
}
21 changes: 21 additions & 0 deletions Server/wwwroot/scripts/RemoteControl/FileUploader.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Server/wwwroot/scripts/RemoteControl/FileUploader.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a8df693

Please sign in to comment.