Implemented VirtualPrinterDriver project

This commit is contained in:
Marco Batzinger 2020-10-19 17:44:50 +02:00
parent f29c84821b
commit 5c87967c3f
125 changed files with 8191 additions and 0 deletions

View file

@ -0,0 +1,218 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using VirtualPrinter.Agent.Core;
using VirtualPrinter.Agent.Lib.Model;
using VirtualPrinter.Logging;
namespace VirtualPrinter.Agent.Lib.Misc
{
public class GhostScriptConverter : IPostScriptConverter
{
private const string GsWin64 = "gswin64c.exe";
private const string GsWin32 = "gswin32c.exe";
[NotNull]
private readonly IVirtualPrinterLogger<GhostScriptConverter> _logger;
[NotNull]
private readonly IRegistryRepository _registryRepository;
[NotNull]
private readonly IShell _shell;
public event EventHandler<IJob> ProgressInitialized;
public event EventHandler<IJob> ProgressFinished;
public event EventHandler<ProgressUpdateArgs> ProgressUpdate;
public GhostScriptConverter
(
[NotNull]IVirtualPrinterLogger<GhostScriptConverter> logger,
[NotNull]IRegistryRepository registryRepository,
[NotNull]IShell shell
)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (registryRepository == null)
{
throw new ArgumentNullException(nameof(registryRepository));
}
if (shell == null)
{
throw new ArgumentNullException(nameof(shell));
}
_logger = logger;
_registryRepository = registryRepository;
_shell = shell;
}
public void Convert(IJob job, string target, PostScriptRenderOptions options)
{
var ghostScriptExe = GetGhostScriptPath();
if (ghostScriptExe == null)
{
throw new PostScriptConversionException("GhostScript not found. Please place local variable.");
}
ProgressInitialized?.Invoke(this, job);
var pdfTarget = target + ".pdf";
if (options.PdfOptions.Enabled)
{
Convert(job, ghostScriptExe, GetArgumentsForPdfConversion(job.RawDataPath, pdfTarget, options));
if (!_shell.FileExists(pdfTarget))
{
throw new PostScriptConversionException("Postscript conversion failed after output.");
}
}
if (options.TiffOptions.Enabled)
{
var tiffTarget = target + ".tif";
Convert(job, ghostScriptExe, GetArgumentsForTiffConversion(job.RawDataPath, tiffTarget, options));
Convert(job, ghostScriptExe, GetArgumentsForPdfConversion(job.RawDataPath, pdfTarget, options));
if (!_shell.FileExists(tiffTarget))
{
throw new PostScriptConversionException("Postscript conversion failed after output.");
}
}
ProgressFinished?.Invoke(this, job);
}
[CanBeNull]
private string GetGhostScriptPath()
{
return GetGhostScriptPath(GsWin64) ?? GetGhostScriptPath(GsWin32);
}
[CanBeNull]
private string GetGhostScriptPath(string execName)
{
if (!_registryRepository.TryGetGhostscriptPath(out var path))
{
return null;
}
var ghostScriptBinPath = Path.Combine(path, "bin");
var fullPath = Path.Combine(ghostScriptBinPath, execName);
return _shell.FileExists(fullPath) ? fullPath : null;
}
[NotNull]
private static string GetArgumentsForPdfConversion(string source, string target, PostScriptRenderOptions options)
{
const string initialArguments = "-q -P- -dSAFER -dNOPAUSE -dBATCH -dNoCancel -sDEVICE=pdfwrite";
var finalArguments = $"-sOutputFile=\"{target}\" \"{source}\"";
var optionalArguments = "";
if (options.PdfOptions.Archivable)
{
optionalArguments += "-sColorConversionStrategy=/RGB -dUseCIEColor -dPDFACompatibilityPolicy=2";
}
if (options.UserRenderDpi != null && options.UserRenderDpi > 0)
{
optionalArguments += "-r" + options.UserRenderDpi.Value;
}
return $"{initialArguments} {optionalArguments} {finalArguments}";
}
[NotNull]
private static string GetArgumentsForTiffConversion(string source, string target, PostScriptRenderOptions options)
{
const string initialArguments = "-q -P- -dSAFER -dNOPAUSE -dBATCH -sDEVICE=tiff12nc -sCompression=lzw";
var finalArguments = $"-sOutputFile=\"{target}\" \"{source}\"";
var optionalArguments = "-dTextAlphaBits=4 ";
if (options.UserRenderDpi != null && options.UserRenderDpi > 0)
{
optionalArguments += "-r" + options.UserRenderDpi.Value;
}
else
{
optionalArguments += "-r300";
}
return $"{initialArguments} {optionalArguments} {finalArguments}";
}
private void Convert(IJob job, string gsExe, string gsArguments)
{
LogDebug("Starting ps conversion ...\n"+gsExe + " " + gsArguments);
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = gsExe,
Arguments = gsArguments,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
while (!process.StandardOutput.EndOfStream)
{
var readLine = process.StandardOutput.ReadLine();
var progress = ParseProgress(readLine);
if (progress == null)
{
continue;
}
ProgressUpdate?.Invoke(this, new ProgressUpdateArgs(job, progress.Value));
}
}
private static uint? ParseProgress([CanBeNull]string line)
{
// Example output:
// %%[Page: 49]%%
if (line == null)
{
return null;
}
var groups = Regex.Match(line, @"%%\[Page:(.*)\]%%").Groups;
if (groups.Count < 2)
{
return null;
}
if (!uint.TryParse(groups[1].Value, out var result))
{
return null;
}
return result;
}
private void LogDebug(string message, params object[] args)
{
_logger.Debug(message, args);
}
}
}

