mirror of
https://github.com/cyanfish/naps2.git
synced 2024-09-21 12:49:43 +03:00
Improve scan error handling
This commit is contained in:
parent
eaa90f6d7f
commit
02ecc927e5
@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NAPS2.Config;
|
||||
using NAPS2.Images;
|
||||
using NAPS2.Images.Storage;
|
||||
|
||||
namespace NAPS2.Sdk.Tests
|
||||
@ -48,6 +50,11 @@ namespace NAPS2.Sdk.Tests
|
||||
ImageContext.ImageMetadataFactory = rsm;
|
||||
}
|
||||
|
||||
public ScannedImage CreateScannedImage()
|
||||
{
|
||||
return ImageContext.CreateScannedImage(new GdiImage(new Bitmap(100, 100)));
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
rsm?.ForceReleaseLock();
|
||||
|
31
NAPS2.Sdk.Tests/Mocks/StubScanBridge.cs
Normal file
31
NAPS2.Sdk.Tests/Mocks/StubScanBridge.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NAPS2.Images;
|
||||
using NAPS2.Scan;
|
||||
using NAPS2.Scan.Experimental;
|
||||
using NAPS2.Scan.Experimental.Internal;
|
||||
|
||||
namespace NAPS2.Sdk.Tests.Mocks
|
||||
{
|
||||
internal class StubScanBridge : IScanBridge
|
||||
{
|
||||
public List<ScanDevice> MockDevices { get; set; } = new List<ScanDevice>();
|
||||
|
||||
public List<ScannedImage> MockOutput { get; set; } = new List<ScannedImage>();
|
||||
|
||||
public List<ScanDevice> GetDeviceList(ScanOptions options) => MockDevices;
|
||||
|
||||
public async Task Scan(ScanOptions options, CancellationToken cancelToken, IScanEvents scanEvents, Action<ScannedImage, PostProcessingContext> callback)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var img in MockOutput)
|
||||
{
|
||||
callback(img, new PostProcessingContext());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -131,6 +131,7 @@
|
||||
<DependentUpon>PdfiumTestsData.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Mocks\MockScanDriver.cs" />
|
||||
<Compile Include="Mocks\StubScanBridge.cs" />
|
||||
<Compile Include="Scan\AutoSaveTests.cs" />
|
||||
<Compile Include="Config\ProfileSerializerTestsData.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
@ -139,6 +140,7 @@
|
||||
</Compile>
|
||||
<Compile Include="ContextualTexts.cs" />
|
||||
<Compile Include="Scan\RemoteScanControllerTests.cs" />
|
||||
<Compile Include="Scan\ScanErrorHandling.cs" />
|
||||
<Compile Include="Serialization\XmlSerializerTests.cs" />
|
||||
<Compile Include="Util\NaturalStringComparerTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
@ -178,10 +178,5 @@ namespace NAPS2.Sdk.Tests.Scan
|
||||
new BitmapRenderer(ImageContext),
|
||||
new StubConfigProvider<CommonConfig>(InternalDefaults.GetCommonConfig()));
|
||||
}
|
||||
|
||||
private ScannedImage CreateScannedImage()
|
||||
{
|
||||
return ImageContext.CreateScannedImage(new GdiImage(new Bitmap(100, 100)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
52
NAPS2.Sdk.Tests/Scan/ScanErrorHandling.cs
Normal file
52
NAPS2.Sdk.Tests/Scan/ScanErrorHandling.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NAPS2.Images;
|
||||
using NAPS2.Scan.Experimental;
|
||||
using NAPS2.Scan.Experimental.Internal;
|
||||
using NAPS2.Sdk.Tests.Mocks;
|
||||
using Xunit;
|
||||
|
||||
namespace NAPS2.Sdk.Tests.Scan
|
||||
{
|
||||
public class ScanErrorHandling : ContextualTexts
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidOptions()
|
||||
{
|
||||
var localPostProcessor = new Mock<ILocalPostProcessor>();
|
||||
var bridgeFactory = new Mock<IScanBridgeFactory>();
|
||||
var controller = new ScanController(localPostProcessor.Object, new ScanOptionsValidator(), bridgeFactory.Object);
|
||||
|
||||
var invalidOptions = new ScanOptions { Dpi = -1 };
|
||||
Assert.Throws<ArgumentException>(() => controller.Scan(invalidOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void CreateScanBridge()
|
||||
{
|
||||
var localPostProcessor = new Mock<ILocalPostProcessor>();
|
||||
var bridgeFactory = new Mock<IScanBridgeFactory>();
|
||||
var controller = new ScanController(localPostProcessor.Object, new ScanOptionsValidator(), bridgeFactory.Object);
|
||||
|
||||
bridgeFactory.Setup(factory => factory.Create(It.IsAny<ScanOptions>())).Throws<InvalidOperationException>();
|
||||
var source = controller.Scan(new ScanOptions());
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(source.ToList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void LocalPostProcess()
|
||||
{
|
||||
var localPostProcessor = new Mock<ILocalPostProcessor>();
|
||||
var bridge = new StubScanBridge { MockOutput = new List<ScannedImage> { CreateScannedImage() } };
|
||||
var bridgeFactory = new Mock<IScanBridgeFactory>();
|
||||
var controller = new ScanController(localPostProcessor.Object, new ScanOptionsValidator(), bridgeFactory.Object);
|
||||
|
||||
bridgeFactory.Setup(factory => factory.Create(It.IsAny<ScanOptions>())).Returns(bridge);
|
||||
localPostProcessor.Setup(pp => pp.PostProcess(It.IsAny<ScannedImage>(), It.IsAny<ScanOptions>(), It.IsAny<PostProcessingContext>()))
|
||||
.Throws<InvalidOperationException>();
|
||||
var source = controller.Scan(new ScanOptions());
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(source.ToList);
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ namespace NAPS2.Images
|
||||
{
|
||||
if (Completed)
|
||||
{
|
||||
throw new InvalidOperationException("Sink is already in the completed state");
|
||||
return;
|
||||
}
|
||||
Completed = true;
|
||||
images.Last().SetResult(null);
|
||||
|
@ -298,7 +298,7 @@
|
||||
<Compile Include="Scan\Experimental\ScanStartEventArgs.cs" />
|
||||
<Compile Include="Scan\Experimental\TwainOptions.cs" />
|
||||
<Compile Include="Scan\Experimental\WiaOptions.cs" />
|
||||
<Compile Include="Scan\Experimental\Internal\WorkerProcessScanBridge.cs" />
|
||||
<Compile Include="Scan\Experimental\Internal\WorkerScanBridge.cs" />
|
||||
<Compile Include="Scan\KeyValueScanOptions.cs" />
|
||||
<Compile Include="Scan\Exceptions\SaneNotAvailableException.cs" />
|
||||
<Compile Include="Scan\Sane\SaneOption.cs" />
|
||||
|
@ -49,6 +49,8 @@ namespace NAPS2.Scan.Experimental.Internal
|
||||
}
|
||||
|
||||
// TODO: Validate DoOcr based on OcrParams
|
||||
// TODO: Do we need to validate the presence of a device?
|
||||
// TODO: Probably more things as well.
|
||||
|
||||
return options;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NAPS2.Images;
|
||||
using NAPS2.Scan.Experimental.Internal;
|
||||
|
||||
@ -39,7 +40,6 @@ namespace NAPS2.Scan.Experimental
|
||||
public ScannedImageSource Scan(ScanOptions options, CancellationToken cancelToken = default)
|
||||
{
|
||||
options = scanOptionsValidator.ValidateAll(options);
|
||||
var bridge = scanBridgeFactory.Create(options);
|
||||
var sink = new ScannedImageSink();
|
||||
int pageNumber = 0;
|
||||
|
||||
@ -51,25 +51,27 @@ namespace NAPS2.Scan.Experimental
|
||||
void PageEndCallback(ScannedImage image) => PageEnd?.Invoke(this, new PageEndEventArgs(pageNumber, image));
|
||||
|
||||
ScanStartCallback();
|
||||
bridge.Scan(options, cancelToken, new ScanEvents(PageStartCallback, PageProgressCallback), (scannedImage, postProcessingContext) =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
localPostProcessor.PostProcess(scannedImage, options, postProcessingContext);
|
||||
sink.PutImage(scannedImage);
|
||||
PageEndCallback(scannedImage);
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
sink.SetError(t.Exception);
|
||||
ScanErrorCallback(t.Exception);
|
||||
}
|
||||
else
|
||||
try
|
||||
{
|
||||
var bridge = scanBridgeFactory.Create(options);
|
||||
await bridge.Scan(options, cancelToken, new ScanEvents(PageStartCallback, PageProgressCallback),
|
||||
(scannedImage, postProcessingContext) =>
|
||||
{
|
||||
localPostProcessor.PostProcess(scannedImage, options, postProcessingContext);
|
||||
sink.PutImage(scannedImage);
|
||||
PageEndCallback(scannedImage);
|
||||
});
|
||||
sink.SetCompleted();
|
||||
ScanEndCallback(sink.AsSource());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sink.SetError(ex);
|
||||
ScanErrorCallback(ex);
|
||||
}
|
||||
ScanEndCallback(sink.AsSource());
|
||||
});
|
||||
|
||||
return sink.AsSource();
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,9 @@ using System.Threading.Tasks;
|
||||
using NAPS2.Config;
|
||||
using NAPS2.Images;
|
||||
using NAPS2.ImportExport;
|
||||
using NAPS2.Logging;
|
||||
using NAPS2.Operation;
|
||||
using NAPS2.Scan.Exceptions;
|
||||
using NAPS2.Scan.Wia.Native;
|
||||
using NAPS2.Util;
|
||||
using NAPS2.WinForms;
|
||||
@ -20,14 +22,17 @@ namespace NAPS2.Scan.Experimental
|
||||
private readonly OperationProgress operationProgress;
|
||||
private readonly AutoSaver autoSaver;
|
||||
private readonly IProfileManager profileManager;
|
||||
private readonly ErrorOutput errorOutput;
|
||||
|
||||
public ScanPerformer(IFormFactory formFactory, ConfigProvider<CommonConfig> configProvider, OperationProgress operationProgress, AutoSaver autoSaver, IProfileManager profileManager)
|
||||
public ScanPerformer(IFormFactory formFactory, ConfigProvider<CommonConfig> configProvider, OperationProgress operationProgress, AutoSaver autoSaver,
|
||||
IProfileManager profileManager, ErrorOutput errorOutput)
|
||||
{
|
||||
this.formFactory = formFactory;
|
||||
this.configProvider = configProvider;
|
||||
this.operationProgress = operationProgress;
|
||||
this.autoSaver = autoSaver;
|
||||
this.profileManager = profileManager;
|
||||
this.errorOutput = errorOutput;
|
||||
}
|
||||
|
||||
public ScannedImageSource PerformScan(ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent = default,
|
||||
@ -39,11 +44,13 @@ namespace NAPS2.Scan.Experimental
|
||||
// User cancelled out of a dialog
|
||||
return ScannedImageSource.Empty;
|
||||
}
|
||||
|
||||
var controller = new ScanController();
|
||||
var op = new ScanOperation(options.Device, options.PaperSource);
|
||||
|
||||
controller.PageStart += (sender, args) => op.NextPage(args.PageNumber);
|
||||
controller.ScanEnd += (sender, args) => op.Completed();
|
||||
controller.ScanError += (sender, args) => HandleError(args.Exception);
|
||||
TranslateProgress(controller, op);
|
||||
|
||||
ShowOperation(op, scanParams);
|
||||
@ -55,7 +62,29 @@ namespace NAPS2.Scan.Experimental
|
||||
{
|
||||
source = autoSaver.Save(scanProfile.AutoSaveSettings, source);
|
||||
}
|
||||
return source;
|
||||
|
||||
// Errors are handled by the ScanError callback
|
||||
return SwallowErrors(source);
|
||||
}
|
||||
|
||||
private void HandleError(Exception error)
|
||||
{
|
||||
if (error is ScanDriverUnknownException)
|
||||
{
|
||||
Log.ErrorException(error.Message, error.InnerException);
|
||||
errorOutput?.DisplayError(error.Message, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
errorOutput?.DisplayError(error.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private ScannedImageSource SwallowErrors(ScannedImageSource source)
|
||||
{
|
||||
var sink = new ScannedImageSink();
|
||||
source.ForEach(img => sink.PutImage(img)).ContinueWith(t => sink.SetCompleted());
|
||||
return sink.AsSource();
|
||||
}
|
||||
|
||||
private void ShowOperation(ScanOperation op, ScanParams scanParams)
|
||||
@ -90,9 +119,9 @@ namespace NAPS2.Scan.Experimental
|
||||
var options = new ScanOptions
|
||||
{
|
||||
Driver = scanProfile.DriverName == "wia" ? Driver.Wia
|
||||
: scanProfile.DriverName == "sane" ? Driver.Sane
|
||||
: scanProfile.DriverName == "twain" ? Driver.Twain
|
||||
: Driver.Default,
|
||||
: scanProfile.DriverName == "sane" ? Driver.Sane
|
||||
: scanProfile.DriverName == "twain" ? Driver.Twain
|
||||
: Driver.Default,
|
||||
WiaOptions =
|
||||
{
|
||||
WiaVersion = scanProfile.WiaVersion,
|
||||
@ -146,12 +175,13 @@ namespace NAPS2.Scan.Experimental
|
||||
Device = null, // Set after
|
||||
PageSize = null, // Set after
|
||||
};
|
||||
|
||||
|
||||
PageDimensions pageDimensions = scanProfile.PageSize.PageDimensions() ?? scanProfile.CustomPageSize;
|
||||
if (pageDimensions == null)
|
||||
{
|
||||
throw new ArgumentException("No page size specified");
|
||||
}
|
||||
|
||||
options.PageSize = new PageSize(pageDimensions.Width, pageDimensions.Height, pageDimensions.Unit);
|
||||
|
||||
// If a device wasn't specified, prompt the user to pick one
|
||||
@ -167,6 +197,7 @@ namespace NAPS2.Scan.Experimental
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
options.Device = new ScanDevice(wiaDevice.Id(), wiaDevice.Name());
|
||||
}
|
||||
}
|
||||
@ -180,6 +211,7 @@ namespace NAPS2.Scan.Experimental
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
options.Device = deviceForm.SelectedDevice;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user