Grpc wip with tests started

This commit is contained in:
Ben Olden-Cooligan 2019-03-16 15:14:39 -04:00
parent 7b6273aeb2
commit 83fbf68dd1
25 changed files with 419 additions and 347 deletions

View File

@ -10,6 +10,7 @@ using NAPS2.Ocr;
using NAPS2.Operation;
using NAPS2.Scan;
using NAPS2.Images;
using NAPS2.ImportExport.Email.Mapi;
using NAPS2.Scan.Sane;
using NAPS2.Scan.Twain;
using NAPS2.Scan.Wia;
@ -36,6 +37,7 @@ namespace NAPS2.DI.Modules
Bind<PdfExporter>().To<PdfSharpExporter>();
Bind<IScannedImagePrinter>().To<PrintDocumentPrinter>();
Bind<IEmailProviderFactory>().To<NinjectEmailProviderFactory>();
Bind<IMapiWrapper>().To<MapiWrapper>();
Bind<OcrEngineManager>().ToMethod(ctx => OcrEngineManager.Default);
Bind<OcrRequestQueue>().ToSelf().InSingletonScope();
@ -50,6 +52,7 @@ namespace NAPS2.DI.Modules
Bind<IScanDriver>().To<TwainScanDriver>().InSingletonScope().Named(TwainScanDriver.DRIVER_NAME);
Bind<IScanDriver>().To<SaneScanDriver>().InSingletonScope().Named(SaneScanDriver.DRIVER_NAME);
Bind<IScanDriver>().To<ProxiedScanDriver>().InSingletonScope().Named(ProxiedScanDriver.DRIVER_NAME);
Bind<ITwainWrapper>().To<TwainWrapper>();
// Config
Bind<PdfSettingsContainer>().ToSelf().InSingletonScope();

View File

@ -41,6 +41,12 @@
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Grpc.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d754f35622e28bad, processorArchitecture=MSIL">
<HintPath>..\packages\Grpc.Core.1.19.0\lib\net45\Grpc.Core.dll</HintPath>
</Reference>
<Reference Include="Grpc.Core.Api, Version=0.0.0.0, Culture=neutral, PublicKeyToken=d754f35622e28bad, processorArchitecture=MSIL">
<HintPath>..\packages\Grpc.Core.Api.1.19.0\lib\net45\Grpc.Core.Api.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.10.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.10.1\lib\net45\Moq.dll</HintPath>
</Reference>
@ -52,6 +58,9 @@
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Interactive.Async, Version=3.2.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\packages\System.Interactive.Async.3.2.0\lib\net46\System.Interactive.Async.dll</HintPath>
</Reference>
<Reference Include="System.Net" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
@ -98,6 +107,7 @@
<Compile Include="Util\SliceTests.cs" />
<Compile Include="Util\SslHelperTests.cs" />
<Compile Include="Util\TestingContextTests.cs" />
<Compile Include="Worker\WorkerChannelTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NAPS2.Sdk\NAPS2.Sdk.csproj">
@ -125,6 +135,8 @@
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
<Error Condition="!Exists('..\packages\Grpc.Core.1.19.0\build\net45\Grpc.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Grpc.Core.1.19.0\build\net45\Grpc.Core.targets'))" />
</Target>
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" />
<Import Project="..\packages\Grpc.Core.1.19.0\build\net45\Grpc.Core.targets" Condition="Exists('..\packages\Grpc.Core.1.19.0\build\net45\Grpc.Core.targets')" />
</Project>