View file

@ -0,0 +1,15 @@
using VirtualPrinter.Agent.Core;
namespace VirtualPrinter.Agent.Lib.Misc
{
public class Job : IJob
{
public string RawDataPath { get; set; }
public string IniDataPath { get; set; }
public IJobInfo JobInfo { get; set; }
public ISessionInfo SessionInfo { get; set; }
}
}

View file

@ -0,0 +1,243 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Printing;
using System.Security.Principal;
using Cassia;
using JetBrains.Annotations;
using VirtualPrinter.Agent.Core;
using VirtualPrinter.Agent.Core.Enums;
using VirtualPrinter.Logging;
namespace VirtualPrinter.Agent.Lib.Misc
{
public class JobFactory : IJobFactory
{
[NotNull]
private readonly IVirtualPrinterLogger<JobFactory> _logger;
private readonly IDirectoryHelper _directoryHelper;
[NotNull]
private readonly IRegistryRepository _registryRepository;
public JobFactory
(
[NotNull]IRegistryRepository registryRepository,
[NotNull]IVirtualPrinterLogger<JobFactory> logger,
[NotNull]IDirectoryHelper directoryHelper
)
{
if (registryRepository == null)
{
throw new ArgumentNullException(nameof(registryRepository));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (directoryHelper == null)
{
throw new ArgumentNullException(nameof(logger));
}
_registryRepository = registryRepository;
_logger = logger;
_directoryHelper = directoryHelper;
}
public IJob Create(string printerName, Stream stream)
{
if (printerName == null)
{
throw new ArgumentNullException(nameof(printerName));
}
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
try
{
var now = DateTime.Now;
var config = _registryRepository.GetRegistryConfig();
var root = _directoryHelper.GetOutputDirectory(config);
var jobInfo = GetCurrentPrintJobs(printerName).FirstOrDefault();
if (jobInfo == null)
{
throw new InvalidOperationException();
}
var session = GetCurrentSessions(jobInfo).FirstOrDefault();
var iniName = GenerateFileName(now, jobInfo.JobId, 0, config.FileNameMask, "ini");
var iniPath = Path.Combine(root, iniName);
var extension = GetRawFileExtension(config.IntermediateFormat);
var rawName = $"{Path.GetFileNameWithoutExtension(iniName)}.{extension}";
var rawPath = Path.Combine(root, rawName);
using (var output = File.Create(rawPath))
{
stream.CopyTo(output);
}
return new Job
{
RawDataPath = rawPath,
IniDataPath = iniPath,
JobInfo = jobInfo,
SessionInfo = session
};
}
catch (Exception exception)
{
LogError(exception, "Failed to create job.");
return null;
}
}
public IJob Create(string iniPath, string rawPath, IJobInfo jobInfo, ISessionInfo sessionInfo)
{
if (string.IsNullOrWhiteSpace(iniPath))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(iniPath));
}
if (string.IsNullOrWhiteSpace(rawPath))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(rawPath));
}
return new Job
{
IniDataPath = iniPath,
RawDataPath = rawPath,
JobInfo = jobInfo,
SessionInfo = sessionInfo
};
}
[NotNull]
private string GenerateFileName(DateTime time, int job, int page, [NotNull]string pattern, [NotNull]string ending)
{
var fileName = pattern;
fileName = fileName.Replace("{yyyy}", $"{time.Year:0000}");
fileName = fileName.Replace("{MM}", $"{time.Month:00}");
fileName = fileName.Replace("{DD}", $"{time.Day:00}");
fileName = fileName.Replace("{hh}", $"{time.Hour:00}");
fileName = fileName.Replace("{mm}", $"{time.Minute:00}");
fileName = fileName.Replace("{ss}", $"{time.Second:00}");
fileName = fileName.Replace("{fff}", $"{time.Millisecond:000}");
fileName = fileName.Replace("{job05}", $"{job:00000}");
fileName = fileName.Replace("{page03}", $"{page:000}");
return $"{fileName}.{ending}";
}
private IEnumerable<SessionInfo> GetCurrentSessions([NotNull]IJobInfo job)
{
var domain = job.DomainName;
var machine = job.MachineName?.TrimStart('\\');
var user = job.UserName;
LogDebug($"Searching for session of {domain}\\{user} on {machine} !");
if (domain == null || machine == null || user == null)
{
yield break;
}
const StringComparison cmp = StringComparison.OrdinalIgnoreCase;
using (var server = new TerminalServicesManager().GetLocalServer())
{
var sessions = server.GetSessions().Where(s => s.UserName != null && s.DomainName != null);
foreach (var session in sessions)
{
if (!session.UserName.Equals(user, cmp))
{
continue;
}
var isSingleUser = session.DomainName.Equals(machine, cmp);
var isDomainUser = session.DomainName.Equals(domain, cmp);
if (!isSingleUser && !isDomainUser)
{
continue;
}
var sessionId = session.SessionId;
var desktopName = session.WindowStationName;
var account = session.UserAccount;
yield return new SessionInfo
{
Id = sessionId,
Desktop = desktopName,
Sid = account.Translate(typeof(SecurityIdentifier)).Value
};
}
}
}
[ItemNotNull]
private IEnumerable<IJobInfo> GetCurrentPrintJobs(string printerName)
{
using (var server = new LocalPrintServer())
{
using (var queue = server.GetPrintQueue(printerName))
{
using (var jobs = queue.GetPrintJobInfoCollection())
{
foreach (var job in jobs)
{
using (job)
{
var id = job.JobIdentifier;
var machine = server.Name;
var domain = Environment.UserDomainName;
var user = job.Submitter;
var name = job.Name;
yield return new JobInfo
{
JobId = id,
Name = name,
DomainName = domain,
MachineName = machine,
UserName = user,
Status = job.JobStatus,
DeviceName = queue.Name
};
}
}
}
}
}
}
[NotNull]
private string GetRawFileExtension(IntermediateFormat format)
{
switch (format)
{
case IntermediateFormat.Xps:
return "xps";
case IntermediateFormat.Ps:
return "ps";
default:
throw new ArgumentOutOfRangeException();
}
}
private void LogDebug(string message)
{
_logger.Debug(message);
}
private void LogError(Exception exception, string message, params object[] args)
{
_logger.Error(exception, message, args);
}
}
}

