Pdf Import Rewrite
Pdf Import rewritten to greatly improve QR Code detection, reduce reliance on iTextSharp and improve thumbnails. Fixes #50
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
using Quartz;
|
||||
using Quartz.Impl;
|
||||
using Quartz.Impl.Triggers;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services.Documents.AttachmentImport
|
||||
{
|
||||
public class ImportDirectoryMonitor : IDisposable
|
||||
{
|
||||
private FileSystemWatcher watcher;
|
||||
|
||||
public const string WatcherFilter = "*.pdf";
|
||||
public string MonitorLocation { get; private set; }
|
||||
public IScheduler Scheduler { get; private set; }
|
||||
public int ImportDelay { get; private set; }
|
||||
|
||||
public ImportDirectoryMonitor(string MonitorLocation, IScheduler Scheduler, int ImportDelay)
|
||||
{
|
||||
if (MonitorLocation == null)
|
||||
throw new ArgumentNullException(nameof(MonitorLocation));
|
||||
if (Scheduler == null)
|
||||
throw new ArgumentNullException(nameof(Scheduler));
|
||||
|
||||
this.MonitorLocation = MonitorLocation.EndsWith(@"\") ? MonitorLocation : $@"{MonitorLocation}\";
|
||||
this.Scheduler = Scheduler;
|
||||
this.ImportDelay = Math.Max(0, ImportDelay);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (watcher == null)
|
||||
{
|
||||
if (!Directory.Exists(MonitorLocation))
|
||||
{
|
||||
Directory.CreateDirectory(MonitorLocation);
|
||||
}
|
||||
|
||||
watcher = new FileSystemWatcher(MonitorLocation, WatcherFilter);
|
||||
watcher.Created += OnFileCreated;
|
||||
}
|
||||
|
||||
watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (watcher != null)
|
||||
{
|
||||
watcher.EnableRaisingEvents = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFileCreated(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
if (!e.ChangeType.HasFlag(WatcherChangeTypes.Deleted))
|
||||
{
|
||||
ScheduleImport(e.FullPath, ImportDelay);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleImport(string Filename, int ImportDelay)
|
||||
{
|
||||
var startTime = new DateTimeOffset(DateTime.Now.AddMilliseconds(ImportDelay));
|
||||
var jobTrigger = new SimpleTriggerImpl(Guid.NewGuid().ToString(), startTime);
|
||||
|
||||
var jobDetails = new JobDetailImpl(Guid.NewGuid().ToString(), typeof(ImporterJob));
|
||||
jobDetails.JobDataMap.Add("Filename", Filename);
|
||||
jobDetails.JobDataMap.Add("RetryCount", 0);
|
||||
|
||||
Scheduler.ScheduleJob(jobDetails, jobTrigger);
|
||||
}
|
||||
|
||||
public void ScheduleCurrentFiles(int ImportDelay)
|
||||
{
|
||||
foreach (var filename in Directory.GetFiles(this.MonitorLocation, "*.pdf"))
|
||||
{
|
||||
ScheduleImport(filename, ImportDelay);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (watcher != null)
|
||||
{
|
||||
watcher.EnableRaisingEvents = false;
|
||||
watcher.Dispose();
|
||||
watcher = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
using Disco.Data.Repository;
|
||||
using PdfiumViewer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ZXing;
|
||||
using ZXing.Common;
|
||||
using ZXing.Multi.QrCode;
|
||||
|
||||
namespace Disco.Services.Documents.AttachmentImport
|
||||
{
|
||||
internal class ImportPage : IDisposable
|
||||
{
|
||||
public DiscoDataContext Database { get; private set; }
|
||||
public string SessionId { get; private set; }
|
||||
public PdfDocument PdfiumDocument { get; private set; }
|
||||
public int PageIndex { get; private set; }
|
||||
|
||||
public DocumentUniqueIdentifier Identifier { get; private set; }
|
||||
|
||||
private Result qrCodeResult;
|
||||
private float qrCodeResultScale;
|
||||
private Image renderedImage;
|
||||
private Bitmap renderedThumbnail;
|
||||
private RotateFlipType detectedRotation;
|
||||
|
||||
public ImportPage(DiscoDataContext Database, string SessionId, PdfDocument PdfiumDocument, int PageIndex)
|
||||
{
|
||||
this.Database = Database;
|
||||
this.SessionId = SessionId;
|
||||
this.PdfiumDocument = PdfiumDocument;
|
||||
this.PageIndex = PageIndex;
|
||||
}
|
||||
|
||||
public bool IsDetected
|
||||
{
|
||||
get
|
||||
{
|
||||
return Identifier != null;
|
||||
}
|
||||
}
|
||||
|
||||
public Image Image
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetRenderedImage();
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap Thumbnail
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetRenderedThumbnail();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValidAttachment
|
||||
{
|
||||
get
|
||||
{
|
||||
return Identifier != null &&
|
||||
Identifier.Creator != null &&
|
||||
Identifier.Target != null &&
|
||||
(Identifier.DocumentTemplate != null || Identifier.AttachmentType.HasValue);
|
||||
}
|
||||
}
|
||||
|
||||
public int Rotation
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (detectedRotation)
|
||||
{
|
||||
case RotateFlipType.Rotate90FlipNone:
|
||||
return 90;
|
||||
case RotateFlipType.Rotate180FlipNone:
|
||||
return 180;
|
||||
case RotateFlipType.Rotate270FlipNone:
|
||||
return 270;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteThumbnailSessionCache()
|
||||
{
|
||||
var sessionCacheLocation = DataStore.CreateLocation(Database, "Cache\\DocumentDropBox_SessionPages");
|
||||
var filename = Path.Combine(sessionCacheLocation, $"{SessionId}-{PageIndex + 1}");
|
||||
|
||||
Thumbnail.SavePng(filename);
|
||||
}
|
||||
|
||||
public void WriteUndetectedImages()
|
||||
{
|
||||
var undetectedLocation = DataStore.CreateLocation(Database, "DocumentDropBox_Unassigned");
|
||||
var filename = Path.Combine(undetectedLocation, $"{SessionId}_{PageIndex + 1}_thumbnail.png");
|
||||
Thumbnail.SavePng(filename);
|
||||
|
||||
using (var largePreview = Image.ResizeImage(700, 700))
|
||||
{
|
||||
filename = Path.Combine(undetectedLocation, $"{SessionId}_{PageIndex + 1}.jpg");
|
||||
largePreview.SaveJpg(90, filename);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DetectQRCode()
|
||||
{
|
||||
var qrReader = new QRCodeMultiReader();
|
||||
|
||||
var qrReaderHints = new Dictionary<DecodeHintType, object>() {
|
||||
{ DecodeHintType.TRY_HARDER, true }
|
||||
};
|
||||
|
||||
var qrImageSource = new BitmapLuminanceSource((Bitmap)Image);
|
||||
|
||||
var qrBinarizer = new HybridBinarizer(qrImageSource);
|
||||
var qrBinaryBitmap = new BinaryBitmap(qrBinarizer);
|
||||
|
||||
try
|
||||
{
|
||||
qrCodeResult = qrReader.decodeMultiple(qrBinaryBitmap, qrReaderHints)?.FirstOrDefault();
|
||||
qrCodeResultScale = 1F;
|
||||
}
|
||||
catch (ReaderException)
|
||||
{
|
||||
// QR Detection Failed
|
||||
qrCodeResult = null;
|
||||
}
|
||||
|
||||
if (qrCodeResult == null)
|
||||
{
|
||||
var sizePoints = PdfiumDocument.PageSizes[PageIndex];
|
||||
|
||||
// Try at 175%
|
||||
using (var image = PdfiumDocument.Render(PageIndex, (int)(sizePoints.Width * 1.75), (int)(sizePoints.Height * 1.75), 72F, 72F, false))
|
||||
{
|
||||
qrImageSource = new BitmapLuminanceSource((Bitmap)image);
|
||||
|
||||
// Try Entire Image
|
||||
qrBinarizer = new HybridBinarizer(qrImageSource);
|
||||
qrBinaryBitmap = new BinaryBitmap(qrBinarizer);
|
||||
|
||||
try
|
||||
{
|
||||
qrCodeResult = qrReader.decodeMultiple(qrBinaryBitmap, qrReaderHints)?.FirstOrDefault();
|
||||
qrCodeResultScale = 1.75F;
|
||||
}
|
||||
catch (ReaderException)
|
||||
{
|
||||
// QR Detection Failed
|
||||
qrCodeResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (qrCodeResult == null)
|
||||
{
|
||||
// Try at 200%
|
||||
using (var image = PdfiumDocument.Render(PageIndex, (int)(sizePoints.Width * 2), (int)(sizePoints.Height * 2), 72F, 72F, false))
|
||||
{
|
||||
qrImageSource = new BitmapLuminanceSource((Bitmap)image);
|
||||
|
||||
// Try Entire Image
|
||||
qrBinarizer = new HybridBinarizer(qrImageSource);
|
||||
qrBinaryBitmap = new BinaryBitmap(qrBinarizer);
|
||||
|
||||
try
|
||||
{
|
||||
qrCodeResult = qrReader.decodeMultiple(qrBinaryBitmap, qrReaderHints)?.FirstOrDefault();
|
||||
qrCodeResultScale = 2F;
|
||||
}
|
||||
catch (ReaderException)
|
||||
{
|
||||
// QR Detection Failed
|
||||
qrCodeResult = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (qrCodeResult != null)
|
||||
{
|
||||
// Detect Rotation
|
||||
var rotationAngle = Math.Atan2(
|
||||
qrCodeResult.ResultPoints[2].Y - qrCodeResult.ResultPoints[1].Y,
|
||||
qrCodeResult.ResultPoints[2].X - qrCodeResult.ResultPoints[1].X) * 180 / Math.PI;
|
||||
if (rotationAngle <= 45 || rotationAngle > 315)
|
||||
{
|
||||
detectedRotation = RotateFlipType.RotateNoneFlipNone;
|
||||
}
|
||||
else if (rotationAngle <= 135)
|
||||
{
|
||||
detectedRotation = RotateFlipType.Rotate270FlipNone;
|
||||
}
|
||||
else if (rotationAngle <= 225)
|
||||
{
|
||||
detectedRotation = RotateFlipType.Rotate180FlipNone;
|
||||
}
|
||||
else
|
||||
{
|
||||
detectedRotation = RotateFlipType.Rotate90FlipNone;
|
||||
}
|
||||
|
||||
// Reset Thumbnail
|
||||
if (renderedThumbnail != null)
|
||||
{
|
||||
renderedThumbnail.Dispose();
|
||||
renderedThumbnail = null;
|
||||
}
|
||||
|
||||
// Try binary encoding (from v2)
|
||||
if (qrCodeResult.ResultMetadata.ContainsKey(ResultMetadataType.BYTE_SEGMENTS))
|
||||
{
|
||||
var byteSegments = (List<byte[]>)qrCodeResult.ResultMetadata[ResultMetadataType.BYTE_SEGMENTS];
|
||||
var qrBytes = byteSegments[0];
|
||||
if (DocumentUniqueIdentifier.IsDocumentUniqueIdentifier(qrBytes))
|
||||
{
|
||||
Identifier = DocumentUniqueIdentifier.Parse(Database, qrBytes);
|
||||
}
|
||||
}
|
||||
// Fall back to v1
|
||||
if (Identifier == null)
|
||||
{
|
||||
Identifier = DocumentUniqueIdentifier.Parse(Database, qrCodeResult.Text);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Bitmap GetAttachmentThumbnail()
|
||||
{
|
||||
var thumbnail = renderedImage.ResizeImage(48, 48, Brushes.White);
|
||||
|
||||
// Draw Rotation
|
||||
if (detectedRotation != RotateFlipType.RotateNoneFlipNone)
|
||||
{
|
||||
thumbnail.RotateFlip(detectedRotation);
|
||||
}
|
||||
|
||||
// Add PDF Icon overlay
|
||||
using (Image mimeTypeIcon = Disco.Services.Properties.Resources.MimeType_pdf16)
|
||||
{
|
||||
thumbnail.EmbedIconOverlay(mimeTypeIcon);
|
||||
}
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
private Image GetRenderedImage()
|
||||
{
|
||||
if (renderedImage == null)
|
||||
{
|
||||
var pageSize = PdfiumDocument.PageSizes[PageIndex];
|
||||
|
||||
renderedImage = PdfiumDocument.Render(PageIndex, (int)pageSize.Width, (int)pageSize.Height, 72F, 72F, true);
|
||||
}
|
||||
|
||||
return renderedImage;
|
||||
}
|
||||
|
||||
private Bitmap GetRenderedThumbnail()
|
||||
{
|
||||
if (renderedThumbnail == null)
|
||||
{
|
||||
renderedThumbnail = GetRenderedImage().ResizeImage(256, 256, Brushes.White);
|
||||
|
||||
if (qrCodeResult != null && qrCodeResult.ResultPoints.Length == 4)
|
||||
{
|
||||
float thumbnailScale;
|
||||
var thumbnailOffset = renderedImage.CalculateResize(renderedThumbnail.Width, renderedThumbnail.Height, out thumbnailScale);
|
||||
thumbnailScale = thumbnailScale / qrCodeResultScale;
|
||||
|
||||
using (Graphics thumbnailGraphics = Graphics.FromImage(renderedThumbnail))
|
||||
{
|
||||
// Draw Square on QR Code
|
||||
var linePoints = qrCodeResult.ResultPoints.Select(p => new Point((int)(thumbnailOffset.X + (p.X * thumbnailScale)), (int)(thumbnailOffset.Y + (p.Y * thumbnailScale)))).ToList();
|
||||
using (GraphicsPath graphicsPath = new GraphicsPath())
|
||||
{
|
||||
for (int linePointIndex = 0; linePointIndex < (linePoints.Count - 1); linePointIndex++)
|
||||
graphicsPath.AddLine(linePoints[linePointIndex], linePoints[linePointIndex + 1]);
|
||||
|
||||
graphicsPath.AddLine(linePoints[linePoints.Count - 1], linePoints[0]);
|
||||
|
||||
using (SolidBrush graphicsBrush = new SolidBrush(Color.FromArgb(128, 255, 0, 0)))
|
||||
thumbnailGraphics.FillPath(graphicsBrush, graphicsPath);
|
||||
|
||||
using (Pen graphicsPen = new Pen(Color.FromArgb(200, 255, 0, 0), 2))
|
||||
thumbnailGraphics.DrawPath(graphicsPen, graphicsPath);
|
||||
}
|
||||
|
||||
// Draw Rotation
|
||||
if (detectedRotation != RotateFlipType.RotateNoneFlipNone)
|
||||
{
|
||||
renderedThumbnail.RotateFlip(detectedRotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return renderedThumbnail;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
if (renderedImage != null)
|
||||
renderedImage.Dispose();
|
||||
if (renderedThumbnail != null)
|
||||
renderedThumbnail.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Logging;
|
||||
using Disco.Services.Users;
|
||||
using PdfSharp.Pdf;
|
||||
using PdfSharp.Pdf.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services.Documents.AttachmentImport
|
||||
{
|
||||
public static class Importer
|
||||
{
|
||||
public static void Import(DiscoDataContext Database, string SessionId, string Filename)
|
||||
{
|
||||
var dataStoreUnassignedLocation = DataStore.CreateLocation(Database, "DocumentDropBox_Unassigned");
|
||||
var dataStoreSessionPagesCacheLocation = DataStore.CreateLocation(Database, "Cache\\DocumentDropBox_SessionPages");
|
||||
var documentTemplates = Database.DocumentTemplates.ToArray();
|
||||
|
||||
if (!File.Exists(Filename))
|
||||
{
|
||||
DocumentsLog.LogImportWarning(SessionId, string.Format("File not found: {0}", Filename));
|
||||
throw new FileNotFoundException("Document Not Found", Filename);
|
||||
}
|
||||
|
||||
DocumentsLog.LogImportProgress(SessionId, 0, "Reading File");
|
||||
|
||||
List<ImportPage> pages = null;
|
||||
List<ImportPage> assignedPages;
|
||||
double progressInterval;
|
||||
|
||||
try
|
||||
{
|
||||
// Use Pdfium to Rasterize and Detect Pages
|
||||
using (var importFileStream = new FileStream(Filename, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
|
||||
{
|
||||
using (var pdfiumDocument = PdfiumViewer.PdfDocument.Load(importFileStream))
|
||||
{
|
||||
progressInterval = 70D / pdfiumDocument.PageCount;
|
||||
pages = new List<ImportPage>(pdfiumDocument.PageCount);
|
||||
assignedPages = new List<ImportPage>(pdfiumDocument.PageCount);
|
||||
|
||||
// Rasterize and Detect Pages
|
||||
for (int pageIndex = 0; pageIndex < pdfiumDocument.PageCount; pageIndex++)
|
||||
{
|
||||
var pageNumber = pageIndex + 1;
|
||||
|
||||
DocumentsLog.LogImportProgress(SessionId, (int)(pageIndex * progressInterval), $"Processing Page {pageNumber} of {pdfiumDocument.PageCount}");
|
||||
DocumentsLog.LogImportPageStarting(SessionId, pageNumber);
|
||||
|
||||
var page = new ImportPage(Database, SessionId, pdfiumDocument, pageIndex);
|
||||
pages.Add(page);
|
||||
|
||||
// Write Session Thumbnail
|
||||
page.WriteThumbnailSessionCache();
|
||||
DocumentsLog.LogImportPageImageUpdate(SessionId, pageNumber);
|
||||
|
||||
// Detect Image
|
||||
if (page.DetectQRCode())
|
||||
{
|
||||
// Write updated session thumbnail
|
||||
page.WriteThumbnailSessionCache();
|
||||
DocumentsLog.LogImportPageImageUpdate(SessionId, pageNumber);
|
||||
var identifier = page.Identifier;
|
||||
DocumentsLog.LogImportPageDetected(SessionId, pageNumber, identifier.DocumentTemplateId, identifier.DocumentTemplate.Description, identifier.DocumentTemplate.Scope, identifier.TargetId, identifier.Target.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
page.WriteUndetectedImages();
|
||||
DocumentsLog.LogImportPageUndetected(SessionId, pageNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Use PdfSharp to Import and Build Documents
|
||||
using (var importFileStream = new FileStream(Filename, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
|
||||
{
|
||||
using (var pdfSharpDocument = PdfReader.Open(importFileStream, PdfDocumentOpenMode.Import))
|
||||
{
|
||||
// Assign Pages
|
||||
var documents = pages
|
||||
.Where(p => p.IsValidAttachment)
|
||||
.GroupBy(p => p.Identifier.DocumentGroupingId)
|
||||
.ToList();
|
||||
|
||||
if (documents.Count > 0)
|
||||
{
|
||||
progressInterval = 20D / documents.Count;
|
||||
|
||||
foreach (var document in documents)
|
||||
{
|
||||
var documentPages = document.OrderBy(p => p.Identifier.PageIndex).ToList();
|
||||
var documentPageFirst = documentPages.First();
|
||||
|
||||
DocumentsLog.LogImportProgress(SessionId, (int)(70D + (documents.IndexOf(document) * progressInterval)), $"Importing Documents {documents.IndexOf(document) + 1} of {documents.Count}");
|
||||
|
||||
using (MemoryStream msBuilder = new MemoryStream())
|
||||
{
|
||||
using (var pdfSharpDocumentOutput = new PdfDocument())
|
||||
{
|
||||
foreach (var documentPage in documentPages)
|
||||
{
|
||||
var pdfSharpImportPage = pdfSharpDocument.Pages[documentPage.PageIndex];
|
||||
var importedPage = pdfSharpDocumentOutput.AddPage(pdfSharpImportPage);
|
||||
|
||||
importedPage.Rotate = documentPage.Rotation;
|
||||
}
|
||||
|
||||
pdfSharpDocumentOutput.Save(msBuilder, false);
|
||||
}
|
||||
|
||||
msBuilder.Position = 0;
|
||||
using (var attachmentThumbnail = documentPageFirst.GetAttachmentThumbnail())
|
||||
{
|
||||
documentPageFirst.Identifier.ImportPdfAttachment(Database, msBuilder, attachmentThumbnail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write Unassigned Pages
|
||||
var unassignedPages = pages
|
||||
.Where(p => !p.IsValidAttachment)
|
||||
.ToList();
|
||||
|
||||
if (unassignedPages.Count > 0)
|
||||
{
|
||||
progressInterval = 10D / unassignedPages.Count;
|
||||
|
||||
foreach (var documentPage in unassignedPages)
|
||||
{
|
||||
DocumentsLog.LogImportProgress(SessionId, (int)(90 + (unassignedPages.IndexOf(documentPage) * progressInterval)), string.Format("Processing Undetected Pages {0} of {1}", unassignedPages.IndexOf(documentPage) + 1, unassignedPages.Count));
|
||||
|
||||
using (var pdfSharpDocumentOutput = new PdfDocument())
|
||||
{
|
||||
var pdfSharpImportPage = pdfSharpDocument.Pages[documentPage.PageIndex];
|
||||
pdfSharpDocumentOutput.AddPage(pdfSharpImportPage);
|
||||
|
||||
var filename = Path.Combine(dataStoreUnassignedLocation, $"{SessionId}_{documentPage.PageIndex + 1}.pdf");
|
||||
|
||||
pdfSharpDocumentOutput.Save(filename);
|
||||
|
||||
DocumentsLog.LogImportPageUndetectedStored(SessionId, documentPage.PageIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Dispose of pages
|
||||
if (pages != null && pages.Count != 0)
|
||||
{
|
||||
for (int i = 0; i < pages.Count; i++)
|
||||
{
|
||||
pages[i].Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ImportPdfAttachment(this DocumentUniqueIdentifier Identifier, DiscoDataContext Database, string PdfFilename)
|
||||
{
|
||||
return ImportPdfAttachment(Identifier, Database, PdfFilename, null);
|
||||
}
|
||||
|
||||
public static bool ImportPdfAttachment(this DocumentUniqueIdentifier Identifier, DiscoDataContext Database, string PdfFilename, Image Thumbnail)
|
||||
{
|
||||
using (var pdfStream = File.OpenRead(PdfFilename))
|
||||
{
|
||||
return ImportPdfAttachment(Identifier, Database, pdfStream, Thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ImportPdfAttachment(this DocumentUniqueIdentifier Identifier, DiscoDataContext Database, Stream PdfContent)
|
||||
{
|
||||
return ImportPdfAttachment(Identifier, Database, PdfContent, null);
|
||||
}
|
||||
|
||||
public static bool ImportPdfAttachment(this DocumentUniqueIdentifier Identifier, DiscoDataContext Database, Stream PdfContent, Image Thumbnail)
|
||||
{
|
||||
string filename;
|
||||
string comments;
|
||||
IAttachment attachment;
|
||||
|
||||
if (Identifier.DocumentTemplate == null)
|
||||
{
|
||||
filename = $"{Identifier.Target.AttachmentReferenceId.Replace('\\', '_')}_{Identifier.TimeStamp:yyyyMMdd-HHmmss}.pdf";
|
||||
comments = $"Uploaded: {Identifier.TimeStamp:s}";
|
||||
}
|
||||
else
|
||||
{
|
||||
filename = $"{Identifier.DocumentTemplateId}_{Identifier.TimeStamp:yyyyMMdd-HHmmss}.pdf";
|
||||
comments = string.Format("Generated: {0:s}", Identifier.TimeStamp);
|
||||
}
|
||||
|
||||
User creatorUser = UserService.GetUser(Identifier.CreatorId, Database);
|
||||
if (creatorUser == null)
|
||||
{
|
||||
// No Creator User (or Username invalid)
|
||||
creatorUser = UserService.CurrentUser;
|
||||
}
|
||||
|
||||
switch (Identifier.AttachmentType)
|
||||
{
|
||||
case AttachmentTypes.Device:
|
||||
Device d = (Device)Identifier.Target;
|
||||
attachment = d.CreateAttachment(Database, creatorUser, filename, DocumentTemplate.PdfMimeType, comments, PdfContent, Identifier.DocumentTemplate, Thumbnail);
|
||||
break;
|
||||
case AttachmentTypes.Job:
|
||||
Job j = (Job)Identifier.Target;
|
||||
attachment = j.CreateAttachment(Database, creatorUser, filename, DocumentTemplate.PdfMimeType, comments, PdfContent, Identifier.DocumentTemplate, Thumbnail);
|
||||
break;
|
||||
case AttachmentTypes.User:
|
||||
User u = (User)Identifier.Target;
|
||||
attachment = u.CreateAttachment(Database, creatorUser, filename, DocumentTemplate.PdfMimeType, comments, PdfContent, Identifier.DocumentTemplate, Thumbnail);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Identifier.DocumentTemplate != null && !string.IsNullOrWhiteSpace(Identifier.DocumentTemplate.OnImportAttachmentExpression))
|
||||
{
|
||||
try
|
||||
{
|
||||
var expressionResult = Identifier.DocumentTemplate.EvaluateOnAttachmentImportExpression(attachment, Database, creatorUser, Identifier.TimeStamp);
|
||||
DocumentsLog.LogImportAttachmentExpressionEvaluated(Identifier.DocumentTemplate, Identifier.Target, attachment, expressionResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SystemLog.LogException("Document Importer - OnImportAttachmentExpression", ex);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Services.Logging;
|
||||
using Disco.Services.Tasks;
|
||||
using Quartz;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services.Documents.AttachmentImport
|
||||
{
|
||||
public class ImporterCleanCacheJob : ScheduledTask
|
||||
{
|
||||
public override string TaskName { get { return "Document Importer - Clean Cache Task"; } }
|
||||
|
||||
public override bool SingleInstanceTask { get { return true; } }
|
||||
public override bool CancelInitiallySupported { get { return false; } }
|
||||
public override bool LogExceptionsOnly { get { return true; } }
|
||||
|
||||
public override void InitalizeScheduledTask(DiscoDataContext Database)
|
||||
{
|
||||
// Trigger Daily @ 12:30am
|
||||
TriggerBuilder triggerBuilder = TriggerBuilder.Create().WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(0, 30));
|
||||
|
||||
this.ScheduleTask(triggerBuilder);
|
||||
}
|
||||
|
||||
protected override void ExecuteTask()
|
||||
{
|
||||
string dataStoreLocation;
|
||||
using (DiscoDataContext database = new DiscoDataContext())
|
||||
{
|
||||
dataStoreLocation = DataStore.CreateLocation(database, @"Cache\DocumentDropBox_SessionPages");
|
||||
}
|
||||
|
||||
int deleteCount = 0;
|
||||
int errorCount = 0;
|
||||
|
||||
var dataStoreInfo = new DirectoryInfo(dataStoreLocation);
|
||||
|
||||
foreach (var file in dataStoreInfo.GetFiles())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file.CreationTime < DateTime.Today)
|
||||
{
|
||||
file.Delete();
|
||||
deleteCount++;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
SystemLog.LogInformation(
|
||||
$"Cleared DocumentDropBox_SessionPages Cache, Deleted {deleteCount} File/s, with {errorCount} Error/s",
|
||||
deleteCount,
|
||||
errorCount
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Disco.Data.Repository;
|
||||
using Exceptionless;
|
||||
using Quartz;
|
||||
using Quartz.Impl.Triggers;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services.Documents.AttachmentImport
|
||||
{
|
||||
[PersistJobDataAfterExecution]
|
||||
public class ImporterJob : IJob
|
||||
{
|
||||
public void Execute(IJobExecutionContext context)
|
||||
{
|
||||
var sessionId = context.JobDetail.JobDataMap["SessionId"] as string;
|
||||
if (sessionId == null)
|
||||
{
|
||||
sessionId = Guid.NewGuid().ToString();
|
||||
context.JobDetail.JobDataMap["SessionId"] = sessionId;
|
||||
}
|
||||
|
||||
var filename = context.JobDetail.JobDataMap["Filename"] as string;
|
||||
var retryCount = (int)context.JobDetail.JobDataMap["RetryCount"];
|
||||
|
||||
using (DiscoDataContext database = new DiscoDataContext())
|
||||
{
|
||||
try
|
||||
{
|
||||
DocumentsLog.LogImportStarting(sessionId, Path.GetFileName(filename));
|
||||
|
||||
// Returns null if unrecoverable error (eg. Not matched document)
|
||||
Importer.Import(database, sessionId, filename);
|
||||
|
||||
// Success - Delete File
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
|
||||
DocumentsLog.LogImportFinished(sessionId);
|
||||
|
||||
// All Done - Delete job
|
||||
context.Scheduler.DeleteJob(context.JobDetail.Key);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// File not found - Delete job and don't reschedule
|
||||
context.Scheduler.DeleteJob(context.JobDetail.Key);
|
||||
DocumentsLog.LogImportFinished(sessionId);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.ToExceptionless().Submit();
|
||||
|
||||
// Retry 18 times (for 3 minutes)
|
||||
if (retryCount < 18)
|
||||
{
|
||||
context.JobDetail.JobDataMap["RetryCount"] = ++retryCount;
|
||||
DocumentsLog.LogImportWarning(sessionId, string.Format("{0}; Will try again in 10 Seconds", ex.Message));
|
||||
// Reschedule Job for 10 seconds
|
||||
var trig = new SimpleTriggerImpl(Guid.NewGuid().ToString(), new DateTimeOffset(DateTime.Now.AddSeconds(10)));
|
||||
context.Scheduler.RescheduleJob(context.Trigger.Key, trig);
|
||||
}
|
||||
else
|
||||
{
|
||||
// To Many Errors
|
||||
DocumentsLog.LogImportError(sessionId, $"To many errors occurred trying to import (SessionId: {sessionId})");
|
||||
// Move to Errors Folder
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
try
|
||||
{
|
||||
var folderError = DataStore.CreateLocation(database, "DocumentDropBox_Errors");
|
||||
var filenameError = Path.Combine(folderError, Path.GetFileName(filename));
|
||||
var filenameErrorCount = 0;
|
||||
while (File.Exists(filenameError))
|
||||
{
|
||||
filenameError = Path.Combine(folderError, $"{Path.GetFileNameWithoutExtension(filename)} ({++filenameErrorCount}){Path.GetExtension(filename)}");
|
||||
}
|
||||
File.Move(filename, filenameError);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore Errors
|
||||
}
|
||||
}
|
||||
DocumentsLog.LogImportFinished(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user