View File

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Grpc.Core;
using Moq;
using NAPS2.Images;
using NAPS2.ImportExport.Email.Mapi;
using NAPS2.Scan;
using NAPS2.Scan.Twain;
using NAPS2.Worker;
using Xunit;
namespace NAPS2.Sdk.Tests.Worker
{
public class WorkerChannelTests
{
private Channel Start()
{
var twainWrapper = new Mock<ITwainWrapper>();
var thumbnailRenderer = new ThumbnailRenderer();
var mapiWrapper = new Mock<IMapiWrapper>();
Server server = new Server
{
Services = { GrpcWorkerService.BindService(new GrpcWorkerServiceImpl(twainWrapper.Object, thumbnailRenderer, mapiWrapper.Object)) },
Ports = { new ServerPort("localhost", 0, ServerCredentials.Insecure) }
};
server.Start();
var client = new GrpcWorkerServiceAdapter(server.Ports.First().BoundPort);
return new Channel
{
Server = server,
Client = client,
TwainWrapper = twainWrapper,
ThumbnailRenderer = thumbnailRenderer,
MapiWrapper = mapiWrapper
};
}
[Fact]
public void Init()
{
using (var channel = Start())
{
// TODO: When init actually does something, do a better test
channel.Client.Init("");
}
}
[Fact]
public void TwainGetDeviceList()
{
using (var channel = Start())
{
channel.TwainWrapper
.Setup(tw => tw.GetDeviceList(TwainImpl.OldDsm))
.Returns(new List<ScanDevice> { new ScanDevice("test_id", "test_name") });
var deviceList = channel.Client.TwainGetDeviceList(TwainImpl.OldDsm);
Assert.Single(deviceList);
Assert.Equal("test_id", deviceList[0].ID);
Assert.Equal("test_name", deviceList[0].Name);
channel.TwainWrapper.Verify(tw => tw.GetDeviceList(TwainImpl.OldDsm));
channel.TwainWrapper.VerifyNoOtherCalls();
}
}
private class Channel : IDisposable
{
public Server Server { get; set; }
public GrpcWorkerServiceAdapter Client { get; set; }
public Mock<ITwainWrapper> TwainWrapper { get; set; }
public ThumbnailRenderer ThumbnailRenderer { get; set; }
public Mock<IMapiWrapper> MapiWrapper { get; set; }
public void Dispose()
{
Server.KillAsync();
}
}
}
}

View File

@ -2,7 +2,10 @@
<packages>
<package id="BouncyCastle" version="1.8.4" targetFramework="net461" />
<package id="Castle.Core" version="4.3.1" targetFramework="net461" />
<package id="Grpc.Core" version="1.19.0" targetFramework="net461" />
<package id="Grpc.Core.Api" version="1.19.0" targetFramework="net461" />
<package id="Moq" version="4.10.1" targetFramework="net461" />
<package id="System.Interactive.Async" version="3.2.0" targetFramework="net461" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.0" targetFramework="net461" />
<package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net461" />
<package id="xunit" version="2.4.1" targetFramework="net461" />

View File

@ -38,6 +38,7 @@ namespace NAPS2.Images.Storage
// TODO: Better format choice?
var format = convertParams.Lossless ? ImageFormat.Png : ImageFormat.Jpeg;
input.Bitmap.Save(stream, format);
stream.Seek(0, SeekOrigin.Begin);
return new MemoryStreamStorage(stream);
}
}

View File

@ -0,0 +1,7 @@
namespace NAPS2.ImportExport.Email.Mapi
{
public interface IMapiWrapper
{
MapiSendMailReturnCode SendEmail(EmailMessage message);
}
}

View File