View file

@ -0,0 +1,150 @@
using System;
using System.IO;
using JetBrains.Annotations;
using VirtualPrinter.Agent.Core;
using VirtualPrinter.Agent.Lib.Model;
using VirtualPrinter.Logging;
using VirtualPrinter.ProgressInfo.Core;
namespace VirtualPrinter.Agent.Lib.Misc
{
public class JobProcessor : IJobProcessor
{
[NotNull]
private readonly IRegistryRepository _registryRepository;
[NotNull]
private readonly IJobRedirector _jobRedirector;
[NotNull]
private readonly IVirtualPrinterLogger<JobProcessor> _logger;
[NotNull]
private readonly IPostScriptConverter _postScriptConverter;
[NotNull]
private readonly IProgressInfo _progressInfo;
private readonly IDirectoryHelper _directoryHelper;
public JobProcessor
(
[NotNull]IRegistryRepository registryRepository,
[NotNull]IVirtualPrinterLogger<JobProcessor> logger,
[NotNull]IPostScriptConverter postScriptConverter,
[NotNull]IJobRedirector redirector,
[NotNull]IProgressInfo progressInfo,
[NotNull]IDirectoryHelper directoryHelper
)
{
if (registryRepository == null)
{
throw new ArgumentNullException(nameof(registryRepository));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (postScriptConverter == null)
{
throw new ArgumentNullException(nameof(postScriptConverter));
}
if (redirector == null)
{
throw new ArgumentNullException(nameof(redirector));
}
if (progressInfo == null)
{
throw new ArgumentNullException(nameof(progressInfo));
}
if (directoryHelper == null)
{
throw new ArgumentNullException(nameof(directoryHelper));
}
_registryRepository = registryRepository;
_logger = logger;
_postScriptConverter = postScriptConverter;
_jobRedirector = redirector;
_progressInfo = progressInfo;
_directoryHelper = directoryHelper;
_postScriptConverter.ProgressInitialized += OnProgressInitialized;
_postScriptConverter.ProgressFinished += OnProgressFinished;
_postScriptConverter.ProgressUpdate += OnProgressUpdate;
}
public void Process(IJob job, IUserConfig userConfig)
{
if (job == null)
{
throw new ArgumentNullException(nameof(job));
}
if (userConfig == null)
{
throw new ArgumentNullException(nameof(userConfig));
}
var targetFile = $"{Path.GetFileNameWithoutExtension(job.RawDataPath)}";
var config = _registryRepository.GetRegistryConfig();
var dir = _directoryHelper.GetOutputDirectory(config);
targetFile = Path.Combine(dir, targetFile);
var options = new PostScriptRenderOptions
{
UserRenderDpi = userConfig.UserRenderDpi,
PdfOptions = new PostScriptRenderPdfOptions
{
Enabled = userConfig.Format == "PDF" || string.IsNullOrEmpty(userConfig.Format)
},
TiffOptions = new PostScriptRenderTiffOptions
{
Enabled = userConfig.Format == "TIFF"
}
};
try
{
_postScriptConverter.Convert(job, targetFile, options);
}
catch (PostScriptConversionException exception)
{
LogError(exception, "Error processing PS file.");
return;
}
_jobRedirector.Redirect(job, userConfig);
}
private void OnProgressUpdate(object sender, [NotNull]ProgressUpdateArgs args)
{
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
_progressInfo.Progress(args.Job, args.Value);
}
private void OnProgressFinished(object sender, IJob job)
{
_progressInfo.Finish(job);
}
private void OnProgressInitialized(object sender, IJob job)
{
_progressInfo.Initialize(job);
}
private void LogError(Exception exception, string message, params object[] args)
{
_logger.Error(exception, message, args);
}
}
}

View file

@ -0,0 +1,106 @@
using System;
using System.IO;
using JetBrains.Annotations;
using VirtualPrinter.Agent.Core;
using VirtualPrinter.Delivery;
using VirtualPrinter.Logging;
using VirtualPrinter.Utils;
namespace VirtualPrinter.Agent.Lib.Misc
{
public class JobRedirector : IJobRedirector
{
[NotNull]
private readonly IVirtualPrinterLogger<JobRedirector> _logger;
[NotNull]
private readonly IShell _shell;
public JobRedirector([NotNull]IVirtualPrinterLogger<JobRedirector> logger, [NotNull]IShell shell)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (shell == null)
{
throw new ArgumentNullException(nameof(shell));
}
_logger = logger;
_shell = shell;
}
public void Redirect(IJob job, IUserConfig userConfig)
{
if (job == null)
{
throw new ArgumentNullException(nameof(job));
}
if (userConfig == null)
{
throw new ArgumentNullException(nameof(userConfig));
}
var printer = userConfig.RedirectPrinter;
if (string.IsNullOrWhiteSpace(printer))
{
LogWarn("Can not redirect to empty printer.");
return;
}
try
{
var pdfToRedirect = GetPdfPath(job.RawDataPath);
LogDebug($"Redirecting '{pdfToRedirect}' to '{printer}'...");
var redirectExe = Path.GetFullPath(typeof(Redirector).Assembly.Location);
var redirectArgs = $@"redirect ""{pdfToRedirect}"" ""{printer}""";
_shell.Execute(job.JobInfo, job.SessionInfo, redirectExe, redirectArgs);
}
catch (Exception exception)
{
LogError(exception, $"{exception.GetType()}: {exception.Message}");
}
}
[NotNull]
private static string GetPdfPath([NotNull]string rawFilePath)
{
if (string.IsNullOrWhiteSpace(rawFilePath))
{
throw new ArgumentException(nameof(rawFilePath));
}
var directoryName = Path.GetDirectoryName(rawFilePath);
if (string.IsNullOrWhiteSpace(directoryName))
{
throw new ArgumentException(nameof(directoryName));
}
var file = Path.GetFileNameWithoutExtension(rawFilePath);
return Path.Combine(directoryName, file) + ".pdf";
}
private void LogDebug(string message, params object[] args)
{
_logger.Debug(message, args);
}
private void LogWarn(string message, params object[] args)
{
_logger.Warn(message, args);
}
private void LogError(Exception exception, string message, params object[] args)
{
_logger.Error(exception, message, args);
}
}
}

