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:
Gary Sharp
2016-08-26 09:46:35 +10:00
parent 44f6d325db
commit 5ea9a814d6
98 changed files with 3168 additions and 3202 deletions
@@ -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);
}
}
}
}
}
}