@ -14,10 +14,10 @@ namespace NAPS2.ImportExport.Email.Mapi
public class MapiEmailProvider : IEmailProvider
{
private readonly IWorkerServiceFactory workerServiceFactory;
private readonly MapiWrapper mapiWrapper;
private readonly IMapiWrapper mapiWrapper;
private readonly ErrorOutput errorOutput;
public MapiEmailProvider(IWorkerServiceFactory workerServiceFactory, MapiWrapper mapiWrapper, ErrorOutput errorOutput)
public MapiEmailProvider(IWorkerServiceFactory workerServiceFactory, IMapiWrapper mapiWrapper, ErrorOutput errorOutput)
{
this.workerServiceFactory = workerServiceFactory;
this.mapiWrapper = mapiWrapper;

View File

@ -6,7 +6,7 @@ using NAPS2.Util;
namespace NAPS2.ImportExport.Email.Mapi
{
public class MapiWrapper
public class MapiWrapper : IMapiWrapper
{
private readonly SystemEmailClients systemEmailClients;

View File

@ -188,6 +188,7 @@
<Compile Include="Images\ScannedImageSink.cs" />
<Compile Include="Images\Storage\PdfConverters.cs" />
<Compile Include="Images\Storage\PdfStorage.cs" />
<Compile Include="ImportExport\Email\Mapi\IMapiWrapper.cs" />
<Compile Include="ImportExport\Email\Mapi\MapiWrapper.cs" />
<Compile Include="ImportExport\Images\ImageSettingsProvider.cs" />
<Compile Include="ImportExport\Pdf\GhostscriptManager.cs" />
@ -255,6 +256,7 @@
<Compile Include="Scan\Sane\SaneOptionParser.cs" />
<Compile Include="Scan\Sane\SaneScanDriver.cs" />
<Compile Include="Scan\Sane\SaneWrapper.cs" />
<Compile Include="Scan\Twain\ITwainWrapper.cs" />
<Compile Include="Scan\Wia\Native\Hresult.cs" />
<Compile Include="Scan\Wia\Native\IWiaDeviceProps.cs" />
<Compile Include="Scan\Wia\Native\NativeStreamWrapper.cs" />
@ -290,6 +292,7 @@
<Compile Include="Platform\IRuntimeCompat.cs" />
<Compile Include="Platform\MonoRuntimeCompat.cs" />
<Compile Include="Util\ExpFallback.cs" />
<Compile Include="Worker\GrpcHelper.cs" />
<Compile Include="Util\IInvoker.cs" />
<Compile Include="Util\ImageExtensions.cs" />
<Compile Include="Util\Invoker.cs" />
@ -342,11 +345,7 @@
</Compile>
<Compile Include="Worker\GrpcWorkerServiceImpl.cs" />
<Compile Include="Worker\GrpcWorkerServiceAdapter.cs" />
<Compile Include="Worker\IWorkerCallback.cs" />
<Compile Include="Worker\ScanDriverExceptionDetail.cs" />
<Compile Include="Worker\WorkerCallback.cs" />
<Compile Include="Worker\WorkerContext.cs" />
<Compile Include="Worker\WorkerService.cs" />
<Compile Include="Icons.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
@ -453,7 +452,6 @@
<Compile Include="Operation\IOperation.cs" />
<Compile Include="Worker\IWorkerServiceFactory.cs" />
<Compile Include="Util\OverwritePrompt.cs" />
<Compile Include="Worker\IWorkerService.cs" />
<Compile Include="Util\ISaveNotify.cs" />
<Compile Include="Util\Lifecycle.cs" />
<Compile Include="Logging\Log.cs" />

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.Config;
@ -12,7 +11,6 @@ using NAPS2.Lang.Resources;
using NAPS2.Logging;
using NAPS2.Util;
using NAPS2.WinForms;
using NAPS2.Worker;
namespace NAPS2.Scan
{
@ -138,10 +136,11 @@ namespace NAPS2.Scan
{
error = e;
}
catch (FaultException<ScanDriverExceptionDetail> e)
{
error = e.Detail.Exception;
}
// TODO
//catch (FaultException<ScanDriverExceptionDetail> e)
//{
// error = e.Detail.Exception;
//}
catch (Exception e)
{
error = new ScanDriverUnknownException(e);

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using NAPS2.Images;
namespace NAPS2.Scan.Twain
{
public interface ITwainWrapper
{
List<ScanDevice> GetDeviceList(TwainImpl twainImpl);
void Scan(IntPtr dialogParent, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams,
CancellationToken cancelToken, ScannedImageSink sink, Action<ScannedImage, ScanParams, string> runBackgroundOcr);
}
}

View File

@ -15,7 +15,7 @@ namespace NAPS2.Scan.Twain
{
public const string DRIVER_NAME = "twain";
private readonly TwainWrapper twainWrapper;
private readonly ITwainWrapper twainWrapper;
private readonly ScannedImageHelper scannedImageHelper;
public TwainScanDriver() : base(ErrorOutput.Default, new AutoSaver())
@ -24,7 +24,7 @@ namespace NAPS2.Scan.Twain
scannedImageHelper = new ScannedImageHelper();
}
public TwainScanDriver(TwainWrapper twainWrapper, ScannedImageHelper scannedImageHelper, ErrorOutput errorOutput, AutoSaver autoSaver) : base(errorOutput, autoSaver)
public TwainScanDriver(ITwainWrapper twainWrapper, ScannedImageHelper scannedImageHelper, ErrorOutput errorOutput, AutoSaver autoSaver) : base(errorOutput, autoSaver)
{
this.twainWrapper = twainWrapper;
this.scannedImageHelper = scannedImageHelper;
@ -63,13 +63,11 @@ namespace NAPS2.Scan.Twain
{
using (var worker = WorkerManager.Factory.Create())
{
worker.Callback.ImageCallback += (img, tempPath) =>
await worker.Service.TwainScan(scanDevice, scanProfile, scanParams, dialogParent, cancelToken, (img, tempPath) =>
{
if (tempPath != null) scannedImageHelper.RunBackgroundOcr(img, scanParams, tempPath);
sink.PutImage(img);
};
cancelToken.Register(worker.Service.CancelTwainScan);
await worker.Service.TwainScan(scanDevice, scanProfile, scanParams, dialogParent);
});
}
}
else

View File

@ -18,7 +18,7 @@ using NAPS2.Util;
namespace NAPS2.Scan.Twain
{
public class TwainWrapper
public class TwainWrapper : ITwainWrapper
{
private static readonly TWIdentity TwainAppId = TWIdentity.CreateFromAssembly(DataGroups.Image | DataGroups.Control, Assembly.GetEntryAssembly());

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NAPS2.Util;
namespace NAPS2.Worker
{
public static class GrpcHelper
{
public static void HandleErrors(Error error)
{
if (!string.IsNullOrEmpty(error?.Xml))
{
var knownExceptionTypes = new Type[] { }; // TODO
var exception = error.Xml.FromXml<Exception>();
exception.PreserveStackTrace();
throw exception;
}
}
public static Task<TResponse> WrapFunc<TResponse>(Func<TResponse> action, Func<Error, TResponse> error) =>
Task.Run(() =>
{
try
{
return action();
}
catch (Exception e)
{
return error(new Error { Xml = e.ToXml() });
}
});
public static Task<TResponse> WrapFunc<TResponse>(Func<Task<TResponse>> action, Func<Error, TResponse> error) =>
Task.Run(async () =>
{
try
{
return await action();
}
catch (Exception e)
{
return error(new Error { Xml = e.ToXml() });
}
});
public static Task WrapAction(Action action, Action<Error> error) =>
Task.Run(() =>
{
try
{
action();
}
catch (Exception e)
{
error(new Error { Xml = e.ToXml() });
}
});
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using NAPS2.Images;
@ -12,38 +13,83 @@ using NAPS2.Util;
namespace NAPS2.Worker
{
class GrpcWorkerServiceAdapter : IWorkerService
public class GrpcWorkerServiceAdapter
{
private int port;
private GrpcWorkerService.GrpcWorkerServiceClient client;
public GrpcWorkerServiceAdapter(int port)
{
this.port = port;
this.client = new GrpcWorkerService.GrpcWorkerServiceClient(new Channel("localhost", port, ChannelCredentials.Insecure));
}
public void Init(string recoveryFolderPath)
{
var req = new InitRequest { RecoveryFolderPath = recoveryFolderPath };
var resp = client.Init(req);
GrpcHelper.HandleErrors(resp.Error);
}
public WiaConfiguration Wia10NativeUI(string scanDevice, IntPtr hwnd) => throw new NotImplementedException();
public WiaConfiguration Wia10NativeUI(string scanDevice, IntPtr hwnd)
{
var req = new Wia10NativeUiRequest
{
DeviceId = scanDevice,
Hwnd = (ulong)hwnd
};
var resp = client.Wia10NativeUi(req);
GrpcHelper.HandleErrors(resp.Error);
return resp.WiaConfigurationXml.FromXml<WiaConfiguration>();
}
public List<ScanDevice> TwainGetDeviceList(TwainImpl twainImpl)
{
var client = new GrpcWorkerService.GrpcWorkerServiceClient(new Channel("localhost", port, ChannelCredentials.Insecure));
var response = client.TwainGetDeviceList(new TwainGetDeviceListRequest { TwainImpl = twainImpl.ToXml() });
return response.DeviceListXml.FromXml<List<ScanDevice>>();
var req = new TwainGetDeviceListRequest { TwainImpl = twainImpl.ToXml() };
var resp = client.TwainGetDeviceList(req);
GrpcHelper.HandleErrors(resp.Error);
return resp.DeviceListXml.FromXml<List<ScanDevice>>();
}
public Task TwainScan(ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr hwnd) => throw new NotImplementedException();
public void CancelTwainScan()
public async Task TwainScan(ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr hwnd, CancellationToken cancelToken, Action<ScannedImage, string> imageCallback)
{
throw new NotImplementedException();
var req = new TwainScanRequest
{
ScanDeviceXml = scanDevice.ToXml(),
ScanProfileXml = scanProfile.ToXml(),
ScanParamsXml = scanParams.ToXml(),
Hwnd = (ulong)hwnd
};
var streamingCall = client.TwainScan(req, cancellationToken: cancelToken);
while (await streamingCall.ResponseStream.MoveNext())
{
var resp = streamingCall.ResponseStream.Current;
GrpcHelper.HandleErrors(resp.Error);
// TODO
//var scannedImage = new ScannedImage(image);
//if (thumbnail != null)
//{
// scannedImage.SetThumbnail(StorageManager.MemoryStorageFactory.Decode(new MemoryStream(thumbnail), ".bmp"));
//}
//ImageCallback?.Invoke(scannedImage, tempImageFilePath);
}
}
public MapiSendMailReturnCode SendMapiEmail(EmailMessage message) => throw new NotImplementedException();
public byte[] RenderThumbnail(ScannedImage.Snapshot snapshot, int size) => throw new NotImplementedException();
public void Init(string recoveryFolderPath)
public MapiSendMailReturnCode SendMapiEmail(EmailMessage message)
{
//throw new NotImplementedException();
var req = new SendMapiEmailRequest { EmailMessageXml = message.ToXml() };
var resp = client.SendMapiEmail(req);
GrpcHelper.HandleErrors(resp.Error);
return resp.ReturnCodeXml.FromXml<MapiSendMailReturnCode>();
}
public byte[] RenderThumbnail(ScannedImage.Snapshot snapshot, int size)
{
var req = new RenderThumbnailRequest
{
SnapshotXml = snapshot.ToXml(),
Size = size
};
var resp = client.RenderThumbnail(req);
GrpcHelper.HandleErrors(resp.Error);
return resp.Thumbnail.ToByteArray();
}
}
}

View File

@ -1,40 +1,144 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Core;
using NAPS2.Images;
using NAPS2.Images.Storage;
using NAPS2.ImportExport.Email;
using NAPS2.ImportExport.Email.Mapi;
using NAPS2.Scan;
using NAPS2.Scan.Twain;
using NAPS2.Scan.Wia;
using NAPS2.Scan.Wia.Native;
using NAPS2.Util;
namespace NAPS2.Worker
{
public class GrpcWorkerServiceImpl : GrpcWorkerService.GrpcWorkerServiceBase
{
private readonly TwainWrapper twainWrapper;
private readonly ITwainWrapper twainWrapper;
private readonly ThumbnailRenderer thumbnailRenderer;
private readonly MapiWrapper mapiWrapper;
private readonly IMapiWrapper mapiWrapper;
private CancellationTokenSource twainScanCts = new CancellationTokenSource();
public GrpcWorkerServiceImpl(TwainWrapper twainWrapper, ThumbnailRenderer thumbnailRenderer, MapiWrapper mapiWrapper)
public GrpcWorkerServiceImpl(ITwainWrapper twainWrapper, ThumbnailRenderer thumbnailRenderer, IMapiWrapper mapiWrapper)
{
this.twainWrapper = twainWrapper;
this.thumbnailRenderer = thumbnailRenderer;
this.mapiWrapper = mapiWrapper;
}
public override async Task<TwainGetDeviceListResponse> TwainGetDeviceList(TwainGetDeviceListRequest request, ServerCallContext context)
public override Task<InitResponse> Init(InitRequest request, ServerCallContext context) =>
GrpcHelper.WrapFunc(
() => new InitResponse(),
err => new InitResponse { Error = err });
public override Task<Wia10NativeUiResponse> Wia10NativeUi(Wia10NativeUiRequest request, ServerCallContext context) =>
GrpcHelper.WrapFunc(
() =>
{
try
{
using (var deviceManager = new WiaDeviceManager(WiaVersion.Wia10))
using (var device = deviceManager.FindDevice(request.DeviceId))
{
var item = device.PromptToConfigure((IntPtr)request.Hwnd);
return new Wia10NativeUiResponse
{
WiaConfigurationXml = new WiaConfiguration
{
DeviceProps = device.Properties.SerializeEditable(),
ItemProps = item.Properties.SerializeEditable(),
ItemName = item.Name()
}.ToXml()
};
}
}
catch (WiaException e)
{
WiaScanErrors.ThrowDeviceError(e);
throw new InvalidOperationException();
}
},
err => new Wia10NativeUiResponse { Error = err });
public override Task<TwainGetDeviceListResponse> TwainGetDeviceList(TwainGetDeviceListRequest request, ServerCallContext context) =>
GrpcHelper.WrapFunc(
() => new TwainGetDeviceListResponse
{
DeviceListXml = twainWrapper.GetDeviceList(request.TwainImpl.FromXml<TwainImpl>()).ToXml()
},
err => new TwainGetDeviceListResponse { Error = err });
public override Task TwainScan(TwainScanRequest request, IServerStreamWriter<TwainScanResponse> responseStream, ServerCallContext context) =>
GrpcHelper.WrapAction(
() =>
{
var imagePathDict = new Dictionary<ScannedImage, string>();
twainWrapper.Scan(
(IntPtr)request.Hwnd,
request.ScanDeviceXml.FromXml<ScanDevice>(),
request.ScanProfileXml.FromXml<ScanProfile>(),
request.ScanParamsXml.FromXml<ScanParams>(),
context.CancellationToken,
new WorkerImageSink(responseStream, imagePathDict),
(img, _, path) => imagePathDict.Add(img, path));
}, err => responseStream.WriteAsync(new TwainScanResponse { Error = err }));
public override Task<SendMapiEmailResponse> SendMapiEmail(SendMapiEmailRequest request, ServerCallContext context) =>
GrpcHelper.WrapFunc(
() => new SendMapiEmailResponse
{
ReturnCodeXml = mapiWrapper.SendEmail(request.EmailMessageXml.FromXml<EmailMessage>()).ToXml()
},
err => new SendMapiEmailResponse { Error = err });
public override Task<RenderThumbnailResponse> RenderThumbnail(RenderThumbnailRequest request, ServerCallContext context) =>
GrpcHelper.WrapFunc(
async () =>
{
var thumbnail = await thumbnailRenderer.Render(request.SnapshotXml.FromXml<ScannedImage.Snapshot>(), request.Size);
var stream = StorageManager.Convert<MemoryStreamStorage>(thumbnail, new StorageConvertParams { Lossless = true }).Stream;
return new RenderThumbnailResponse
{
Thumbnail = ByteString.FromStream(stream)
};
},
err => new RenderThumbnailResponse { Error = err });
private class WorkerImageSink : ScannedImageSink
{
return await Task.Run(() => new TwainGetDeviceListResponse
private readonly IServerStreamWriter<TwainScanResponse> callback;
private readonly Dictionary<ScannedImage, string> imagePathDict;
public WorkerImageSink(IServerStreamWriter<TwainScanResponse> callback, Dictionary<ScannedImage, string> imagePathDict)
{
DeviceListXml = twainWrapper.GetDeviceList(request.TwainImpl.FromXml<TwainImpl>()).ToXml()
});
this.callback = callback;
this.imagePathDict = imagePathDict;
}
public override void PutImage(ScannedImage image)
{
// TODO: Ideally this shouldn't be inheriting ScannedImageSink, some other cleaner mechanism
// TODO
MemoryStream stream = null;
var thumb = image.GetThumbnail();
if (thumb != null)
{
stream = StorageManager.Convert<MemoryStreamStorage>(thumb, new StorageConvertParams { Lossless = true }).Stream;
}
//callback.WriteAsync(new TwainScanResponse
//{
// RecoveryIndexImageXml = image.
//});
//callback.TwainImageReceived(image.RecoveryIndexImage, stream?.ToArray(), imagePathDict.Get(image));
}
}
}
}

View File

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using NAPS2.Recovery;
using NAPS2.Images;
namespace NAPS2.Worker
{
[ServiceContract]
public interface IWorkerCallback
{
[OperationContract(IsOneWay = true)]
void TwainImageReceived(RecoveryIndexImage image, byte[] thumbnail, string tempImageFilePath);
event Action<ScannedImage, string> ImageCallback;
}
}

View File

@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Threading.Tasks;
using NAPS2.ImportExport.Email;
using NAPS2.ImportExport.Email.Mapi;
using NAPS2.Scan;
using NAPS2.Images;
using NAPS2.Scan.Wia;
namespace NAPS2.Worker
{
/// <summary>
/// The WCF service interface for NAPS2.Worker.exe.
/// </summary>
[ServiceContract(CallbackContract = typeof(IWorkerCallback))]
public interface IWorkerService
{
[OperationContract]
void Init(string recoveryFolderPath);
[FaultContract(typeof(ScanDriverExceptionDetail))]
[OperationContract]
WiaConfiguration Wia10NativeUI(string scanDevice, IntPtr hwnd);
[OperationContract]
List<ScanDevice> TwainGetDeviceList(TwainImpl twainImpl);
[FaultContract(typeof(ScanDriverExceptionDetail))]
[OperationContract]
Task TwainScan(ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr hwnd);
[OperationContract]
void CancelTwainScan();
[OperationContract]
MapiSendMailReturnCode SendMapiEmail(EmailMessage message);
[OperationContract]
byte[] RenderThumbnail(ScannedImage.Snapshot snapshot, int size);
}
}

View File

@ -1,35 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using NAPS2.Scan.Exceptions;
using NAPS2.Util;
namespace NAPS2.Worker
{
public class ScanDriverExceptionDetail
{
public ScanDriverExceptionDetail()
{
}
public ScanDriverExceptionDetail(ScanDriverException e)
{
var stream = new MemoryStream();
new NetDataContractSerializer().Serialize(stream, e);
SerializedException = stream.ToArray();
}
public byte[] SerializedException { get; set; }
public ScanDriverException Exception => (ScanDriverException)new NetDataContractSerializer().Deserialize(new MemoryStream(SerializedException));
public void Throw()
{
var exception = Exception;
exception.PreserveStackTrace();
throw exception;
}
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NAPS2.Recovery;
using NAPS2.Images;
namespace NAPS2.Worker
{
public class WorkerCallback : IWorkerCallback
{
public event Action<ScannedImage, string> ImageCallback;
public void TwainImageReceived(RecoveryIndexImage image, byte[] thumbnail, string tempImageFilePath)
{
// TODO
//var scannedImage = new ScannedImage(image);
//if (thumbnail != null)
//{
// scannedImage.SetThumbnail(StorageManager.MemoryStorageFactory.Decode(new MemoryStream(thumbnail), ".bmp"));
//}
//ImageCallback?.Invoke(scannedImage, tempImageFilePath);
}
}
}

View File

@ -12,25 +12,12 @@ namespace NAPS2.Worker
/// </summary>
public class WorkerContext : IDisposable
{
public IWorkerService Service { get; set; }
public IWorkerCallback Callback { get; set; }
public GrpcWorkerServiceAdapter Service { get; set; }
public Process Process { get; set; }
public void Dispose()
{
try
{
((IDisposable) Service)?.Dispose();
}
catch (CommunicationObjectFaultedException)
{
}
catch (Exception e)
{
Log.ErrorException("Error cleaning up worker", e);
}
try
{
Process.Kill();

View File

@ -5,7 +5,6 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.Threading.Tasks;
using NAPS2.Platform;
@ -16,7 +15,6 @@ namespace NAPS2.Worker
/// </summary>
public static class WorkerManager
{
public const string PIPE_NAME_FORMAT = "net.pipe://localhost/NAPS2.Worker/{0}";
public const string WORKER_EXE_NAME = "NAPS2.Worker.exe";
public static readonly string[] SearchDirs =
{
@ -86,18 +84,7 @@ namespace NAPS2.Worker
Task.Factory.StartNew(() =>
{
var (proc, port) = StartWorkerProcess();
//var pipeName = string.Format(PIPE_NAME_FORMAT, proc.Id);
//var callback = new WorkerCallback();
//var instanceContext = new InstanceContext(callback);
//var channelFactory = new DuplexChannelFactory<IWorkerService>(instanceContext,
// new NetNamedPipeBinding
// {
// SendTimeout = TimeSpan.FromHours(24),
// MaxReceivedMessageSize = int.MaxValue
// },
// new EndpointAddress(pipeName));
//var channel = channelFactory.CreateChannel();
_workerQueue.Add(new WorkerContext { Service = new GrpcWorkerServiceAdapter(port), Callback = null, Process = proc });
_workerQueue.Add(new WorkerContext { Service = new GrpcWorkerServiceAdapter(port), Process = proc });
});
}

View File

@ -1,153 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.ImportExport.Email;
using NAPS2.ImportExport.Email.Mapi;
using NAPS2.Scan;
using NAPS2.Scan.Exceptions;
using NAPS2.Images;
using NAPS2.Scan.Twain;
using NAPS2.Scan.Wia;
using NAPS2.Scan.Wia.Native;
namespace NAPS2.Worker
{
/// <summary>
/// The WCF service implementation for NAPS2.Worker.exe.
/// </summary>
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,
IncludeExceptionDetailInFaults = true,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class WorkerService : IWorkerService
{
private readonly TwainWrapper twainWrapper;
private readonly ThumbnailRenderer thumbnailRenderer;
private readonly MapiWrapper mapiWrapper;
private CancellationTokenSource twainScanCts = new CancellationTokenSource();
public WorkerService(TwainWrapper twainWrapper, ThumbnailRenderer thumbnailRenderer, MapiWrapper mapiWrapper)
{
this.twainWrapper = twainWrapper;
this.thumbnailRenderer = thumbnailRenderer;
this.mapiWrapper = mapiWrapper;
}
public void Init(string recoveryFolderPath)
{
Callback = OperationContext.Current.GetCallbackChannel<IWorkerCallback>();
// TODO
//RecoveryImage.RecoveryFolder = new DirectoryInfo(recoveryFolderPath);
}
public WiaConfiguration Wia10NativeUI(string deviceId, IntPtr hwnd)
{
try
{
try
{
using (var deviceManager = new WiaDeviceManager(WiaVersion.Wia10))
using (var device = deviceManager.FindDevice(deviceId))
{
var item = device.PromptToConfigure(hwnd);
return new WiaConfiguration
{
DeviceProps = device.Properties.SerializeEditable(),
ItemProps = item.Properties.SerializeEditable(),
ItemName = item.Name()
};
}
}
catch (WiaException e)
{
WiaScanErrors.ThrowDeviceError(e);
throw new InvalidOperationException();
}
}
catch (ScanDriverException e)
{
throw new FaultException<ScanDriverExceptionDetail>(new ScanDriverExceptionDetail(e));
}
}
public List<ScanDevice> TwainGetDeviceList(TwainImpl twainImpl)
{
return twainWrapper.GetDeviceList(twainImpl);
}
public async Task TwainScan(ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr hwnd)
{
try
{
await Task.Factory.StartNew(() =>
{
var imagePathDict = new Dictionary<ScannedImage, string>();
twainWrapper.Scan(hwnd, scanDevice, scanProfile, scanParams, twainScanCts.Token,
new WorkerImageSink(Callback, imagePathDict), (img, _, path) => imagePathDict.Add(img, path));
}, TaskCreationOptions.LongRunning);
}
catch (ScanDriverException e)
{
throw new FaultException<ScanDriverExceptionDetail>(new ScanDriverExceptionDetail(e));
}
}
public void CancelTwainScan()
{
twainScanCts.Cancel();
}
public MapiSendMailReturnCode SendMapiEmail(EmailMessage message)
{
return mapiWrapper.SendEmail(message);
}
public byte[] RenderThumbnail(ScannedImage.Snapshot snapshot, int size)
{
var stream = new MemoryStream();
// TODO
//using (var bitmap = Task.Factory.StartNew(() => thumbnailRenderer.RenderThumbnail(snapshot, size)).Unwrap().Result)
//{
// bitmap.Save(stream, ImageFormat.Png);
//}
return stream.ToArray();
}
public void Dispose()
{
}
public IWorkerCallback Callback { get; set; }
private class WorkerImageSink : ScannedImageSink
{
private readonly IWorkerCallback callback;
private readonly Dictionary<ScannedImage, string> imagePathDict;
public WorkerImageSink(IWorkerCallback callback, Dictionary<ScannedImage, string> imagePathDict)
{
this.callback = callback;
this.imagePathDict = imagePathDict;
}
public override void PutImage(ScannedImage image)
{
// TODO: Ideally this shouldn't be inheriting ScannedImageSink, some other cleaner mechanism
// TODO
//MemoryStream stream = null;
//var thumb = image.GetThumbnail();
//if (thumb != null)
//{
// stream = new MemoryStream();
// thumb.Save(stream, ImageFormat.Png);
//}
//callback.TwainImageReceived(image.RecoveryIndexImage, stream?.ToArray(), imagePathDict.Get(image));
}
}
}
}

View File

@ -6,6 +6,9 @@ service GrpcWorkerService {
rpc Init (InitRequest) returns (InitResponse) {}
rpc Wia10NativeUi (Wia10NativeUiRequest) returns (Wia10NativeUiResponse) {}
rpc TwainGetDeviceList (TwainGetDeviceListRequest) returns (TwainGetDeviceListResponse) {}
rpc TwainScan (TwainScanRequest) returns (stream TwainScanResponse) {}
rpc SendMapiEmail (SendMapiEmailRequest) returns (SendMapiEmailResponse) {}
rpc RenderThumbnail (RenderThumbnailRequest) returns (RenderThumbnailResponse) {}
}
message Error {
@ -39,4 +42,35 @@ message TwainGetDeviceListResponse {
string deviceListXml = 2;
}
// TODO more. Use streaming for scan callback/cancel.
message TwainScanRequest {
string scanDeviceXml = 1;
string scanProfileXml = 2;
string scanParamsXml = 3;
fixed64 hwnd = 4;
}
message TwainScanResponse {
Error error = 1;
string recoveryIndexImageXml = 2;
bytes thumbnail = 3;
string tempImageFilePath = 4;
}
message SendMapiEmailRequest {
string emailMessageXml = 1;
}
message SendMapiEmailResponse {
Error error = 1;
string returnCodeXml = 2;
}
message RenderThumbnailRequest {
string snapshotXml = 1;
int32 size = 2;
}
message RenderThumbnailResponse {
Error error = 1;
bytes thumbnail = 2;
}

View File

@ -6,6 +6,9 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters /&gt;&lt;/data&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Grpc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=hwnd/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jpegs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Lineart/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mapi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pdfs/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>