View file

@ -0,0 +1,170 @@
using System;
using System.IO;
using System.Printing;
using JetBrains.Annotations;
using VirtualPrinter.Agent.Core;
namespace VirtualPrinter.Agent.Lib.Misc
{
public class JobService : IJobService
{
[NotNull]
private readonly IRegistryRepository _registryRepository;
[NotNull]
private readonly IJobFactory _jobFactory;
[NotNull]
private readonly IShell _shell;
private IDirectoryHelper _directoryHelper;
public JobService
(
[NotNull]IRegistryRepository registryRepository,
[NotNull]IJobFactory jobFactory,
[NotNull]IShell shell,
[NotNull]IDirectoryHelper directoryHelper
)
{
if (registryRepository == null)
{
throw new ArgumentNullException(nameof(registryRepository));
}
if (jobFactory == null)
{
throw new ArgumentNullException(nameof(jobFactory));
}
if (shell == null)
{
throw new ArgumentNullException(nameof(shell));
}
if (directoryHelper == null)
{
throw new ArgumentNullException(nameof(directoryHelper));
}
_registryRepository = registryRepository;
_jobFactory = jobFactory;
_shell = shell;
_directoryHelper = directoryHelper;
}
public void Start(IJob job)
{
if (job == null)
{
throw new ArgumentNullException(nameof(job));
}
const PrintStatus status = PrintStatus.Paused;
var iniFile = Path.GetFullPath(job.IniDataPath);
var config = _registryRepository.GetRegistryConfig();
var pre = config.ResolvedPreconverter;
WriteJobStartIni(job, status);
_shell.Execute(job.JobInfo, job.SessionInfo, pre.Item1, $"{pre.Item2} \"{iniFile}\"");
}
public IJob CreateJob(string iniFile, string rawFile)
{
var jobInfo = GetJobInfo(iniFile);
var sessionInfo = GetSessionInfo(iniFile);
return _jobFactory.Create(iniFile, rawFile, jobInfo, sessionInfo);
}
public PrintStatus ReadStatus(string iniPath)
{
var txt = _shell.ReadIniEntry<string>("Preconverting", "Status", iniPath);
Enum.TryParse(txt, true, out PrintStatus result);
return result;
}
public JobStatus ReadJobStatus(string iniPath)
{
var status = _shell.ReadIniEntry<string>("Job", "Status", iniPath);
Enum.TryParse(status, true, out JobStatus result);
return result;
}
public void Finish(IJob job)
{
var config = _registryRepository.GetRegistryConfig();
WriteJobFinishIni(job.IniDataPath, config);
var iniFile = Path.GetFullPath(job.IniDataPath);
var post = config.ResolvedPostconverter;
_shell.Execute(job.JobInfo, job.SessionInfo, post.Item1, $"{post.Item2} \"{iniFile}\"");
}
private void WriteJobStartIni([NotNull]IJob job, PrintStatus status)
{
_shell.WriteIniEntry("Job", "Status", JobStatus.InProgress.ToString().ToLowerInvariant(), job.IniDataPath);
_shell.WriteIniEntry("Device", "DeviceName", job.JobInfo.DeviceName, job.IniDataPath);
_shell.WriteIniEntry("Document", "Name", job.JobInfo.Name.Normalize(), job.IniDataPath);
_shell.WriteIniEntry("Document", "JobID", $"{job.JobInfo.JobId}", job.IniDataPath);
_shell.WriteIniEntry("Document", "DomainName", job.JobInfo.DomainName, job.IniDataPath);
_shell.WriteIniEntry("Document", "MachineName", job.JobInfo.MachineName, job.IniDataPath);
_shell.WriteIniEntry("Document", "UserName", job.JobInfo.UserName, job.IniDataPath);
_shell.WriteIniEntry("Document", "SessionID", $"{job.SessionInfo.Id}", job.IniDataPath);
_shell.WriteIniEntry("Document", "Desktop", $"{job.SessionInfo.Desktop}", job.IniDataPath);
_shell.WriteIniEntry("Document", "SID", $"{job.SessionInfo.Sid}", job.IniDataPath);
_shell.WriteIniEntry("Document", "Status", job.JobInfo.Status.ToString(), job.IniDataPath);
_shell.WriteIniEntry("Preconverting", "Status", status.ToIni(), job.IniDataPath);
}
private SessionInfo GetSessionInfo(string iniFile)
{
var sessionInfo = new SessionInfo
{
Id = _shell.ReadIniEntry<int>("Document",
"SessionID",
iniFile),
Sid = _shell.ReadIniEntry<string>("Document", "SID", iniFile),
Desktop = _shell.ReadIniEntry<string>("Document", "Desktop", iniFile)
};
return sessionInfo;
}
private JobInfo GetJobInfo(string iniFile)
{
var jobInfo = new JobInfo
{
DomainName = _shell.ReadIniEntry<string>("Document",
"DomainName",
iniFile),
MachineName = _shell.ReadIniEntry<string>("Document",
"MachineName",
iniFile),
UserName = _shell.ReadIniEntry<string>("Document",
"UserName",
iniFile)
};
return jobInfo;
}
private void WriteJobFinishIni(string iniPath, [NotNull]IExConfig config)
{
const PrintStatus status = PrintStatus.Complete;
const PrintJobStatus spoolerState = PrintJobStatus.Printed;
_shell.WriteIniEntry("Preconverting", "Status", status.ToIni(), iniPath);
var pdfFile = Path.GetFileNameWithoutExtension(iniPath) + ".pdf";
var tiffFile = Path.GetFileNameWithoutExtension(iniPath) + ".tif";
var dir = _directoryHelper.GetOutputDirectory(config);
pdfFile = Path.Combine(dir, pdfFile);
tiffFile = Path.Combine(dir, tiffFile);
_shell.WriteIniEntry("PDF", "File0", pdfFile, iniPath);
_shell.WriteIniEntry("TIFF", "File0", tiffFile, iniPath);
_shell.WriteIniEntry("Document", "Status", spoolerState.ToString(), iniPath);
}
}
}

