Use an injected install prompt for ghostscript to behave correctly on console, and add an "--install" argument to console

This commit is contained in:
Ben Olden-Cooligan 2017-05-24 00:08:36 -04:00
parent 7eabb6448f
commit 108aab4b23
14 changed files with 210 additions and 25 deletions

View File

@ -7,15 +7,18 @@ using System.Threading;
using System.Windows.Forms;
using System.Xml.Serialization;
using NAPS2.Config;
using NAPS2.Dependencies;
using NAPS2.ImportExport;
using NAPS2.ImportExport.Email;
using NAPS2.ImportExport.Images;
using NAPS2.ImportExport.Pdf;
using NAPS2.Lang.ConsoleResources;
using NAPS2.Ocr;
using NAPS2.Operation;
using NAPS2.Scan;
using NAPS2.Scan.Images;
using NAPS2.Util;
using NAPS2.WinForms;
namespace NAPS2.Automation
{
@ -32,6 +35,8 @@ namespace NAPS2.Automation
private readonly ImageSettingsContainer imageSettingsContainer;
private readonly IOperationFactory operationFactory;
private readonly AppConfigManager appConfigManager;
private readonly OcrDependencyManager ocrDependencyManager;
private readonly IFormFactory formFactory;
private readonly AutomatedScanningOptions options;
private ScannedImageList imageList;
@ -40,7 +45,7 @@ namespace NAPS2.Automation
private DateTime startTime;
private string actualOutputPath;
public AutomatedScanning(AutomatedScanningOptions options, IProfileManager profileManager, IScanPerformer scanPerformer, IErrorOutput errorOutput, IEmailer emailer, IScannedImageImporter scannedImageImporter, IUserConfigManager userConfigManager, PdfSettingsContainer pdfSettingsContainer, FileNamePlaceholders fileNamePlaceholders, ImageSettingsContainer imageSettingsContainer, IOperationFactory operationFactory, AppConfigManager appConfigManager)
public AutomatedScanning(AutomatedScanningOptions options, IProfileManager profileManager, IScanPerformer scanPerformer, IErrorOutput errorOutput, IEmailer emailer, IScannedImageImporter scannedImageImporter, IUserConfigManager userConfigManager, PdfSettingsContainer pdfSettingsContainer, FileNamePlaceholders fileNamePlaceholders, ImageSettingsContainer imageSettingsContainer, IOperationFactory operationFactory, AppConfigManager appConfigManager, OcrDependencyManager ocrDependencyManager, IFormFactory formFactory)
{
this.options = options;
this.profileManager = profileManager;
@ -54,6 +59,8 @@ namespace NAPS2.Automation
this.imageSettingsContainer = imageSettingsContainer;
this.operationFactory = operationFactory;
this.appConfigManager = appConfigManager;
this.ocrDependencyManager = ocrDependencyManager;
this.formFactory = formFactory;
}
private void OutputVerbose(string value, params object[] args)
@ -76,6 +83,15 @@ namespace NAPS2.Automation
startTime = DateTime.Now;
ConsoleOverwritePrompt.ForceOverwrite = options.ForceOverwrite;
if (options.Install != null)
{
InstallComponents();
if (options.OutputPath == null && options.EmailFileName == null && !options.AutoSave)
{
return;
}
}
if (!PreCheckOverwriteFile())
{
return;
@ -132,6 +148,53 @@ namespace NAPS2.Automation
}
}
private void InstallComponents()
{
var availableComponents = new List<(DownloadInfo download, ExternalComponent component)>();
if (ocrDependencyManager.Components.Tesseract304.IsSupported)
{
availableComponents.Add((ocrDependencyManager.Downloads.Tesseract304, ocrDependencyManager.Components.Tesseract304));
} else if (ocrDependencyManager.Components.Tesseract304Xp.IsSupported)
{
availableComponents.Add((ocrDependencyManager.Downloads.Tesseract304Xp, ocrDependencyManager.Components.Tesseract304Xp));
}
foreach (var lang in ocrDependencyManager.Languages.Keys)
{
availableComponents.Add((ocrDependencyManager.Downloads.Tesseract304Languages[lang], ocrDependencyManager.Components.Tesseract304Languages[lang]));
}
availableComponents.Add((GhostscriptPdfRenderer.Dependencies.GhostscriptDownload, GhostscriptPdfRenderer.Dependencies.GhostscriptComponent));
var componentDict = availableComponents.ToDictionary(x => x.component.Id.ToLowerInvariant());
var installId = options.Install.ToLowerInvariant();
if (!componentDict.TryGetValue(installId, out var toInstall))
{
Console.WriteLine(ConsoleResources.ComponentNotAvailable);
return;
}
if (toInstall.component.IsInstalled)
{
Console.WriteLine(ConsoleResources.ComponentAlreadyInstalled);
return;
}
// Using a form here is not ideal (since this is supposed to be a console app), but good enough for now
// Especially considering wia/twain often show forms anyway
var progressForm = formFactory.Create<FDownloadProgress>();
if (toInstall.component.Id.StartsWith("ocr-") && componentDict.TryGetValue("ocr", out var ocrExe) && !ocrExe.component.IsInstalled)
{
progressForm.QueueFile(ocrExe.download, ocrExe.component.Install);
if (options.Verbose)
{
Console.WriteLine(ConsoleResources.Installing, ocrExe.component.Id);
}
}
progressForm.QueueFile(toInstall.download, toInstall.component.Install);
if (options.Verbose)
{
Console.WriteLine(ConsoleResources.Installing, toInstall.component.Id);
}
progressForm.ShowDialog();
}
private void ReorderScannedImages()
{
var e = new List<int>();
@ -300,7 +363,7 @@ namespace NAPS2.Automation
public bool ValidateOptions()
{
// Most validation is done by the CommandLineParser library, but some constraints that can't be represented by that API need to be checked here
if (options.OutputPath == null && options.EmailFileName == null && !options.AutoSave)
if (options.OutputPath == null && options.EmailFileName == null && options.Install == null && !options.AutoSave)
{
errorOutput.DisplayError(ConsoleResources.OutputOrEmailRequired);
return false;

View File

@ -18,6 +18,9 @@ namespace NAPS2.Automation
" Only works if the profile has Auto Save enabled.")]
public bool AutoSave { get; set; }
[Option("install", HelpText = "Use this option to download and install optional components (e.g. \"ocr-eng\", \"generic-import\").")]
public string Install { get; set; }
[Option('p', "profile", HelpText = "The name of the profile to use for scanning." +
" If not specified, the most-recently-used profile from the GUI is selected.")]
public string ProfileName { get; set; }

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using NAPS2.Dependencies;
using NAPS2.Lang.ConsoleResources;
using NAPS2.Lang.Resources;
namespace NAPS2.Automation
{
public class ConsoleComponentInstallPrompt : IComponentInstallPrompt
{
public bool PromptToInstall(DownloadInfo download, ExternalComponent component, string promptText)
{
Console.WriteLine(ConsoleResources.ComponentNeeded, component.Id);
return false;
}
}
}

View File

@ -21,12 +21,15 @@ namespace NAPS2.Dependencies
private readonly PlatformSupport platformSupport;
public ExternalComponent(string path, PlatformSupport platformSupport = null)
public ExternalComponent(string id, string path, PlatformSupport platformSupport = null)
{
Id = id;
this.platformSupport = platformSupport;
Path = System.IO.Path.Combine(BasePath, path);
}
public string Id { get; }
public string Path { get; }
public bool IsInstalled

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NAPS2.Dependencies
{
public interface IComponentInstallPrompt
{
bool PromptToInstall(DownloadInfo download, ExternalComponent component, string promptText);
}
}

View File

@ -17,15 +17,15 @@ namespace NAPS2.ImportExport.Pdf
{
public class GhostscriptPdfRenderer : IPdfRenderer
{
private readonly IFormFactory formFactory;
private readonly IComponentInstallPrompt componentInstallPrompt;
private readonly AppConfigManager appConfigManager;
private readonly IErrorOutput errorOutput;
private readonly Lazy<byte[]> gsLibBytes;
public GhostscriptPdfRenderer(IFormFactory formFactory, AppConfigManager appConfigManager, IErrorOutput errorOutput)
public GhostscriptPdfRenderer(IComponentInstallPrompt componentInstallPrompt, AppConfigManager appConfigManager, IErrorOutput errorOutput)
{
this.formFactory = formFactory;
this.componentInstallPrompt = componentInstallPrompt;
this.appConfigManager = appConfigManager;
this.errorOutput = errorOutput;
@ -68,18 +68,10 @@ namespace NAPS2.ImportExport.Pdf
{
return false;
}
// TODO: Change behaviour for console
if (MessageBox.Show(MiscResources.PdfImportComponentNeeded, MiscResources.DownloadNeeded, MessageBoxButtons.YesNo) == DialogResult.Yes)
{
var progressForm = formFactory.Create<FDownloadProgress>();
progressForm.QueueFile(Dependencies.GhostscriptDownload,
path => Dependencies.GhostscriptComponent.Install(path));
progressForm.ShowDialog();
}
return Dependencies.GhostscriptComponent.IsInstalled;
return componentInstallPrompt.PromptToInstall(Dependencies.GhostscriptDownload, Dependencies.GhostscriptComponent, MiscResources.PdfImportComponentNeeded);
}
private static class Dependencies
public static class Dependencies
{
private const string DOWNLOAD_URL_FORMAT = @"https://sourceforge.net/projects/naps2/files/components/gs-9.21/{0}/download";
@ -89,9 +81,9 @@ namespace NAPS2.ImportExport.Pdf
public static DownloadInfo GhostscriptDownload => Environment.Is64BitProcess ? GhostscriptDownload64 : GhostscriptDownload32;
private static readonly ExternalComponent GhostscriptComponent32 = new ExternalComponent(@"gs-9.21\gsdll32.dll", PlatformSupport.Windows);
private static readonly ExternalComponent GhostscriptComponent32 = new ExternalComponent("generic-import", @"gs-9.21\gsdll32.dll", PlatformSupport.Windows);
private static readonly ExternalComponent GhostscriptComponent64 = new ExternalComponent(@"gs-9.21\gsdll64.dll", PlatformSupport.Windows);
private static readonly ExternalComponent GhostscriptComponent64 = new ExternalComponent("generic-import", @"gs-9.21\gsdll64.dll", PlatformSupport.Windows);
public static ExternalComponent GhostscriptComponent => Environment.Is64BitProcess ? GhostscriptComponent64 : GhostscriptComponent32;
}

View File

@ -105,6 +105,33 @@ namespace NAPS2.Lang.ConsoleResources {
}
}
/// <summary>
/// Looks up a localized string similar to The specified component is already installed..
/// </summary>
internal static string ComponentAlreadyInstalled {
get {
return ResourceManager.GetString("ComponentAlreadyInstalled", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to An additional component is needed to perform this operation. Run &quot;NAPS2.Console --install {0}&quot; to install it..
/// </summary>
internal static string ComponentNeeded {
get {
return ResourceManager.GetString("ComponentNeeded", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The specified component to install is not available. Check your spelling..
/// </summary>
internal static string ComponentNotAvailable {
get {
return ResourceManager.GetString("ComponentNotAvailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The encryption configuration file could not be loaded..
/// </summary>
@ -277,6 +304,15 @@ namespace NAPS2.Lang.ConsoleResources {
}
}
/// <summary>
/// Looks up a localized string similar to Installing {0}....
/// </summary>
internal static string Installing {
get {
return ResourceManager.GetString("Installing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to NAPS2.
/// </summary>
@ -341,7 +377,8 @@ namespace NAPS2.Lang.ConsoleResources {
}
/// <summary>
/// Looks up a localized string similar to The specified profile is unavailable or ambiguous.\r\nUse the --profile option to specify a profile by name..
/// Looks up a localized string similar to The specified profile is unavailable or ambiguous.
///Use the --profile option to specify a profile by name..
/// </summary>
internal static string ProfileUnavailableOrAmbiguous {
get {

View File

@ -163,7 +163,8 @@
<value>No scanned pages to export.</value>
</data>
<data name="ProfileUnavailableOrAmbiguous" xml:space="preserve">
<value>The specified profile is unavailable or ambiguous.\r\nUse the --profile option to specify a profile by name.</value>
<value>The specified profile is unavailable or ambiguous.
Use the --profile option to specify a profile by name.</value>
</data>
<data name="Attaching" xml:space="preserve">
<value>Attaching {0}...</value>
@ -235,4 +236,16 @@ Use the "--importpassword" option.</value>
<data name="OutputOrEmailRequiredForImport" xml:space="preserve">
<value>At least one of the -o/--output or -e/--email options must be specified if -i/--import is specified. Import does not work with -a/--autosave.</value>
</data>
<data name="ComponentAlreadyInstalled" xml:space="preserve">
<value>The specified component is already installed.</value>
</data>
<data name="ComponentNeeded" xml:space="preserve">
<value>An additional component is needed to perform this operation. Run "NAPS2.Console --install {0}" to install it.</value>
</data>
<data name="ComponentNotAvailable" xml:space="preserve">
<value>The specified component to install is not available. Check your spelling.</value>
</data>
<data name="Installing" xml:space="preserve">
<value>Installing {0}...</value>
</data>
</root>

View File

@ -81,6 +81,9 @@
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.ValueTuple, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.3.1\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
@ -101,6 +104,7 @@
<Compile Include="Automation\ConsoleOperationProgress.cs" />
<Compile Include="Automation\ConsoleOverwritePrompt.cs" />
<Compile Include="Automation\ConsolePdfPasswordProvider.cs" />
<Compile Include="Automation\ConsoleComponentInstallPrompt.cs" />
<Compile Include="Config\KeyboardShortcuts.cs" />
<Compile Include="Config\SaveButtonDefaultAction.cs" />
<Compile Include="Host\BackgroundForm.cs">
@ -118,6 +122,7 @@
<Compile Include="ImportExport\IAutoSave.cs" />
<Compile Include="ImportExport\ImportOperation.cs" />
<Compile Include="ImportExport\Pdf\GhostscriptPdfRenderer.cs" />
<Compile Include="Dependencies\IComponentInstallPrompt.cs" />
<Compile Include="ImportExport\Pdf\IPdfRenderer.cs" />
<Compile Include="Scan\Images\DeskewOperation.cs" />
<Compile Include="ImportExport\Pdf\SavePdfOperation.cs" />
@ -525,6 +530,7 @@
<Compile Include="WinForms\ToolStripDoubleButton.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="WinForms\WinFormsComponentInstallPrompt.cs" />
<Compile Include="WinForms\WinFormsExportHelper.cs" />
<Compile Include="WinForms\WinFormsOperationProgress.cs" />
<Compile Include="WinForms\WinFormsOverwritePrompt.cs">

View File

@ -178,16 +178,16 @@ namespace NAPS2.Ocr
public class OcrComponents
{
public readonly ExternalComponent Tesseract304Xp = new ExternalComponent(@"tesseract-3.0.4\tesseract_xp.exe", PlatformSupport.Windows);
public readonly ExternalComponent Tesseract304Xp = new ExternalComponent("ocr", @"tesseract-3.0.4\tesseract_xp.exe", PlatformSupport.Windows);
public readonly ExternalComponent Tesseract304 = new ExternalComponent(@"tesseract-3.0.4\tesseract.exe", PlatformSupport.Windows.Except(PlatformSupport.WindowsXp));
public readonly ExternalComponent Tesseract304 = new ExternalComponent("ocr", @"tesseract-3.0.4\tesseract.exe", PlatformSupport.Windows.Except(PlatformSupport.WindowsXp));
public readonly ExternalComponent Tesseract302 = new ExternalComponent(@"tesseract-3.0.2\tesseract.exe", PlatformSupport.Windows);
public readonly ExternalComponent Tesseract302 = new ExternalComponent("ocr", @"tesseract-3.0.2\tesseract.exe", PlatformSupport.Windows);
public readonly IDictionary<string, ExternalComponent> Tesseract304Languages = LanguageData.ToDictionary(x => x.Code, x => new ExternalComponent(Path.Combine(@"tesseract-3.0.4\tessdata", x.Filename.Replace(".gz", ""))));
public readonly IDictionary<string, ExternalComponent> Tesseract304Languages = LanguageData.ToDictionary(x => x.Code, x => new ExternalComponent($"ocr-{x.Code}", Path.Combine(@"tesseract-3.0.4\tessdata", x.Filename.Replace(".gz", ""))));
// The set of 302 languages is actually smaller, but that has no practical effect so we don't have to store the difference anywhere
public readonly IDictionary<string, ExternalComponent> Tesseract302Languages = LanguageData.ToDictionary(x => x.Code, x => new ExternalComponent(Path.Combine(@"tesseract-3.0.2\tessdata", x.Filename.Replace(".gz", ""))));
public readonly IDictionary<string, ExternalComponent> Tesseract302Languages = LanguageData.ToDictionary(x => x.Code, x => new ExternalComponent($"ocr-{x.Code}", Path.Combine(@"tesseract-3.0.2\tessdata", x.Filename.Replace(".gz", ""))));
}
public class OcrDownloads

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using NAPS2.Dependencies;
using NAPS2.Lang.Resources;
namespace NAPS2.WinForms
{
public class WinFormsComponentInstallPrompt : IComponentInstallPrompt
{
private readonly IFormFactory formFactory;
public WinFormsComponentInstallPrompt(IFormFactory formFactory)
{
this.formFactory = formFactory;
}
public bool PromptToInstall(DownloadInfo download, ExternalComponent component, string promptText)
{
if (MessageBox.Show(promptText, MiscResources.DownloadNeeded, MessageBoxButtons.YesNo) == DialogResult.Yes)
{
var progressForm = formFactory.Create<FDownloadProgress>();
progressForm.QueueFile(download, component.Install);
progressForm.ShowDialog();
}
return component.IsInstalled;
}
}
}

View File

@ -2,5 +2,6 @@
<packages>
<package id="CommandLineParser" version="1.9.71" targetFramework="net40-Client" />
<package id="Ghostscript.NET" version="1.2.1" targetFramework="net40-client" />
<package id="System.ValueTuple" version="4.3.1" targetFramework="net40-client" />
<package id="ZXing.Net" version="0.14.0.1" targetFramework="net40-Client" />
</packages>

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NAPS2.Automation;
using NAPS2.Dependencies;
using NAPS2.ImportExport.Pdf;
using NAPS2.Operation;
using NAPS2.Scan.Images;
@ -19,6 +20,7 @@ namespace NAPS2.DI.Modules
Bind<IErrorOutput>().To<ConsoleErrorOutput>();
Bind<IOverwritePrompt>().To<ConsoleOverwritePrompt>();
Bind<IOperationProgress>().To<ConsoleOperationProgress>();
Bind<IComponentInstallPrompt>().To<ConsoleComponentInstallPrompt>();
Bind<ThumbnailRenderer>().To<NullThumbnailRenderer>();
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NAPS2.Dependencies;
using NAPS2.ImportExport.Pdf;
using NAPS2.Operation;
using NAPS2.Scan.Wia;
@ -18,6 +19,7 @@ namespace NAPS2.DI.Modules
Bind<IErrorOutput>().To<MessageBoxErrorOutput>();
Bind<IOverwritePrompt>().To<WinFormsOverwritePrompt>();
Bind<IOperationProgress>().To<WinFormsOperationProgress>();
Bind<IComponentInstallPrompt>().To<WinFormsComponentInstallPrompt>();
}
}
}