diff --git a/NAPS2.Core/ImportExport/Pdf/SavePdfOperation.cs b/NAPS2.Core/ImportExport/Pdf/SavePdfOperation.cs index b26341bfd..9be369732 100644 --- a/NAPS2.Core/ImportExport/Pdf/SavePdfOperation.cs +++ b/NAPS2.Core/ImportExport/Pdf/SavePdfOperation.cs @@ -21,7 +21,7 @@ namespace NAPS2.ImportExport.Pdf private readonly IOverwritePrompt overwritePrompt; private readonly ThreadFactory threadFactory; private readonly AppConfigManager appConfigManager; - + private Thread thread; public SavePdfOperation(FileNamePlaceholders fileNamePlaceholders, IPdfExporter pdfExporter, IOverwritePrompt overwritePrompt, ThreadFactory threadFactory, AppConfigManager appConfigManager, IWorkerServiceFactory workerServiceFactory) @@ -64,20 +64,13 @@ namespace NAPS2.ImportExport.Pdf { try { - if (UseWorker) + Status.Success = DoWork(new SavePdfWorkArgs { - using (var worker = WorkerServiceFactory.Create()) - { - worker.Service.SetRecoveryFolder(RecoveryImage.RecoveryFolder.FullName); - worker.Callback.OnProgress += OnProgress; - worker.Service.ExportPdf(subFileName, snapshots.Export(), pdfSettings, ocrLanguageCode); - Status.Success = worker.Callback.WaitForFinish(); - } - } - else - { - Status.Success = pdfExporter.Export(subFileName, snapshots, pdfSettings, ocrLanguageCode, OnProgress); - } + SubFileName = subFileName, + Snapshots = snapshots.Export(), + PdfSettings = pdfSettings, + OcrLanguageCode = ocrLanguageCode + }); } catch (UnauthorizedAccessException ex) { @@ -114,9 +107,24 @@ namespace NAPS2.ImportExport.Pdf return true; } + protected internal override bool DoWorkInternal(WorkArgs args) + { + var a = (SavePdfWorkArgs)args; + return pdfExporter.Export(a.SubFileName, a.Snapshots.Import(), a.PdfSettings, a.OcrLanguageCode, OnProgress); + } + public override void WaitUntilFinished() { thread.Join(); } + + [Serializable] + internal class SavePdfWorkArgs : WorkArgs + { + public string SubFileName { get; set; } + public List Snapshots { get; set; } + public PdfSettings PdfSettings { get; set; } + public string OcrLanguageCode { get; set; } + } } } diff --git a/NAPS2.Core/NAPS2.Core.csproj b/NAPS2.Core/NAPS2.Core.csproj index 47818539c..dab8052bc 100644 --- a/NAPS2.Core/NAPS2.Core.csproj +++ b/NAPS2.Core/NAPS2.Core.csproj @@ -5282,13 +5282,13 @@ - + diff --git a/NAPS2.Core/Operation/OperationBase.cs b/NAPS2.Core/Operation/OperationBase.cs index e663ab87f..21f33365b 100644 --- a/NAPS2.Core/Operation/OperationBase.cs +++ b/NAPS2.Core/Operation/OperationBase.cs @@ -46,8 +46,9 @@ namespace NAPS2.Operation Error?.Invoke(this, args); } - protected bool OnProgress(int current, int max) + protected virtual bool OnProgress(int current, int max) { + // TODO: Maybe don't make this virtual. Instead, clone the status object, and project event invocations back to the client. Status.CurrentProgress = current; Status.MaxProgress = max; InvokeStatusChanged(); diff --git a/NAPS2.Core/Operation/WorkerOperation.cs b/NAPS2.Core/Operation/WorkerOperation.cs index eb7f6da80..acbc1f357 100644 --- a/NAPS2.Core/Operation/WorkerOperation.cs +++ b/NAPS2.Core/Operation/WorkerOperation.cs @@ -1,19 +1,59 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using NAPS2.Recovery; +using NAPS2.Util; using NAPS2.Worker; namespace NAPS2.Operation { public abstract class WorkerOperation : OperationBase { + private readonly IWorkerServiceFactory workerServiceFactory; + protected WorkerOperation(IWorkerServiceFactory workerServiceFactory) { - WorkerServiceFactory = workerServiceFactory; + this.workerServiceFactory = workerServiceFactory; } - protected bool UseWorker => !Environment.Is64BitProcess; + protected virtual bool UseWorker => !Environment.Is64BitProcess; - protected IWorkerServiceFactory WorkerServiceFactory { get; } + public ProgressHandler ProgressProxy { get; set; } + + protected bool DoWork(WorkArgs args) + { + if (UseWorker) + { + using (var worker = workerServiceFactory.Create()) + { + worker.Service.SetRecoveryFolder(RecoveryImage.RecoveryFolder.FullName); + worker.Callback.OnProgress += OnProgress; + worker.Service.DoOperationWork(GetType().FullName, args); + return worker.Callback.WaitForFinish(); + } + } + + return DoWorkInternal(args); + } + + protected internal abstract bool DoWorkInternal(WorkArgs args); + + protected override bool OnProgress(int current, int max) + { + return ProgressProxy?.Invoke(current, max) ?? base.OnProgress(current, max); + } + + [KnownType("DerivedTypes")] + [Serializable] + public class WorkArgs + { + // ReSharper disable once UnusedMember.Local + private static Type[] DerivedTypes() + { + return Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsSubclassOf(typeof(WorkArgs))).ToArray(); + } + } } } \ No newline at end of file diff --git a/NAPS2.Core/Worker/IWorkerService.cs b/NAPS2.Core/Worker/IWorkerService.cs index af96638a7..729973630 100644 --- a/NAPS2.Core/Worker/IWorkerService.cs +++ b/NAPS2.Core/Worker/IWorkerService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.ServiceModel; using NAPS2.ImportExport.Pdf; +using NAPS2.Operation; using NAPS2.Recovery; using NAPS2.Scan; using NAPS2.Scan.Images; @@ -25,6 +26,6 @@ namespace NAPS2.Worker List TwainScan(int recoveryFileNumber, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams); [OperationContract(IsOneWay = true)] - void ExportPdf(string subFileName, List snapshots, PdfSettings pdfSettings, string ocrLanguageCode); + void DoOperationWork(string operationTypeName, WorkerOperation.WorkArgs args); } } diff --git a/NAPS2.Core/Worker/WorkerService.cs b/NAPS2.Core/Worker/WorkerService.cs index 4eaa702f2..e39192d56 100644 --- a/NAPS2.Core/Worker/WorkerService.cs +++ b/NAPS2.Core/Worker/WorkerService.cs @@ -2,15 +2,18 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.Serialization; using System.ServiceModel; using System.Windows.Forms; using NAPS2.ImportExport.Pdf; +using NAPS2.Operation; using NAPS2.Recovery; using NAPS2.Scan; using NAPS2.Scan.Images; using NAPS2.Scan.Images.Transforms; using NAPS2.Scan.Twain; +using NAPS2.Util; namespace NAPS2.Worker { @@ -21,13 +24,15 @@ namespace NAPS2.Worker { private readonly TwainWrapper twainWrapper; private readonly IPdfExporter pdfExporter; + private readonly IOperationFactory operationFactory; public Form ParentForm { get; set; } - public WorkerService(TwainWrapper twainWrapper, IPdfExporter pdfExporter) + public WorkerService(TwainWrapper twainWrapper, IPdfExporter pdfExporter, IOperationFactory operationFactory) { this.twainWrapper = twainWrapper; this.pdfExporter = pdfExporter; + this.operationFactory = operationFactory; } public void Init() @@ -55,23 +60,32 @@ namespace NAPS2.Worker public void Dispose() { } - - public void ExportPdf(string subFileName, List snapshots, PdfSettings pdfSettings, string ocrLanguageCode) + + public void DoOperationWork(string operationTypeName, WorkerOperation.WorkArgs args) { // TODO: Make a type for a serializable snapshot // TODO: Other operations. Import. Recovery. Save images. Password and ghostscript callbacks. // Figure out ghostscript operation in general. // Also - consider off-process thumbnail rendering. That's probably IO bound though, right? // So parellization doesn't help. The only benefit would be memory. Which is not a bad benefit. - WrapOperation(() => pdfExporter.Export(subFileName, snapshots.Import(), pdfSettings, ocrLanguageCode, Callback.Progress)); - } + var operationType = Type.GetType(operationTypeName); + if (operationType == null) + { + Log.Error($"Operation type not available: {operationTypeName}"); + return; + } + var op = (WorkerOperation)typeof(IOperationFactory).GetMethod("Create")?.MakeGenericMethod(operationType).Invoke(operationFactory, new object[0]); + if (op == null) + { + Log.Error($"Could not create operation: {operationTypeName}"); + return; + } - private void WrapOperation(Func op) - { bool success = false; try { - success = op(); + op.ProgressProxy = Callback.Progress; + success = op.DoWorkInternal(args); } catch (Exception e) { @@ -82,7 +96,7 @@ namespace NAPS2.Worker Callback.Finish(success); } } - + public IWorkerCallback Callback { get; set; } } }