View file

@ -0,0 +1,276 @@
using JetBrains.Annotations;
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using VirtualPrinter.Agent.Core;
using VirtualPrinter.Logging;
using VirtualPrinter.SetupDriver;
namespace VirtualPrinter.Agent.Lib.Misc
{
public class VirtualTcpInputPrinter : IVirtualPrinter
{
[NotNull]
private readonly IRegistryRepository _registryRepository;
[NotNull]
private readonly IJobFactory _jobFactory;
[NotNull]
private readonly IJobService _jobService;
[NotNull]
private readonly IVirtualPrinterLogger<VirtualTcpInputPrinter> _logger;
[NotNull]
private readonly IJobProcessor _jobProcessor;
private IDirectoryHelper _directoryHelper;
private TcpListener _socket;
private FileSystemWatcher _watcher;
public VirtualTcpInputPrinter
(
[NotNull]IRegistryRepository registryRepository,
[NotNull]IVirtualPrinterLogger<VirtualTcpInputPrinter> logger,
[NotNull]IJobFactory jobFactory,
[NotNull]IJobService jobService,
[NotNull]IJobProcessor jobProcessor,
[NotNull]IDirectoryHelper directoryHelper
)
{
_registryRepository = registryRepository;
_logger = logger;
_jobFactory = jobFactory;
_jobService = jobService;
_jobProcessor = jobProcessor;
_directoryHelper = directoryHelper;
}
public void Dispose()
{
_watcher.Dispose();
_socket.Stop();
}
public void Init()
{
var config = GetRegistryConfig();
var dir = _directoryHelper.GetOutputDirectory(config);
_watcher = new FileSystemWatcher(dir, "*.ini")
{
IncludeSubdirectories = false,
NotifyFilter = NotifyFilters.LastWrite,
EnableRaisingEvents = true
};
_watcher.Changed += IniFileChanged;
_socket = new TcpListener(IPAddress.Loopback, config.PrinterPort);
_socket.Start();
_socket.BeginAcceptTcpClient(HandleClient, _socket);
LogDebug($"Waiting on {_socket.LocalEndpoint}...");
}
private void HandleClient([NotNull]IAsyncResult ar)
{
const string printer = Defaults.PrinterName;
IJob job;
var socket = (TcpListener) ar.AsyncState;
using (var client = socket.EndAcceptTcpClient(ar))
{
var local = client.Client.LocalEndPoint;
var remote = client.Client.RemoteEndPoint;
LogDebug($"{remote} --> {local}");
job = _jobFactory.Create(printer, client.GetStream());
if (job == null)
{
LogError("Job could not be created.");
return;
}
}
LogDebug($"Temporarily printed '{job.RawDataPath}'!");
socket.BeginAcceptTcpClient(HandleClient, ar.AsyncState);
_jobService.Start(job);
}
private void IniFileChanged(object sender, [NotNull]FileSystemEventArgs e)
{
var ini = e.FullPath;
if (!ini.ToLowerInvariant().EndsWith(".ini"))
{
return;
}
var rawName = $"{Path.GetFileNameWithoutExtension(ini)}.ps";
var config = GetRegistryConfig();
var dir = _directoryHelper.GetOutputDirectory(config);
var rawFile = Path.Combine(dir, rawName);
var status = _jobService.ReadStatus(ini);
if (status == PrintStatus.Resumed)
{
var job = _jobService.CreateJob(ini, rawFile);
var isJobValid = IsJobValid(job);
if (!isJobValid)
{
return;
}
ProcessFile(rawFile, ini);
}
if (status == PrintStatus.Canceled)
{
DeleteFiles(ini, dir, rawFile);
}
var jobStatus = _jobService.ReadJobStatus(ini);
if (jobStatus == JobStatus.Completed || jobStatus == JobStatus.Failed)
{
DeleteFiles(ini, dir, rawFile);
}
}
private void DeleteFiles([NotNull]string ini, [NotNull]string outputDir, [NotNull]string rawFile)
{
if (string.IsNullOrWhiteSpace(ini))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(ini));
}
if (string.IsNullOrWhiteSpace(outputDir))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(outputDir));
}
if (string.IsNullOrWhiteSpace(rawFile))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(rawFile));
}
var pdfFile = Path.Combine(outputDir, $"{Path.GetFileNameWithoutExtension(ini)}.pdf");
var tiffFile = Path.Combine(outputDir, $"{Path.GetFileNameWithoutExtension(ini)}.tif");
if (File.Exists(pdfFile) && !IsFileLocked(pdfFile))
{
File.Delete(pdfFile);
}
if (File.Exists(tiffFile) && !IsFileLocked(tiffFile))
{
File.Delete(tiffFile);
}
File.Delete(ini);
File.Delete(rawFile);
}
private bool IsFileLocked(string filePath)
{
try
{
using(var stream = File.Open(filePath, FileMode.Open, FileAccess.Read))
{
stream.Close();
}
return false;
}
catch (IOException)
{
return true;
}
}
[NotNull]
private IExConfig GetRegistryConfig()
{
return _registryRepository.GetRegistryConfig();
}
private bool IsJobValid([CanBeNull]IJob job)
{
if (job == null)
{
return false;
}
if (string.IsNullOrWhiteSpace(job.JobInfo.DomainName))
{
return false;
}
if (string.IsNullOrWhiteSpace(job.JobInfo.MachineName))
{
return false;
}
if (string.IsNullOrWhiteSpace(job.JobInfo.UserName))
{
return false;
}
if (string.IsNullOrWhiteSpace(job.SessionInfo.Sid))
{
return false;
}
return true;
}
private void ProcessFile(string filePath, string iniFile)
{
var thread = new Thread(obj =>
{
var tuple = (Tuple<string, string>) obj;
var rawFile = tuple.Item1;
var ini = tuple.Item2;
var job = _jobService.CreateJob(ini,
rawFile);
try
{
var userConfig = _registryRepository.GetUserRegistryConfig(job.SessionInfo.Sid);
_jobProcessor.Process(job, userConfig);
LogDebug($"Converted '{rawFile}'!");
_jobService.Finish(job);
}
catch (Exception exception)
{
LogError(exception,
"Failed to process file. Job: {@job}",
job);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start(Tuple.Create(filePath, iniFile));
}
private void LogError(Exception exception, string message, params object[] args)
{
_logger.Error(exception, message, args);
}
private void LogError(string message, params object[] args)
{
_logger.Error(message, args);
}
private void LogDebug(string message, params object[] args)
{
_logger.Debug(message, args);
}
}
}

View file

@ -0,0 +1,22 @@
using System;
using JetBrains.Annotations;
using VirtualPrinter.Agent.Core.Interfaces;
namespace VirtualPrinter.Agent.Lib.Model
{
public class ProgressUpdateArgs : EventArgs
{
public ProgressUpdateArgs([NotNull] IJob job, uint val)
{
Job = job;
Value = val;
}
[NotNull]
public IJob Job { get; }
public uint Value { get; }
}
}

View file

@ -0,0 +1,32 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("VirtualPrinter.Agent.Lib")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3d3f946d-c8dc-4518-b464-75e73cbad734")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{94E8105F-5001-403B-B9F1-B0B0B236AD65}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>VirtualPrinter.Agent.Lib</RootNamespace>
<AssemblyName>VirtualPrinter.Agent.Lib</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Files\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Compile Include="Misc\GhostScriptConverter.cs" />
<Compile Include="Misc\JobProcessor.cs" />
<Compile Include="Misc\JobRedirector.cs" />
<Compile Include="Misc\Job.cs" />
<Compile Include="Misc\JobFactory.cs" />
<Compile Include="Misc\JobService.cs" />
<Compile Include="Misc\VirtualTcpInputPrinter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VirtualPrinterService.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Printing" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Agent\VirtualPrinter.Delivery\VirtualPrinter.Delivery.csproj">
<Project>{74fa80b3-7cf1-4b68-8aa3-4c3d37bbe855}</Project>
<Name>VirtualPrinter.Delivery</Name>
</ProjectReference>
<ProjectReference Include="..\..\Installer\VirtualPrinter.SetupDriver\VirtualPrinter.SetupDriver.csproj">
<Project>{12402f90-a2ae-4549-9142-f90650e2082a}</Project>
<Name>VirtualPrinter.SetupDriver</Name>
</ProjectReference>
<ProjectReference Include="..\..\UI\VirtualPrinter.ProgressInfo.Core\VirtualPrinter.ProgressInfo.Core.csproj">
<Project>{24d28558-c825-43e6-85d2-7c59f4a97698}</Project>
<Name>VirtualPrinter.ProgressInfo.Core</Name>
</ProjectReference>
<ProjectReference Include="..\VirtualPrinter.Agent.Core\VirtualPrinter.Agent.Core.csproj">
<Project>{135c85eb-2116-4cc4-8ccb-b6804b9d6467}</Project>
<Name>VirtualPrinter.Agent.Core</Name>
</ProjectReference>
<ProjectReference Include="..\VirtualPrinter.Logging\VirtualPrinter.Logging.csproj">
<Project>{AA25364D-22D5-44B0-86A5-6FB14C686308}</Project>
<Name>VirtualPrinter.Logging</Name>
</ProjectReference>
<ProjectReference Include="..\VirtualPrinter.Utils\VirtualPrinter.Utils.csproj">
<Project>{cd1c8e9d-5335-41ac-b0c0-88fd7c7c55f3}</Project>
<Name>VirtualPrinter.Utils</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="6.0.0" />
<PackageReference Include="Cassia" Version="3.0.0-alpha.9" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0-rc.2.20475.5" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0-rc.2.20475.5" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,27 @@
using JetBrains.Annotations;
using VirtualPrinter.Agent.Core;
namespace VirtualPrinter.Agent.Lib
{
public class VirtualPrinterService : IVirtualPrinterService
{
[NotNull]
private readonly IVirtualPrinter _printer;
public VirtualPrinterService([NotNull]IVirtualPrinter printer)
{
_printer = printer;
}
public void Start()
{
_printer.Init();
}
public void Stop()
{
_printer.Dispose();
}
}
}