mirror of
https://github.com/cyanfish/naps2.git
synced 2024-11-10 14:28:12 +03:00
Network WIP
This commit is contained in:
parent
0baa9f0188
commit
d7f4ed83b1
@ -50,24 +50,24 @@ namespace NAPS2.Server
|
||||
}
|
||||
}
|
||||
|
||||
new Thread(() => ServerDiscovery.ListenForBroadcast(port)) { IsBackground = true }.Start();
|
||||
new Thread(() => Discovery.ListenForBroadcast(port)) { IsBackground = true }.Start();
|
||||
|
||||
// Listen for requests
|
||||
using var host = new ServiceHost(typeof(ScanService));
|
||||
// // Listen for requests
|
||||
// using var host = new ServiceHost(typeof(ScanService));
|
||||
var serverIcon = new ServerNotifyIcon(port, () => form.Close());
|
||||
host.Opened += (sender, eventArgs) => serverIcon.Show();
|
||||
// host.Description.Behaviors.Add(new ServiceFactoryBehavior(() => kernel.Get<ScanService>()));
|
||||
var binding = new NetTcpBinding
|
||||
{
|
||||
ReceiveTimeout = TimeSpan.FromHours(1),
|
||||
SendTimeout = TimeSpan.FromHours(1),
|
||||
Security =
|
||||
{
|
||||
Mode = SecurityMode.None
|
||||
}
|
||||
};
|
||||
host.AddServiceEndpoint(typeof(IScanService), binding, $"net.tcp://0.0.0.0:{port}/NAPS2.Server");
|
||||
host.Open();
|
||||
// host.Opened += (sender, eventArgs) => serverIcon.Show();
|
||||
// // host.Description.Behaviors.Add(new ServiceFactoryBehavior(() => kernel.Get<ScanService>()));
|
||||
// var binding = new NetTcpBinding
|
||||
// {
|
||||
// ReceiveTimeout = TimeSpan.FromHours(1),
|
||||
// SendTimeout = TimeSpan.FromHours(1),
|
||||
// Security =
|
||||
// {
|
||||
// Mode = SecurityMode.None
|
||||
// }
|
||||
// };
|
||||
// host.AddServiceEndpoint(typeof(IScanService), binding, $"net.tcp://0.0.0.0:{port}/NAPS2.Server");
|
||||
// host.Open();
|
||||
try
|
||||
{
|
||||
Application.Run(form);
|
||||
|
@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.ServiceModel;
|
||||
using NAPS2.Logging;
|
||||
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
public class ClientContext : IDisposable
|
||||
{
|
||||
public IScanService Service { get; set; }
|
||||
|
||||
public IScanCallback Callback { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
((IDisposable)Service)?.Dispose();
|
||||
}
|
||||
catch (CommunicationObjectFaultedException)
|
||||
{
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.ErrorException("Error cleaning up client", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.ServiceModel;
|
||||
using NAPS2.Scan;
|
||||
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
public class ClientContextFactory
|
||||
{
|
||||
public ClientContext Create(ScanProxyConfig proxyConfig)
|
||||
{
|
||||
// TODO: Validate IP
|
||||
var uri = new Uri($"net.tcp://{proxyConfig.Ip}:{proxyConfig.Port}/NAPS2.Server");
|
||||
var callback = new ScanCallback();
|
||||
var instanceContext = new InstanceContext(callback);
|
||||
var channelFactory = new DuplexChannelFactory<IScanService>(instanceContext,
|
||||
new NetTcpBinding
|
||||
{
|
||||
MaxReceivedMessageSize = 1024 * 1024 * 1024,
|
||||
Security = { Mode = SecurityMode.None }
|
||||
},
|
||||
new EndpointAddress(uri));
|
||||
var channel = channelFactory.CreateChannel();
|
||||
return new ClientContext { Service = channel, Callback = callback };
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.ServiceModel;
|
||||
using NAPS2.Recovery;
|
||||
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
[ServiceContract]
|
||||
public interface IScanCallback
|
||||
{
|
||||
[OperationContract(IsOneWay = true)]
|
||||
void ImageReceived(byte[] imageData, RecoveryIndexImage indexImage);
|
||||
|
||||
event Action<byte[], RecoveryIndexImage> ImageCallback;
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ServiceModel;
|
||||
using System.Threading.Tasks;
|
||||
using NAPS2.Scan;
|
||||
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
[ServiceContract(CallbackContract = typeof(IScanCallback))]
|
||||
public interface IScanService
|
||||
{
|
||||
[OperationContract]
|
||||
List<string> GetSupportedDriverNames();
|
||||
|
||||
[OperationContract]
|
||||
List<ScanDevice> GetDeviceList(ScanProfile scanProfile);
|
||||
|
||||
[OperationContract]
|
||||
Task<int> Scan(ScanProfile scanProfile, ScanParams scanParams);
|
||||
|
||||
[OperationContract(IsOneWay = true)]
|
||||
void CancelScan();
|
||||
}
|
||||
}
|
@ -9,16 +9,17 @@ using NAPS2.Util;
|
||||
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
public class ServerDiscovery
|
||||
public class Discovery
|
||||
{
|
||||
private const int DISCOVERY_PORT = 33277;
|
||||
public const int DEFAULT_DISCOVERY_PORT = 33277;
|
||||
|
||||
private static readonly byte[] MagicBroadcastBytes = { 0x7f, 0x87, 0x00, 0x8b, 0x08, 0x87, 0x5d, 0xd3, 0x64, 0x1a };
|
||||
private static readonly byte[] MagicResponseBytes = { 0xf4, 0x38, 0xb9, 0xa3, 0xf7, 0x37, 0xaf, 0x35, 0x41, 0xc7 };
|
||||
|
||||
public static void ListenForBroadcast(int serverPort)
|
||||
{
|
||||
var fallback = new ExpFallback(100, 60 * 1000);
|
||||
var udpClient = new UdpClient(DISCOVERY_PORT) { Client = { ReceiveTimeout = 0 } };
|
||||
var udpClient = new UdpClient(DEFAULT_DISCOVERY_PORT) { Client = { ReceiveTimeout = 0 } };
|
||||
IPEndPoint remoteEndpoint = null;
|
||||
while (true)
|
||||
{
|
||||
@ -45,7 +46,7 @@ namespace NAPS2.Remoting.Network
|
||||
public static void SendBroadcast(Action<string, IPEndPoint> callback)
|
||||
{
|
||||
var udpClient = new UdpClient { Client = { ReceiveTimeout = 1000 } };
|
||||
udpClient.Send(MagicBroadcastBytes, MagicBroadcastBytes.Length, new IPEndPoint(IPAddress.Broadcast, DISCOVERY_PORT));
|
||||
udpClient.Send(MagicBroadcastBytes, MagicBroadcastBytes.Length, new IPEndPoint(IPAddress.Broadcast, DEFAULT_DISCOVERY_PORT));
|
||||
|
||||
var fallback = new ExpFallback(100, 60 * 1000);
|
||||
IPEndPoint remoteEndpoint = null;
|
49
NAPS2.Sdk.Network/Internal/NetworkScanService.proto
Normal file
49
NAPS2.Sdk.Network/Internal/NetworkScanService.proto
Normal file
@ -0,0 +1,49 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package NAPS2.Remoting.Network;
|
||||
|
||||
import "NAPS2.Sdk/Remoting/Error.proto";
|
||||
import "NAPS2.Sdk/Serialization/SerializedImage.proto";
|
||||
|
||||
service NetworkScanService {
|
||||
rpc GetCapabilities (GetCapabilitiesRequest) returns (GetCapabilitiesResponse) {}
|
||||
rpc GetDeviceList (GetDeviceListRequest) returns (GetDeviceListResponse) {}
|
||||
rpc Scan (ScanRequest) returns (stream ScanResponse) {}
|
||||
}
|
||||
|
||||
message GetCapabilitiesRequest {
|
||||
}
|
||||
|
||||
message GetCapabilitiesResponse {
|
||||
NAPS2.Remoting.Error error = 1;
|
||||
repeated string supportedDrivers = 2;
|
||||
}
|
||||
|
||||
message GetDeviceListRequest {
|
||||
string optionsXml = 1;
|
||||
}
|
||||
|
||||
message GetDeviceListResponse {
|
||||
NAPS2.Remoting.Error error = 1;
|
||||
string deviceListXml = 2;
|
||||
}
|
||||
|
||||
message ScanRequest {
|
||||
string optionsXml = 1;
|
||||
}
|
||||
|
||||
message ScanResponse {
|
||||
oneof event {
|
||||
NAPS2.Remoting.Error error = 1;
|
||||
NAPS2.Serialization.SerializedImage image = 2;
|
||||
ProgressEvent progress = 3;
|
||||
PageStartEvent pageStart = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message ProgressEvent {
|
||||
double value = 1;
|
||||
}
|
||||
|
||||
message PageStartEvent {
|
||||
}
|
@ -3,16 +3,56 @@ using System.Collections.Generic;
|
||||
using System.ServiceModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Grpc.Core;
|
||||
using NAPS2.Scan;
|
||||
using NAPS2.Scan.Internal;
|
||||
using NAPS2.Serialization;
|
||||
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,
|
||||
IncludeExceptionDetailInFaults = true,
|
||||
ConcurrencyMode = ConcurrencyMode.Multiple)]
|
||||
public class ScanService : IScanService
|
||||
internal class ScanService : NetworkScanService.NetworkScanServiceBase
|
||||
{
|
||||
public override Task<GetCapabilitiesResponse> GetCapabilities(GetCapabilitiesRequest request, ServerCallContext context)
|
||||
{
|
||||
return base.GetCapabilities(request, context);
|
||||
}
|
||||
|
||||
public override async Task<GetDeviceListResponse> GetDeviceList(GetDeviceListRequest request, ServerCallContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var controller = new ScanController();
|
||||
var options = request.OptionsXml.FromXml<ScanOptions>();
|
||||
var devices = await controller.GetDeviceList(options);
|
||||
return new GetDeviceListResponse
|
||||
{
|
||||
DeviceListXml = devices.ToXml()
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new GetDeviceListResponse
|
||||
{
|
||||
Error = RemotingHelper.ToError(e)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task Scan(ScanRequest request, IServerStreamWriter<ScanResponse> responseStream, ServerCallContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await responseStream.WriteAsync(new ScanResponse
|
||||
{
|
||||
Error = RemotingHelper.ToError(e)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// private readonly IRemoteScanController remoteScanController;
|
||||
//
|
||||
// private CancellationTokenSource scanCts = new CancellationTokenSource();
|
@ -19,8 +19,10 @@
|
||||
<PackageReference Include="BouncyCastle" Version="1.8.4" />
|
||||
<PackageReference Include="Grpc.Core" Version="2.26.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.26.0" />
|
||||
|
||||
<Reference Include="System.ServiceModel" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProtoBuf Include="../NAPS2.Sdk.Network/**/*.proto" Access="Internal" ProtoRoot=".." />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
39
NAPS2.Sdk.Network/NetworkScanClient.cs
Normal file
39
NAPS2.Sdk.Network/NetworkScanClient.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
public class NetworkScanClient
|
||||
{
|
||||
public async Task<IEnumerable<DiscoveredServer>> DiscoverServers(int timeout = 5000, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var servers = new List<DiscoveredServer>();
|
||||
await DiscoverServers(servers.Add, timeout, cancellationToken);
|
||||
return servers;
|
||||
}
|
||||
|
||||
public async Task DiscoverServers(Action<DiscoveredServer> callback, int timeout = 5000, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var timeoutCts = new CancellationTokenSource();
|
||||
timeoutCts.CancelAfter(timeout);
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
public class DiscoveredServer
|
||||
{
|
||||
internal DiscoveredServer(string name, IPEndPoint ipEndPoint)
|
||||
{
|
||||
Name = name;
|
||||
IPEndPoint = ipEndPoint;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public IPEndPoint IPEndPoint { get; }
|
||||
}
|
||||
}
|
22
NAPS2.Sdk.Network/NetworkScanServer.cs
Normal file
22
NAPS2.Sdk.Network/NetworkScanServer.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
public class NetworkScanServer : IDisposable
|
||||
{
|
||||
public void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Kill()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
11
NAPS2.Sdk.Network/NetworkScanServerOptions.cs
Normal file
11
NAPS2.Sdk.Network/NetworkScanServerOptions.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
public class NetworkScanServerOptions
|
||||
{
|
||||
public int Port { get; set; }
|
||||
|
||||
public bool AllowDiscovery { get; set; } = true;
|
||||
|
||||
public int DiscoveryPort { get; set; }
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using NAPS2.Recovery;
|
||||
|
||||
namespace NAPS2.Remoting.Network
|
||||
{
|
||||
public class ScanCallback : IScanCallback
|
||||
{
|
||||
public void ImageReceived(byte[] imageData, RecoveryIndexImage indexImage)
|
||||
{
|
||||
ImageCallback?.Invoke(imageData, indexImage);
|
||||
}
|
||||
|
||||
public event Action<byte[], RecoveryIndexImage> ImageCallback;
|
||||
}
|
||||
}
|
@ -31,7 +31,7 @@ namespace NAPS2.Remoting
|
||||
}
|
||||
}
|
||||
|
||||
private static Error ToError(Exception e) =>
|
||||
public static Error ToError(Exception e) =>
|
||||
new Error
|
||||
{
|
||||
Type = e.GetType().FullName,
|
||||
|
32
NAPS2.Sdk/Remoting/SequencedWriter.cs
Normal file
32
NAPS2.Sdk/Remoting/SequencedWriter.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.Threading.Tasks;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace NAPS2.Remoting
|
||||
{
|
||||
public class SequencedWriter<T>
|
||||
{
|
||||
private readonly IServerStreamWriter<T> serverStreamWriter;
|
||||
private Task lastWriteTask = Task.CompletedTask;
|
||||
|
||||
public SequencedWriter(IServerStreamWriter<T> serverStreamWriter)
|
||||
{
|
||||
this.serverStreamWriter = serverStreamWriter;
|
||||
}
|
||||
|
||||
public void Write(T item)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
lastWriteTask = lastWriteTask.ContinueWith(t => serverStreamWriter.WriteAsync(item)).Unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
public Task WaitForCompletion()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
return lastWriteTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -86,48 +86,44 @@ namespace NAPS2.Remoting.Worker
|
||||
},
|
||||
err => new GetDeviceListResponse { Error = err });
|
||||
|
||||
public override Task Scan(ScanRequest request, IServerStreamWriter<ScanResponse> responseStream, ServerCallContext context)
|
||||
public override async Task Scan(ScanRequest request, IServerStreamWriter<ScanResponse> responseStream, ServerCallContext context)
|
||||
{
|
||||
// gRPC doesn't allow multiple pending writes. However, we don't want to write synchronously because that will slow down the scanning process.
|
||||
// Instead, we can use some async magic (chained tasks) to only write one at a time.
|
||||
Task lastWriteTask = Task.CompletedTask;
|
||||
|
||||
Task WriteSequenced(ScanResponse response)
|
||||
var sequencedWriter = new SequencedWriter<ScanResponse>(responseStream);
|
||||
try
|
||||
{
|
||||
lastWriteTask = lastWriteTask.ContinueWith(t => responseStream.WriteAsync(response)).Unwrap();
|
||||
return lastWriteTask;
|
||||
}
|
||||
|
||||
return RemotingHelper.WrapAction(
|
||||
async () =>
|
||||
{
|
||||
var scanEvents = new ScanEvents(
|
||||
() => WriteSequenced(new ScanResponse
|
||||
var scanEvents = new ScanEvents(
|
||||
() => sequencedWriter.Write(new ScanResponse
|
||||
{
|
||||
PageStart = new PageStartEvent()
|
||||
}),
|
||||
progress => sequencedWriter.Write(new ScanResponse
|
||||
{
|
||||
Progress = new ProgressEvent
|
||||
{
|
||||
PageStart = new PageStartEvent()
|
||||
}),
|
||||
progress => WriteSequenced(new ScanResponse
|
||||
Value = progress
|
||||
}
|
||||
})
|
||||
);
|
||||
await remoteScanController.Scan(request.OptionsXml.FromXml<ScanOptions>(),
|
||||
context.CancellationToken, scanEvents,
|
||||
(image, postProcessingContext) =>
|
||||
sequencedWriter.Write(new ScanResponse
|
||||
{
|
||||
Progress = new ProgressEvent
|
||||
{
|
||||
Value = progress
|
||||
}
|
||||
})
|
||||
);
|
||||
await remoteScanController.Scan(request.OptionsXml.FromXml<ScanOptions>(), context.CancellationToken, scanEvents,
|
||||
(image, postProcessingContext) =>
|
||||
WriteSequenced(new ScanResponse
|
||||
{
|
||||
Image = SerializedImageHelper.Serialize(imageContext, image, new SerializedImageHelper.SerializeOptions
|
||||
Image = SerializedImageHelper.Serialize(imageContext, image,
|
||||
new SerializedImageHelper.SerializeOptions
|
||||
{
|
||||
TransferOwnership = true,
|
||||
IncludeThumbnail = true,
|
||||
RenderedFilePath = postProcessingContext.TempPath
|
||||
})
|
||||
}));
|
||||
// It's important that we wait for writes to complete, otherwise the channel might close first.
|
||||
await lastWriteTask;
|
||||
}, err => WriteSequenced(new ScanResponse { Error = err }).Wait());
|
||||
}));
|
||||
await sequencedWriter.WaitForCompletion();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
sequencedWriter.Write(new ScanResponse { Error = RemotingHelper.ToError(e) });
|
||||
await sequencedWriter.WaitForCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<SendMapiEmailResponse> SendMapiEmail(SendMapiEmailRequest request, ServerCallContext context) =>
|
||||
|
Loading…
Reference in New Issue
Block a user