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:
@@ -16,6 +16,10 @@
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Common.Logging" publicKeyToken="AF08829B84F0328E" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
|
||||
@@ -46,4 +50,4 @@
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
</configuration>
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Exceptionless;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class AttachmentActionExtensions
|
||||
{
|
||||
public static DeviceAttachment CreateAttachment(this Device Device, DiscoDataContext Database, User CreatorUser, string Filename, string MimeType, string Comments, Stream Content, DocumentTemplate DocumentTemplate = null, Image PdfThumbnail = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(MimeType) || MimeType.Equals("unknown/unknown", StringComparison.OrdinalIgnoreCase))
|
||||
MimeType = Interop.MimeTypes.ResolveMimeType(Filename);
|
||||
|
||||
DeviceAttachment da = new DeviceAttachment()
|
||||
{
|
||||
DeviceSerialNumber = Device.SerialNumber,
|
||||
TechUserId = CreatorUser.UserId,
|
||||
Filename = Filename,
|
||||
MimeType = MimeType,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = Comments
|
||||
};
|
||||
|
||||
if (DocumentTemplate != null)
|
||||
da.DocumentTemplateId = DocumentTemplate.Id;
|
||||
|
||||
Database.DeviceAttachments.Add(da);
|
||||
Database.SaveChanges();
|
||||
|
||||
da.SaveAttachment(Database, Content);
|
||||
Content.Position = 0;
|
||||
if (PdfThumbnail == null)
|
||||
da.GenerateThumbnail(Database, Content);
|
||||
else
|
||||
da.SaveThumbnailAttachment(Database, PdfThumbnail);
|
||||
|
||||
return da;
|
||||
}
|
||||
|
||||
public static JobAttachment CreateAttachment(this Job Job, DiscoDataContext Database, User CreatorUser, string Filename, string MimeType, string Comments, Stream Content, DocumentTemplate DocumentTemplate = null, Image PdfThumbnail = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(MimeType) || MimeType.Equals("unknown/unknown", StringComparison.OrdinalIgnoreCase))
|
||||
MimeType = Interop.MimeTypes.ResolveMimeType(Filename);
|
||||
|
||||
JobAttachment ja = new JobAttachment()
|
||||
{
|
||||
JobId = Job.Id,
|
||||
TechUserId = CreatorUser.UserId,
|
||||
Filename = Filename,
|
||||
MimeType = MimeType,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = Comments
|
||||
};
|
||||
|
||||
if (DocumentTemplate != null)
|
||||
ja.DocumentTemplateId = DocumentTemplate.Id;
|
||||
|
||||
Database.JobAttachments.Add(ja);
|
||||
Database.SaveChanges();
|
||||
|
||||
ja.SaveAttachment(Database, Content);
|
||||
Content.Position = 0;
|
||||
if (PdfThumbnail == null)
|
||||
ja.GenerateThumbnail(Database, Content);
|
||||
else
|
||||
ja.SaveThumbnailAttachment(Database, PdfThumbnail);
|
||||
|
||||
return ja;
|
||||
}
|
||||
|
||||
public static UserAttachment CreateAttachment(this User User, DiscoDataContext Database, User CreatorUser, string Filename, string MimeType, string Comments, Stream Content, DocumentTemplate DocumentTemplate = null, Image PdfThumbnail = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(MimeType) || MimeType.Equals("unknown/unknown", StringComparison.OrdinalIgnoreCase))
|
||||
MimeType = Interop.MimeTypes.ResolveMimeType(Filename);
|
||||
|
||||
UserAttachment ua = new UserAttachment()
|
||||
{
|
||||
UserId = User.UserId,
|
||||
TechUserId = CreatorUser.UserId,
|
||||
Filename = Filename,
|
||||
MimeType = MimeType,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = Comments
|
||||
};
|
||||
|
||||
if (DocumentTemplate != null)
|
||||
ua.DocumentTemplateId = DocumentTemplate.Id;
|
||||
|
||||
Database.UserAttachments.Add(ua);
|
||||
Database.SaveChanges();
|
||||
|
||||
ua.SaveAttachment(Database, Content);
|
||||
Content.Position = 0;
|
||||
if (PdfThumbnail == null)
|
||||
ua.GenerateThumbnail(Database, Content);
|
||||
else
|
||||
ua.SaveThumbnailAttachment(Database, PdfThumbnail);
|
||||
|
||||
return ua;
|
||||
}
|
||||
|
||||
public static string GenerateThumbnail(this IAttachment attachment, DiscoDataContext Database, Stream AttachmentStream)
|
||||
{
|
||||
string thumbnailFilePath = attachment.RepositoryThumbnailFilename(Database);
|
||||
|
||||
Image thumbnail;
|
||||
if (GenerateThumbnail(AttachmentStream, attachment.MimeType, out thumbnail))
|
||||
{
|
||||
thumbnail.SaveJpg(90, thumbnailFilePath);
|
||||
}
|
||||
|
||||
return thumbnailFilePath;
|
||||
}
|
||||
|
||||
public static string GenerateThumbnail(this IAttachment attachment, DiscoDataContext Database)
|
||||
{
|
||||
string thumbnailFilePath = attachment.RepositoryThumbnailFilename(Database);
|
||||
|
||||
using (var attachmentStream = File.OpenRead(attachment.RepositoryFilename(Database)))
|
||||
{
|
||||
Image thumbnail;
|
||||
if (GenerateThumbnail(attachmentStream, attachment.MimeType, out thumbnail))
|
||||
{
|
||||
thumbnail.SaveJpg(90, thumbnailFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
return thumbnailFilePath;
|
||||
}
|
||||
|
||||
public static bool GenerateThumbnail(Stream Source, string SourceMimeType, out Image Thumbnail)
|
||||
{
|
||||
if (Source != null)
|
||||
{
|
||||
// GDI+ (jpg, png, gif, bmp)
|
||||
if (SourceMimeType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase) || SourceMimeType.Contains("jpg") ||
|
||||
SourceMimeType.Equals("image/png", StringComparison.OrdinalIgnoreCase) || SourceMimeType.Contains("png") ||
|
||||
SourceMimeType.Equals("image/gif", StringComparison.OrdinalIgnoreCase) || SourceMimeType.Contains("gif") ||
|
||||
SourceMimeType.Equals("image/bmp", StringComparison.OrdinalIgnoreCase) || SourceMimeType.Contains("bmp"))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (Image sourceImage = Image.FromStream(Source))
|
||||
{
|
||||
Thumbnail = sourceImage.ResizeImage(48, 48);
|
||||
using (Image mimeTypeIcon = Disco.Services.Properties.Resources.MimeType_img16)
|
||||
{
|
||||
Thumbnail.EmbedIconOverlay(mimeTypeIcon);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.ToExceptionless().Submit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// PDF
|
||||
if (SourceMimeType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase) || SourceMimeType.Contains("pdf"))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var pdfiumDocument = PdfiumViewer.PdfDocument.Load(Source))
|
||||
{
|
||||
|
||||
if (pdfiumDocument.PageCount > 0)
|
||||
{
|
||||
var pageSize = pdfiumDocument.PageSizes[0];
|
||||
var size = ImagingExtensions.CalculateResize((int)pageSize.Width, (int)pageSize.Height, 48, 48);
|
||||
|
||||
Thumbnail = pdfiumDocument.Render(0, (int)size.Width, (int)size.Height, 72, 72, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.ToExceptionless().Submit();
|
||||
}
|
||||
}
|
||||
}
|
||||
Thumbnail = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class AttachmentDataStoreExtensions
|
||||
{
|
||||
|
||||
public static string RepositoryFilename(this IAttachment Attachment, DiscoDataContext Database)
|
||||
{
|
||||
switch (Attachment.AttachmentType)
|
||||
{
|
||||
case AttachmentTypes.Device:
|
||||
return Path.Combine(DataStore.CreateLocation(Database, "DeviceAttachments", Attachment.Timestamp),
|
||||
string.Format("{0}_{1}_file", Attachment.Reference, Attachment.Id));
|
||||
case AttachmentTypes.Job:
|
||||
return Path.Combine(DataStore.CreateLocation(Database, "JobAttachments", Attachment.Timestamp),
|
||||
string.Format("{0}_{1}_file", Attachment.Reference, Attachment.Id));
|
||||
case AttachmentTypes.User:
|
||||
return Path.Combine(DataStore.CreateLocation(Database, "UserAttachments", Attachment.Timestamp),
|
||||
string.Format("{0}_{1}_file", ((string)Attachment.Reference).Replace('\\', '_'), Attachment.Id));
|
||||
default:
|
||||
throw new ArgumentException("Unknown Attachment Type", nameof(Attachment));
|
||||
}
|
||||
}
|
||||
|
||||
public static string RepositoryThumbnailFilename(this IAttachment Attachment, DiscoDataContext Database)
|
||||
{
|
||||
switch (Attachment.AttachmentType)
|
||||
{
|
||||
case AttachmentTypes.Device:
|
||||
return Path.Combine(DataStore.CreateLocation(Database, "DeviceAttachments", Attachment.Timestamp),
|
||||
string.Format("{0}_{1}_thumb.jpg", Attachment.Reference, Attachment.Id));
|
||||
case AttachmentTypes.Job:
|
||||
return Path.Combine(DataStore.CreateLocation(Database, "JobAttachments", Attachment.Timestamp),
|
||||
string.Format("{0}_{1}_thumb.jpg", Attachment.Reference, Attachment.Id));
|
||||
case AttachmentTypes.User:
|
||||
return Path.Combine(DataStore.CreateLocation(Database, "UserAttachments", Attachment.Timestamp),
|
||||
string.Format("{0}_{1}_thumb.jpg", ((string)Attachment.Reference).Replace('\\', '_'), Attachment.Id));
|
||||
default:
|
||||
throw new ArgumentException("Unknown Attachment Type", nameof(Attachment));
|
||||
}
|
||||
}
|
||||
|
||||
public static string SaveAttachment(this IAttachment Attachment, DiscoDataContext Database, Stream FileContent)
|
||||
{
|
||||
string filePath = Attachment.RepositoryFilename(Database);
|
||||
DataStore.WriteFile(filePath, FileContent);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public static string SaveAttachment(this IAttachment Attachment, DiscoDataContext Database, byte[] FileContent)
|
||||
{
|
||||
string filePath = Attachment.RepositoryFilename(Database);
|
||||
DataStore.WriteFile(filePath, FileContent);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public static string SaveThumbnailAttachment(this IAttachment Attachment, DiscoDataContext Database, Image Thumbnail)
|
||||
{
|
||||
string filePath = Attachment.RepositoryThumbnailFilename(Database);
|
||||
Thumbnail.SaveJpg(90, filePath);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public static string SaveThumbnailAttachment(this IAttachment Attachment, DiscoDataContext Database, Stream FileContent)
|
||||
{
|
||||
string filePath = Attachment.RepositoryThumbnailFilename(Database);
|
||||
DataStore.WriteFile(filePath, FileContent);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public static string SaveThumbnailAttachment(this IAttachment Attachment, DiscoDataContext Database, byte[] FileContent)
|
||||
{
|
||||
string filePath = Attachment.RepositoryThumbnailFilename(Database);
|
||||
DataStore.WriteFile(filePath, FileContent);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public static void RepositoryDelete(this IAttachment Attachment, DiscoDataContext Database)
|
||||
{
|
||||
DataStore.DeleteFiles(Attachment.RepositoryFilename(Database), Attachment.RepositoryThumbnailFilename(Database));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Disco.Data.Configuration;
|
||||
using Disco.Data.Repository;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class DataStore
|
||||
{
|
||||
|
||||
public static string CreateLocation(DiscoDataContext Database, string SubLocation, DateTime? SubSubLocationTimestamp = null)
|
||||
{
|
||||
return CreateLocation(Database.DiscoConfiguration, SubLocation, SubSubLocationTimestamp);
|
||||
}
|
||||
|
||||
public static string CreateLocation(SystemConfiguration DiscoConfiguration, string SubLocation, DateTime? SubSubLocationTimestamp = null)
|
||||
{
|
||||
string SubSubLocation = string.Empty;
|
||||
if (SubSubLocationTimestamp.HasValue)
|
||||
SubSubLocation = SubSubLocationTimestamp.Value.ToString(@"yyyy\\MM");
|
||||
|
||||
string storeDirectory = System.IO.Path.Combine(DiscoConfiguration.DataStoreLocation, SubLocation, SubSubLocation);
|
||||
if (!System.IO.Directory.Exists(storeDirectory))
|
||||
System.IO.Directory.CreateDirectory(storeDirectory);
|
||||
|
||||
return storeDirectory;
|
||||
}
|
||||
|
||||
public static void DeleteFile(string FilePath)
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
File.Delete(FilePath);
|
||||
}
|
||||
|
||||
public static void DeleteFiles(params string[] FilePaths)
|
||||
{
|
||||
foreach (string filePath in FilePaths)
|
||||
{
|
||||
DeleteFile(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteFile(string FilePath, Stream FileContent)
|
||||
{
|
||||
using (FileStream outStream = new FileStream(FilePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
FileContent.CopyTo(outStream);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteFile(string FilePath, byte[] FileContent)
|
||||
{
|
||||
File.WriteAllBytes(FilePath, FileContent);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Disco.Models.Repository;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class DeviceDataStoreExtensions
|
||||
{
|
||||
public static bool ImageImport(this DeviceModel deviceModel, Stream ImageStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (Bitmap inputBitmap = new Bitmap(ImageStream))
|
||||
{
|
||||
using (Image outputBitmap = inputBitmap.ResizeImage(256, 256))
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
outputBitmap.SavePng(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
var deviceModelImagePath = deviceModel.ImageFilePath();
|
||||
|
||||
|
||||
using (var storeStream = new FileStream(deviceModelImagePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
ms.CopyTo(storeStream);
|
||||
}
|
||||
//deviceModel.Image = ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static FileStream Image(this DeviceModel deviceModel)
|
||||
{
|
||||
var deviceModelImagePath = deviceModel.ImageFilePath();
|
||||
|
||||
if (File.Exists(deviceModelImagePath))
|
||||
return new FileStream(deviceModelImagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string ImageFilePath(this DeviceModel deviceModel)
|
||||
{
|
||||
var configCache = new Disco.Data.Configuration.SystemConfiguration(null);
|
||||
|
||||
var deviceModelImagesDataStore = DataStore.CreateLocation(configCache, "DeviceModelImages");
|
||||
|
||||
return Path.Combine(deviceModelImagesDataStore, string.Format("{0}.png", deviceModel.Id));
|
||||
}
|
||||
|
||||
public static string ImageHash(this DeviceModel deviceModel)
|
||||
{
|
||||
var deviceModelImagePath = deviceModel.ImageFilePath();
|
||||
|
||||
if (File.Exists(deviceModelImagePath))
|
||||
return File.GetLastWriteTimeUtc(deviceModelImagePath).ToBinary().ToString();
|
||||
else
|
||||
return "-1";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,18 @@
|
||||
<Reference Include="Owin">
|
||||
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PdfiumViewer, Version=2.10.0.0, Culture=neutral, PublicKeyToken=91e4789cfb0609e0, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\PdfiumViewer.2.10.0.0\lib\net20\PdfiumViewer.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="PdfSharp, Version=1.32.3057.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\PDFsharp.1.32.3057.0\lib\net20\PdfSharp.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="PdfSharp.Charting, Version=1.32.3057.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\PDFsharp.1.32.3057.0\lib\net20\PdfSharp.Charting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Quartz">
|
||||
<HintPath>..\Resources\Libraries\Quartz\Quartz.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -77,6 +89,9 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\RazorGenerator.Mvc.2.2.3\lib\net40\RazorGenerator.Mvc.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Spring.Core">
|
||||
<HintPath>..\..\..\Resources\Libraries\Spring.NET\Spring.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -86,6 +101,7 @@
|
||||
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.DirectoryServices" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Net.Http.Extensions">
|
||||
@@ -146,9 +162,19 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\WebActivatorEx.2.0.5\lib\net40\WebActivatorEx.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="zxing, Version=0.14.0.0, Culture=neutral, PublicKeyToken=4e88037ac681fe60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ZXing.Net.0.14.0.1\lib\net40\zxing.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="zxing.presentation, Version=0.14.0.0, Culture=neutral, PublicKeyToken=4e88037ac681fe60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ZXing.Net.0.14.0.1\lib\net40\zxing.presentation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="App_Start\RazorGeneratorMvcStart.cs" />
|
||||
<Compile Include="Attachments\AttachmentActionExtensions.cs" />
|
||||
<Compile Include="Attachments\AttachmentDataStoreExtensions.cs" />
|
||||
<Compile Include="Authorization\AccessDeniedException.cs" />
|
||||
<Compile Include="Authorization\AuthorizationLog.cs" />
|
||||
<Compile Include="Authorization\AuthorizationToken.cs" />
|
||||
@@ -194,6 +220,8 @@
|
||||
<Compile Include="Authorization\Roles\RoleCache.cs" />
|
||||
<Compile Include="Authorization\Roles\RoleClaims.cs" />
|
||||
<Compile Include="Authorization\Roles\RoleToken.cs" />
|
||||
<Compile Include="DataStore.cs" />
|
||||
<Compile Include="Devices\DeviceDataStoreExtensions.cs" />
|
||||
<Compile Include="Devices\Exporting\DeviceExport.cs" />
|
||||
<Compile Include="Devices\Exporting\DeviceExportTask.cs" />
|
||||
<Compile Include="Devices\Exporting\DeviceExportTaskContext.cs" />
|
||||
@@ -227,8 +255,37 @@
|
||||
<Compile Include="Devices\ManagedGroups\DeviceManagedGroups.cs" />
|
||||
<Compile Include="Devices\ManagedGroups\DeviceProfileAssignedUsersManagedGroup.cs" />
|
||||
<Compile Include="Devices\ManagedGroups\DeviceProfileDevicesManagedGroup.cs" />
|
||||
<Compile Include="Documents\AttachmentImport\Importer.cs" />
|
||||
<Compile Include="Documents\AttachmentImport\ImporterCleanCacheJob.cs" />
|
||||
<Compile Include="Documents\AttachmentImport\ImporterJob.cs" />
|
||||
<Compile Include="Documents\AttachmentImport\ImportDirectoryMonitor.cs" />
|
||||
<Compile Include="Documents\AttachmentImport\ImportPage.cs" />
|
||||
<Compile Include="Documents\DocumentsLog.cs" />
|
||||
<Compile Include="Documents\DocumentTemplateActionExtensions.cs" />
|
||||
<Compile Include="Documents\DocumentTemplateDataStoreExtensions.cs" />
|
||||
<Compile Include="Documents\DocumentTemplateExpressionExtensions.cs" />
|
||||
<Compile Include="Documents\DocumentUniqueIdentifier.cs" />
|
||||
<Compile Include="Documents\DocumentUniqueIdentifierExtensions.cs" />
|
||||
<Compile Include="Expressions\EvaluateExpressionParseException.cs" />
|
||||
<Compile Include="Expressions\EvaluateExpressionPart.cs" />
|
||||
<Compile Include="Expressions\Expression.cs" />
|
||||
<Compile Include="Expressions\ExpressionCache.cs" />
|
||||
<Compile Include="Expressions\ExpressionCachePreloadTask.cs" />
|
||||
<Compile Include="Expressions\ExpressionTypeDescriptor.cs" />
|
||||
<Compile Include="Expressions\ExpressionTypeMemberDescriptor.cs" />
|
||||
<Compile Include="Expressions\Extensions\DataExt.cs" />
|
||||
<Compile Include="Expressions\Extensions\DeviceExt.cs" />
|
||||
<Compile Include="Expressions\Extensions\ImageExt.cs" />
|
||||
<Compile Include="Expressions\Extensions\ImageResultImplementations\BaseImageExpressionResult.cs" />
|
||||
<Compile Include="Expressions\Extensions\ImageResultImplementations\BitmapImageExpressionResult.cs" />
|
||||
<Compile Include="Expressions\Extensions\ImageResultImplementations\FileImageExpressionResult.cs" />
|
||||
<Compile Include="Expressions\Extensions\ImageResultImplementations\FileMontageImageExpressionResult.cs" />
|
||||
<Compile Include="Expressions\Extensions\UserExt.cs" />
|
||||
<Compile Include="Expressions\IExpressionPart.cs" />
|
||||
<Compile Include="Expressions\TextExpressionPart.cs" />
|
||||
<Compile Include="Extensions\DateTimeExtensions.cs" />
|
||||
<Compile Include="Extensions\EnumerableExtensions.cs" />
|
||||
<Compile Include="Extensions\ImagingExtensions.cs" />
|
||||
<Compile Include="Extensions\RxExtensions.cs" />
|
||||
<Compile Include="Extensions\StringExtensions.cs" />
|
||||
<Compile Include="Extensions\UIHelpers.cs" />
|
||||
@@ -257,6 +314,7 @@
|
||||
<Compile Include="Interop\DiscoServices\Jobs.cs" />
|
||||
<Compile Include="Interop\DiscoServices\PluginLibrary.cs" />
|
||||
<Compile Include="Interop\DiscoServices\PluginLibraryUpdateTask.cs" />
|
||||
<Compile Include="Interop\MimeTypes.cs" />
|
||||
<Compile Include="Interop\VicEduDept\VicSmart.cs" />
|
||||
<Compile Include="Interop\DiscoServices\UpdateQuery.cs" />
|
||||
<Compile Include="Interop\DiscoServices\UpdateQueryTask.cs" />
|
||||
@@ -322,6 +380,11 @@
|
||||
<Compile Include="Plugins\PluginWebViewPage.cs" />
|
||||
<Compile Include="Plugins\WebPageHelper.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Searching\Search.cs" />
|
||||
<Compile Include="Tasks\IScheduledTaskStatus.cs" />
|
||||
<Compile Include="Tasks\ScheduledTask.cs" />
|
||||
@@ -383,7 +446,25 @@
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<None Include="Resources\MimeType-doc48.png" />
|
||||
<None Include="Resources\MimeType-img16.png" />
|
||||
<None Include="Resources\MimeType-pdf16.png" />
|
||||
<None Include="Resources\MimeType-pdf48.png" />
|
||||
<None Include="Resources\MimeType-unknown48.png" />
|
||||
<Content Include="x64\pdfium.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="x86\pdfium.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class DocumentTemplateActionExtensions
|
||||
{
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class DocumentTemplateDataStoreExtensions
|
||||
{
|
||||
public static string RepositoryFilename(this DocumentTemplate dt, DiscoDataContext Database)
|
||||
{
|
||||
return Path.Combine(DataStore.CreateLocation(Database, "DocumentTemplates"), string.Format("{0}.pdf", dt.Id));
|
||||
}
|
||||
public static string SavePdfTemplate(this DocumentTemplate dt, DiscoDataContext Database, Stream TemplateFile)
|
||||
{
|
||||
string filePath = dt.RepositoryFilename(Database);
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
TemplateFile.CopyTo(fs);
|
||||
}
|
||||
Expressions.ExpressionCache.InvalidModule(string.Format(DocumentTemplateExpressionExtensions.CacheTemplate, dt.Id));
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Services.Expressions;
|
||||
using System;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class DocumentTemplateExpressionExtensions
|
||||
{
|
||||
internal const string CacheTemplate = "DocumentTemplate_{0}";
|
||||
|
||||
public static Expression FilterExpressionFromCache(this DocumentTemplate dt)
|
||||
{
|
||||
return ExpressionCache.GetValue("DocumentTemplate_FilterExpression", dt.Id, () => { return Expression.TokenizeSingleDynamic(null, dt.FilterExpression, 0); });
|
||||
}
|
||||
|
||||
public static void FilterExpressionInvalidateCache(this DocumentTemplate dt)
|
||||
{
|
||||
ExpressionCache.InvalidateKey("DocumentTemplate_FilterExpression", dt.Id);
|
||||
}
|
||||
|
||||
public static bool FilterExpressionMatches(this DocumentTemplate dt, object Data, DiscoDataContext Database, User User, System.DateTime TimeStamp, DocumentState State)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dt.FilterExpression))
|
||||
{
|
||||
Expression compiledExpression = dt.FilterExpressionFromCache();
|
||||
System.Collections.IDictionary evaluatorVariables = Expression.StandardVariables(dt, Database, User, TimeStamp, State);
|
||||
try
|
||||
{
|
||||
object er = compiledExpression.EvaluateFirst<object>(Data, evaluatorVariables);
|
||||
if (er is bool)
|
||||
{
|
||||
return (bool)er;
|
||||
}
|
||||
bool erBool;
|
||||
if (bool.TryParse(er.ToString(), out erBool))
|
||||
{
|
||||
return erBool;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Expression OnImportAttachmentExpressionFromCache(this DocumentTemplate dt)
|
||||
{
|
||||
return ExpressionCache.GetValue("DocumentTemplate_OnImportExpression", dt.Id, () => { return Expression.TokenizeSingleDynamic(null, dt.OnImportAttachmentExpression, 0); });
|
||||
}
|
||||
|
||||
public static void OnImportAttachmentExpressionInvalidateCache(this DocumentTemplate dt)
|
||||
{
|
||||
ExpressionCache.InvalidateKey("DocumentTemplate_OnImportExpression", dt.Id);
|
||||
}
|
||||
|
||||
public static string EvaluateOnAttachmentImportExpression(this DocumentTemplate dt, object Data, DiscoDataContext Database, User User, DateTime TimeStamp)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dt.OnImportAttachmentExpression))
|
||||
{
|
||||
Expression compiledExpression = dt.OnImportAttachmentExpressionFromCache();
|
||||
System.Collections.IDictionary evaluatorVariables = Expression.StandardVariables(dt, Database, User, TimeStamp, null);
|
||||
try
|
||||
{
|
||||
object result = compiledExpression.EvaluateFirst<object>(Data, evaluatorVariables);
|
||||
if (result == null)
|
||||
return null;
|
||||
else
|
||||
return result.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Expression OnGenerateExpressionFromCache(this DocumentTemplate dt)
|
||||
{
|
||||
return ExpressionCache.GetValue("DocumentTemplate_OnGenerateExpression", dt.Id, () => { return Expression.TokenizeSingleDynamic(null, dt.OnGenerateExpression, 0); });
|
||||
}
|
||||
|
||||
public static void OnGenerateExpressionInvalidateCache(this DocumentTemplate dt)
|
||||
{
|
||||
ExpressionCache.InvalidateKey("DocumentTemplate_OnGenerateExpression", dt.Id);
|
||||
}
|
||||
|
||||
public static string EvaluateOnGenerateExpression(this DocumentTemplate dt, object Data, DiscoDataContext Database, User User, DateTime TimeStamp, DocumentState State)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dt.OnGenerateExpression))
|
||||
{
|
||||
Expression compiledExpression = dt.OnGenerateExpressionFromCache();
|
||||
System.Collections.IDictionary evaluatorVariables = Expression.StandardVariables(dt, Database, User, TimeStamp, State);
|
||||
try
|
||||
{
|
||||
object result = compiledExpression.EvaluateFirst<object>(Data, evaluatorVariables);
|
||||
return result.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Interop.ActiveDirectory;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Disco.Services.Documents
|
||||
{
|
||||
public class DocumentUniqueIdentifier
|
||||
{
|
||||
private const int CurrentVersion = 2;
|
||||
private const byte MagicNumber = 0xC4;
|
||||
private DiscoDataContext database;
|
||||
|
||||
public int Version { get; private set; }
|
||||
public short DeploymentChecksum { get; private set; }
|
||||
public string DocumentTemplateId { get; private set; }
|
||||
public string TargetId { get; private set; }
|
||||
public string CreatorId { get; private set; }
|
||||
public DateTime TimeStamp { get; private set; }
|
||||
public int PageIndex { get; private set; }
|
||||
|
||||
private DocumentTemplate documentTemplate;
|
||||
private AttachmentTypes? attachmentType;
|
||||
private IAttachmentTarget target;
|
||||
private User creator;
|
||||
|
||||
public DocumentTemplate DocumentTemplate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (documentTemplate == null && !string.IsNullOrWhiteSpace(DocumentTemplateId) && !DocumentTemplateId.StartsWith("--", StringComparison.Ordinal))
|
||||
{
|
||||
documentTemplate = database.DocumentTemplates.Find(DocumentTemplateId);
|
||||
if (documentTemplate != null)
|
||||
{
|
||||
attachmentType = documentTemplate.AttachmentType;
|
||||
}
|
||||
}
|
||||
|
||||
return documentTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
public string DocumentGroupingId
|
||||
{
|
||||
get
|
||||
{
|
||||
// Unique identifier to distinguish this document from others (but keep pages together)
|
||||
return $"{TimeStamp.Ticks}|{TargetId}|{DocumentTemplateId}|{CreatorId}|{Version}|{DeploymentChecksum}";
|
||||
}
|
||||
}
|
||||
|
||||
public AttachmentTypes? AttachmentType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!attachmentType.HasValue)
|
||||
{
|
||||
if (DocumentTemplateId.StartsWith("--", StringComparison.Ordinal))
|
||||
{
|
||||
if (DocumentTemplateId.Equals("--DEVICE", StringComparison.Ordinal))
|
||||
{
|
||||
attachmentType = AttachmentTypes.Device;
|
||||
}
|
||||
else if (DocumentTemplateId.Equals("--JOB", StringComparison.Ordinal))
|
||||
{
|
||||
attachmentType = AttachmentTypes.Job;
|
||||
}
|
||||
else if (DocumentTemplateId.Equals("--USER", StringComparison.Ordinal))
|
||||
{
|
||||
attachmentType = AttachmentTypes.User;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var dt = DocumentTemplate;
|
||||
if (dt != null)
|
||||
{
|
||||
attachmentType = dt.AttachmentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attachmentType;
|
||||
}
|
||||
}
|
||||
|
||||
public IAttachmentTarget Target
|
||||
{
|
||||
get
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
switch (AttachmentType)
|
||||
{
|
||||
case AttachmentTypes.Device:
|
||||
target = database.Devices.Find(TargetId);
|
||||
break;
|
||||
case AttachmentTypes.Job:
|
||||
target = database.Jobs.Find(int.Parse(TargetId));
|
||||
break;
|
||||
case AttachmentTypes.User:
|
||||
target = database.Users.Find(ActiveDirectory.ParseDomainAccountId(TargetId));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unexpected Attachment Type", nameof(AttachmentType));
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
public User Creator
|
||||
{
|
||||
get
|
||||
{
|
||||
if (creator == null)
|
||||
{
|
||||
creator = database.Users.Find(ActiveDirectory.ParseDomainAccountId(CreatorId));
|
||||
}
|
||||
return creator;
|
||||
}
|
||||
}
|
||||
|
||||
private DocumentUniqueIdentifier(DiscoDataContext Database, int Version, short DeploymentChecksum, string DocumentTemplateId, string TargetId, string CreatorId, DateTime TimeStamp, int PageIndex, AttachmentTypes? AttachmentType)
|
||||
{
|
||||
this.database = Database;
|
||||
this.Version = Version;
|
||||
this.DeploymentChecksum = DeploymentChecksum;
|
||||
this.DocumentTemplateId = DocumentTemplateId;
|
||||
this.attachmentType = AttachmentType;
|
||||
this.TargetId = TargetId;
|
||||
this.CreatorId = ActiveDirectory.ParseDomainAccountId(CreatorId);
|
||||
this.TimeStamp = TimeStamp;
|
||||
this.PageIndex = PageIndex;
|
||||
}
|
||||
|
||||
public string ToQRCodeString()
|
||||
{
|
||||
return $"Disco|1|{DocumentTemplate.Id}|{TargetId}|{CreatorId}|{TimeStamp:s}|{PageIndex}";
|
||||
}
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
using (var stringWriter = new StringWriter(builder))
|
||||
{
|
||||
using (var writer = new JsonTextWriter(stringWriter))
|
||||
{
|
||||
writer.Formatting = Formatting.Indented;
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("version");
|
||||
writer.WriteValue(CurrentVersion);
|
||||
writer.WritePropertyName("attachmentType");
|
||||
writer.WriteValue(AttachmentType.Value.ToString());
|
||||
writer.WritePropertyName("documentTemplateId");
|
||||
writer.WriteValue(DocumentTemplateId);
|
||||
writer.WritePropertyName("targetId");
|
||||
writer.WriteValue(TargetId);
|
||||
writer.WritePropertyName("creatorId");
|
||||
writer.WriteValue(CreatorId);
|
||||
writer.WritePropertyName("timestamp");
|
||||
writer.WriteValue(TimeStamp);
|
||||
writer.WritePropertyName("pageIndex");
|
||||
writer.WriteValue(PageIndex);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public byte[] ToQRCodeBytes()
|
||||
{
|
||||
// Byte | Meaning
|
||||
// 0 | magic number = 0x0D
|
||||
// 1 | bits 0-3 = version; 4-7 = flags (1 = has document template id, 2 = device attachment,
|
||||
// | 4 = job attachment, 8 = user attachment)
|
||||
// 2-3 | deployment checksum (int16)
|
||||
// 4-7 | timestamp (uint32 unix epoch)
|
||||
// 8-9 | page index (uint16)
|
||||
// 10 | creator id length
|
||||
// 11-? | creator id (UTF8)
|
||||
// ? | target id length
|
||||
// ?-? | target id
|
||||
// ? | document template id length (optional based on flag)
|
||||
// ?-? | document template id (UTF8, optional based on flag)
|
||||
|
||||
var encoding = Encoding.UTF8;
|
||||
byte flags = 0;
|
||||
|
||||
switch (AttachmentType)
|
||||
{
|
||||
case AttachmentTypes.Device:
|
||||
flags = 2;
|
||||
break;
|
||||
case AttachmentTypes.Job:
|
||||
flags = 4;
|
||||
break;
|
||||
case AttachmentTypes.User:
|
||||
flags = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
var deploymentChecksumBytes = BitConverter.GetBytes(DeploymentChecksum);
|
||||
var timeStampEpochBytes = BitConverter.GetBytes((uint)(TimeStamp.ToUniversalTime().Subtract(DateTime.FromFileTimeUtc(116444736000000000L)).Ticks / TimeSpan.TicksPerSecond));
|
||||
var pageIndexBytes = BitConverter.GetBytes((ushort)PageIndex);
|
||||
|
||||
|
||||
// magic number (1) + version/flags (1) + deployment checksum (2) + timestamp (4) +
|
||||
// page index (2) + creator id length (1) + target id length (1)
|
||||
var requiredBytes = 12;
|
||||
var creatorIdLength = encoding.GetByteCount(CreatorId);
|
||||
var targetIdLength = encoding.GetByteCount(TargetId);
|
||||
var documentTemplateIdLength = 0;
|
||||
|
||||
if (DocumentTemplateId != null)
|
||||
{
|
||||
flags |= 1;
|
||||
requiredBytes++;
|
||||
documentTemplateIdLength = encoding.GetByteCount(DocumentTemplateId);
|
||||
}
|
||||
|
||||
int position = 0;
|
||||
var result = new byte[requiredBytes];
|
||||
|
||||
// magic number
|
||||
result[position++] = MagicNumber;
|
||||
// version & flags
|
||||
result[position++] = (byte)(2 | (flags << 4));
|
||||
// deployment checksum
|
||||
deploymentChecksumBytes.CopyTo(result, position);
|
||||
position += 2;
|
||||
// timestamp
|
||||
timeStampEpochBytes.CopyTo(result, position);
|
||||
position += 4;
|
||||
// page index
|
||||
pageIndexBytes.CopyTo(result, position);
|
||||
position += 2;
|
||||
// creator id length
|
||||
result[position++] = (byte)creatorIdLength;
|
||||
// creator id
|
||||
position += encoding.GetBytes(CreatorId, 0, CreatorId.Length, result, position);
|
||||
// target id length
|
||||
result[position++] = (byte)targetIdLength;
|
||||
// target id
|
||||
position += encoding.GetBytes(TargetId, 0, TargetId.Length, result, position);
|
||||
if (documentTemplateIdLength > 0)
|
||||
{
|
||||
// document template id length
|
||||
result[position++] = (byte)documentTemplateIdLength;
|
||||
// document template id
|
||||
position += encoding.GetBytes(DocumentTemplateId, 0, DocumentTemplateId.Length, result, position);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static DocumentUniqueIdentifier Create(DiscoDataContext Database, DocumentTemplate DocumentTemplate, IAttachmentTarget Target, User Creator, DateTime TimeStamp, int PageIndex)
|
||||
{
|
||||
var deploymentChecksum = Database.DiscoConfiguration.DeploymentChecksum;
|
||||
var documentTemplateId = DocumentTemplate.Id;
|
||||
var targetId = Target.AttachmentReferenceId;
|
||||
var creatorId = Creator.UserId;
|
||||
var attachmentType = DocumentTemplate.AttachmentType;
|
||||
|
||||
var identifier = new DocumentUniqueIdentifier(Database, CurrentVersion, deploymentChecksum, documentTemplateId, targetId, creatorId, TimeStamp, PageIndex, attachmentType);
|
||||
|
||||
identifier.documentTemplate = DocumentTemplate;
|
||||
identifier.target = Target;
|
||||
identifier.creator = Creator;
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public static DocumentUniqueIdentifier Create(DiscoDataContext Database, string DocumentTemplateId, string TargetId, string CreatorId, DateTime TimeStamp, int PageIndex)
|
||||
{
|
||||
var deploymentChecksum = Database.DiscoConfiguration.DeploymentChecksum;
|
||||
|
||||
return new DocumentUniqueIdentifier(Database, CurrentVersion, deploymentChecksum, DocumentTemplateId, TargetId, CreatorId, TimeStamp, PageIndex, null);
|
||||
}
|
||||
|
||||
public static DocumentUniqueIdentifier Parse(DiscoDataContext Database, byte[] UniqueIdentifier)
|
||||
{
|
||||
DocumentUniqueIdentifier identifier;
|
||||
if (TryParse(Database, UniqueIdentifier, out identifier))
|
||||
{
|
||||
return identifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException("Invalid Document Unique Identifier");
|
||||
}
|
||||
}
|
||||
|
||||
public static DocumentUniqueIdentifier Parse(DiscoDataContext Database, string UniqueIdentifier)
|
||||
{
|
||||
DocumentUniqueIdentifier identifier;
|
||||
if (TryParse(Database, UniqueIdentifier, out identifier))
|
||||
{
|
||||
return identifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException("Invalid Document Unique Identifier");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryParse(DiscoDataContext Database, string UniqueIdentifier, out DocumentUniqueIdentifier Identifier)
|
||||
{
|
||||
if (IsDocumentUniqueIdentifier(UniqueIdentifier))
|
||||
{
|
||||
var components = UniqueIdentifier.Split('|');
|
||||
|
||||
// Version 0, Version 1 Handling
|
||||
if ((components.Length == 7 || components.Length == 6) &&
|
||||
(components[1].Equals("1", StringComparison.Ordinal) || components[1].Equals("AT", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var documentTemplateId = components[2];
|
||||
var targetId = components[3];
|
||||
var creatorId = components[4];
|
||||
var timeStamp = DateTime.Parse(components[5]);
|
||||
var page = 0;
|
||||
if (components.Length == 7)
|
||||
{
|
||||
page = int.Parse(components[6]);
|
||||
}
|
||||
|
||||
Identifier = new DocumentUniqueIdentifier(Database, 1, -1, documentTemplateId, targetId, creatorId, timeStamp, page, null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Identifier = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryParse(DiscoDataContext Database, byte[] UniqueIdentifier, out DocumentUniqueIdentifier Identifier)
|
||||
{
|
||||
if (IsDocumentUniqueIdentifier(UniqueIdentifier))
|
||||
{
|
||||
// first 4 bit indicate version
|
||||
var version = UniqueIdentifier[2] & 0x0F;
|
||||
|
||||
// Version 2
|
||||
if (version == 2)
|
||||
{
|
||||
// Byte | Meaning
|
||||
// 0 | magic number = 0x0D
|
||||
// 1 | bits 0-3 = version; 4-7 = flags (1 = has document template id, 2 = device attachment,
|
||||
// | 4 = job attachment, 8 = user attachment)
|
||||
// 2-3 | deployment checksum (int16)
|
||||
// 4-7 | timestamp (uint32 unix epoch)
|
||||
// 8-9 | page index (uint16)
|
||||
// 10 | creator id length
|
||||
// 11-? | creator id (UTF8)
|
||||
// ? | target id length
|
||||
// ?-? | target id
|
||||
// ? | document template id length (optional based on flag)
|
||||
// ?-? | document template id (UTF8, optional based on flag)
|
||||
var encoding = Encoding.UTF8;
|
||||
var position = 2;
|
||||
|
||||
// next 4 bits are flags
|
||||
var flags = UniqueIdentifier[position++] >> 4;
|
||||
|
||||
var deploymentChecksum = BitConverter.ToInt16(UniqueIdentifier, position);
|
||||
position += 2;
|
||||
|
||||
var timeStampEpoch = BitConverter.ToUInt32(UniqueIdentifier, position);
|
||||
position += 4;
|
||||
|
||||
var pageIndex = BitConverter.ToUInt16(UniqueIdentifier, position);
|
||||
position += 2;
|
||||
|
||||
var creatorIdLength = UniqueIdentifier[position++];
|
||||
var creatorId = encoding.GetString(UniqueIdentifier, position, creatorIdLength);
|
||||
position += creatorIdLength;
|
||||
|
||||
var targetIdLength = UniqueIdentifier[position++];
|
||||
var targetId = encoding.GetString(UniqueIdentifier, position, targetIdLength);
|
||||
position += targetIdLength;
|
||||
|
||||
string documentTemplateId = null;
|
||||
|
||||
// Has document template id flag
|
||||
if ((flags & 1) == 1)
|
||||
{
|
||||
var documentTemplateIdLength = UniqueIdentifier[position++];
|
||||
documentTemplateId = encoding.GetString(UniqueIdentifier, position, documentTemplateIdLength);
|
||||
}
|
||||
|
||||
AttachmentTypes? attachmentType = null;
|
||||
if ((flags & 2) == 2)
|
||||
attachmentType = AttachmentTypes.Device;
|
||||
else if ((flags & 4) == 4)
|
||||
attachmentType = AttachmentTypes.Job;
|
||||
else if ((flags & 8) == 8)
|
||||
attachmentType = AttachmentTypes.User;
|
||||
|
||||
var timeStamp = DateTime.FromFileTimeUtc(116444736000000000L).AddTicks(TimeSpan.TicksPerSecond * timeStampEpoch).ToLocalTime();
|
||||
|
||||
Identifier = new DocumentUniqueIdentifier(Database, version, deploymentChecksum, documentTemplateId, targetId, creatorId, timeStamp, pageIndex, attachmentType);
|
||||
}
|
||||
}
|
||||
|
||||
Identifier = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsDocumentUniqueIdentifier(string Identifier)
|
||||
{
|
||||
return Identifier != null && Identifier.StartsWith("Disco|", System.StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static bool IsDocumentUniqueIdentifier(byte[] Identifier)
|
||||
{
|
||||
// Identifier[0] = 0xC4; Magic number to identify Disco ICT QR Codes
|
||||
return Identifier != null && Identifier.Length > 2 && Identifier[0] == MagicNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Documents;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class DocumentUniqueIdentifierExtensions
|
||||
{
|
||||
|
||||
public static DocumentUniqueIdentifier CreateUniqueIdentifier(this DocumentTemplate Template, DiscoDataContext Database, IAttachmentTarget Target, User Creator, DateTime Timestamp, int PageIndex)
|
||||
{
|
||||
return DocumentUniqueIdentifier.Create(Database, Template, Target, Creator, Timestamp, PageIndex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Logging;
|
||||
using Disco.Services.Logging.Models;
|
||||
|
||||
namespace Disco.Services.Documents
|
||||
{
|
||||
public class DocumentsLog : LogBase
|
||||
{
|
||||
|
||||
public enum EventTypeIds
|
||||
{
|
||||
ImportStarting = 10,
|
||||
ImportProgress,
|
||||
ImportFinished,
|
||||
ImportWarning = 15,
|
||||
ImportError,
|
||||
ImportAttachmentExpressionEvaluated = 50,
|
||||
ImportPageStarting = 100,
|
||||
ImportPageImageUpdate = 104,
|
||||
ImportPageProgress,
|
||||
ImportPageDetected = 110,
|
||||
ImportPageUndetected = 115,
|
||||
ImportPageError = 120,
|
||||
ImportPageUndetectedStored = 150,
|
||||
DocumentGenerated = 500,
|
||||
DocumentGeneratedWithExpression
|
||||
}
|
||||
|
||||
private const int _ModuleId = 40;
|
||||
|
||||
public static DocumentsLog Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return (DocumentsLog)LogContext.LogModules[_ModuleId];
|
||||
}
|
||||
}
|
||||
|
||||
public override string ModuleDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Documents";
|
||||
}
|
||||
}
|
||||
public override int ModuleId
|
||||
{
|
||||
get
|
||||
{
|
||||
return _ModuleId;
|
||||
}
|
||||
}
|
||||
public override string ModuleName
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Documents";
|
||||
}
|
||||
}
|
||||
private static void Log(DocumentsLog.EventTypeIds EventTypeId, params object[] Args)
|
||||
{
|
||||
DocumentsLog.Current.Log((int)EventTypeId, Args);
|
||||
}
|
||||
public static void LogImportStarting(string SessionId, string DocumentName)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportStarting, new object[]
|
||||
{
|
||||
SessionId,
|
||||
DocumentName
|
||||
});
|
||||
}
|
||||
public static void LogImportProgress(string SessionId, int? Progress, string Status)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportProgress, new object[]
|
||||
{
|
||||
SessionId,
|
||||
Progress,
|
||||
Status
|
||||
});
|
||||
}
|
||||
public static void LogImportFinished(string SessionId)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportFinished, new object[]
|
||||
{
|
||||
SessionId
|
||||
});
|
||||
}
|
||||
public static void LogImportWarning(string SessionId, string Message)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportWarning, new object[]
|
||||
{
|
||||
SessionId,
|
||||
Message
|
||||
});
|
||||
}
|
||||
public static void LogImportError(string SessionId, string Message)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportError, new object[]
|
||||
{
|
||||
SessionId,
|
||||
Message
|
||||
});
|
||||
}
|
||||
public static void LogImportAttachmentExpressionEvaluated(DocumentTemplate template, IAttachmentTarget target, IAttachment attachment, string Result)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportAttachmentExpressionEvaluated, new object[]
|
||||
{
|
||||
template.Id,
|
||||
target.AttachmentReferenceId,
|
||||
attachment.Id,
|
||||
Result
|
||||
});
|
||||
}
|
||||
public static void LogImportPageStarting(string SessionId, int PageNumber)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportPageStarting, new object[]
|
||||
{
|
||||
SessionId,
|
||||
PageNumber
|
||||
});
|
||||
}
|
||||
public static void LogImportPageImageUpdate(string SessionId, int PageNumber)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportPageImageUpdate, new object[]
|
||||
{
|
||||
SessionId,
|
||||
PageNumber
|
||||
});
|
||||
}
|
||||
public static void LogImportPageProgress(string SessionId, int PageNumber, int? Progress, string Status)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportPageProgress, new object[]
|
||||
{
|
||||
SessionId,
|
||||
PageNumber,
|
||||
Progress,
|
||||
Status
|
||||
});
|
||||
}
|
||||
public static void LogImportPageDetected(string SessionId, int PageNumber, string DocumentTypeId, string DocumentTypeName, string TargetType, string AssignedId, string AssignedName)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportPageDetected, new object[]
|
||||
{
|
||||
SessionId,
|
||||
PageNumber,
|
||||
DocumentTypeId,
|
||||
DocumentTypeName,
|
||||
TargetType,
|
||||
AssignedId,
|
||||
AssignedName
|
||||
});
|
||||
}
|
||||
public static void LogImportPageUndetected(string SessionId, int PageNumber)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportPageUndetected, new object[]
|
||||
{
|
||||
SessionId,
|
||||
PageNumber
|
||||
});
|
||||
}
|
||||
public static void LogImportPageError(string SessionId, int PageNumber, string Message)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportPageError, new object[]
|
||||
{
|
||||
SessionId,
|
||||
PageNumber,
|
||||
Message
|
||||
});
|
||||
}
|
||||
public static void LogImportPageUndetectedStored(string SessionId, int PageNumber)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.ImportPageUndetectedStored, new object[]
|
||||
{
|
||||
SessionId,
|
||||
PageNumber
|
||||
});
|
||||
}
|
||||
public static void LogDocumentGenerated(DocumentTemplate Template, Device Device, User Author, string ExpressionResult)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.DocumentGeneratedWithExpression, new object[]
|
||||
{
|
||||
Template.Id,
|
||||
Device.SerialNumber,
|
||||
Author.UserId,
|
||||
ExpressionResult
|
||||
});
|
||||
}
|
||||
public static void LogDocumentGenerated(DocumentTemplate Template, Job Job, User Author, string ExpressionResult)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.DocumentGeneratedWithExpression, new object[]
|
||||
{
|
||||
Template.Id,
|
||||
Job.Id,
|
||||
Author.UserId,
|
||||
ExpressionResult
|
||||
});
|
||||
}
|
||||
public static void LogDocumentGenerated(DocumentTemplate Template, User User, User Author, string ExpressionResult)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.DocumentGeneratedWithExpression, new object[]
|
||||
{
|
||||
Template.Id,
|
||||
User.UserId,
|
||||
Author.UserId,
|
||||
ExpressionResult
|
||||
});
|
||||
}
|
||||
public static void LogDocumentGenerated(DocumentTemplate Template, IAttachmentTarget Data, User Author, string ExpressionResult)
|
||||
{
|
||||
if (Data is Job)
|
||||
LogDocumentGenerated(Template, (Job)Data, Author, ExpressionResult);
|
||||
else if (Data is User)
|
||||
LogDocumentGenerated(Template, (User)Data, Author, ExpressionResult);
|
||||
else if (Data is Device)
|
||||
LogDocumentGenerated(Template, (Device)Data, Author, ExpressionResult);
|
||||
else
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.DocumentGeneratedWithExpression, new object[]
|
||||
{
|
||||
Template.Id,
|
||||
"UNKNOWN",
|
||||
Author.UserId,
|
||||
ExpressionResult
|
||||
});
|
||||
}
|
||||
public static void LogDocumentGenerated(DocumentTemplate Template, Device Device, User Author)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.DocumentGenerated, new object[]
|
||||
{
|
||||
Template.Id,
|
||||
Device.SerialNumber,
|
||||
Author.UserId
|
||||
});
|
||||
}
|
||||
public static void LogDocumentGenerated(DocumentTemplate Template, Job Job, User Author)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.DocumentGenerated, new object[]
|
||||
{
|
||||
Template.Id,
|
||||
Job.Id,
|
||||
Author.UserId
|
||||
});
|
||||
}
|
||||
public static void LogDocumentGenerated(DocumentTemplate Template, User User, User Author)
|
||||
{
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.DocumentGenerated, new object[]
|
||||
{
|
||||
Template.Id,
|
||||
User.UserId,
|
||||
Author.UserId
|
||||
});
|
||||
}
|
||||
public static void LogDocumentGenerated(DocumentTemplate Template, object Data, User Author)
|
||||
{
|
||||
if (Data is Job)
|
||||
LogDocumentGenerated(Template, (Job)Data, Author);
|
||||
else if (Data is User)
|
||||
LogDocumentGenerated(Template, (User)Data, Author);
|
||||
else if (Data is Device)
|
||||
LogDocumentGenerated(Template, (Device)Data, Author);
|
||||
else
|
||||
DocumentsLog.Log(DocumentsLog.EventTypeIds.DocumentGenerated, new object[]
|
||||
{
|
||||
Template.Id,
|
||||
"UNKNOWN",
|
||||
Author.UserId
|
||||
});
|
||||
}
|
||||
protected override System.Collections.Generic.List<LogEventType> LoadEventTypes()
|
||||
{
|
||||
return new System.Collections.Generic.List<LogEventType>
|
||||
{
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportStarting,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Starting",
|
||||
Format = "Starting import of document: {1} (SessionId: {0})",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportProgress,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Progress",
|
||||
Format = "Processing: {1}% Complete; Status: {2}",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = false,
|
||||
UseDisplay = false
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportFinished,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Finished",
|
||||
Format = "Import of document complete (SessionId: {0})",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportWarning,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Warning",
|
||||
Format = "Import Warning: {1} (SessionId: {0})",
|
||||
Severity = (int)LogEventType.Severities.Warning,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportError,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Error",
|
||||
Format = "Import Error: {1} (SessionId: {0})",
|
||||
Severity = (int)LogEventType.Severities.Error,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportAttachmentExpressionEvaluated,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Attachment Expression Evaluated",
|
||||
Format = "The import attachment expression for '{0}' was evaluated for '{1}' (attachment id: {2}) with the result: {3}",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportPageStarting,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Page Starting",
|
||||
Format = "Starting import of page: {1} (SessionId: {0})",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportPageImageUpdate,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Page Image Update",
|
||||
Format = null,
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = false,
|
||||
UseDisplay = false
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportPageProgress,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Page Progress",
|
||||
Format = "Processing: Page {1}; {2}% Complete; Status: {3}",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = false,
|
||||
UseDisplay = false
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportPageDetected,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Page Assigned",
|
||||
Format = "Page {1} of type '{3}' assigned to {4}: '{6}'",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportPageUndetected,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Page Undetected",
|
||||
Format = "Page {1} not detected",
|
||||
Severity = (int)LogEventType.Severities.Warning,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportPageError,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Page Error",
|
||||
Format = "Page {1}, Import Error: {2}",
|
||||
Severity = (int)LogEventType.Severities.Error,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.ImportPageUndetectedStored,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Import Page Undetected Stored",
|
||||
Format = null,
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = false,
|
||||
UseDisplay = false
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.DocumentGenerated,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Document Generated",
|
||||
Format = "A '{0}' document was generated for '{1}' by '{2}'",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.DocumentGeneratedWithExpression,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Document Generated with Expression",
|
||||
Format = "A '{0}' document was generated for '{1}' by '{2}'. The expression returned: {3}",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Spring.Expressions.Parser.antlr;
|
||||
|
||||
namespace Disco.Services.Expressions
|
||||
{
|
||||
public class EvaluateExpressionParseException
|
||||
{
|
||||
public string Expression { get; set; }
|
||||
public int PositionRow { get; set; }
|
||||
public int PositionColumn { get; set; }
|
||||
public string Message { get; set; }
|
||||
|
||||
internal static EvaluateExpressionParseException FromRecognitionException(RecognitionException e, string Expression)
|
||||
{
|
||||
return new EvaluateExpressionParseException()
|
||||
{
|
||||
Expression = Expression,
|
||||
Message = e.Message,
|
||||
PositionRow = e.getLine(),
|
||||
PositionColumn = e.getColumn()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Spring.Expressions.Parser.antlr;
|
||||
using System.Collections;
|
||||
|
||||
namespace Disco.Services.Expressions
|
||||
{
|
||||
public class EvaluateExpressionPart : IExpressionPart
|
||||
{
|
||||
private Spring.Expressions.IExpression _Expression;
|
||||
private RecognitionException _ExpressionParseException;
|
||||
private EvaluateExpressionParseException _EvaluateParseException;
|
||||
|
||||
public string RawSource { get; set; }
|
||||
public string Source { get; set; }
|
||||
public bool ErrorsAllowed { get; set; }
|
||||
public bool IsDynamic { get { return true; } set { return; } }
|
||||
|
||||
public EvaluateExpressionParseException ParseException
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_ExpressionParseException == null)
|
||||
return null;
|
||||
else
|
||||
if (_EvaluateParseException == null)
|
||||
_EvaluateParseException = EvaluateExpressionParseException.FromRecognitionException(_ExpressionParseException, this.Source);
|
||||
return _EvaluateParseException;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ParseError
|
||||
{
|
||||
get { return (_ExpressionParseException != null); }
|
||||
}
|
||||
public string ParseErrorMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ParseError)
|
||||
return ParseException.Message;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public EvaluateExpressionPart(string Source)
|
||||
{
|
||||
this.RawSource = Source;
|
||||
|
||||
if (Source.StartsWith("{") && Source.EndsWith("}"))
|
||||
Source = Source.Substring(1, Source.Length - 2);
|
||||
|
||||
if (Source[0] == '~')
|
||||
{
|
||||
this.ErrorsAllowed = true;
|
||||
this.Source = Source.Substring(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ErrorsAllowed = false;
|
||||
this.Source = Source;
|
||||
}
|
||||
try
|
||||
{
|
||||
this._Expression = Spring.Expressions.Expression.Parse(this.Source);
|
||||
|
||||
}
|
||||
catch (RecognitionException ex)
|
||||
{
|
||||
this._ExpressionParseException = ex;
|
||||
}
|
||||
}
|
||||
object IExpressionPart.Evaluate(object ExpressionContext, IDictionary Variables)
|
||||
{
|
||||
if (this._ExpressionParseException == null)
|
||||
{
|
||||
return this._Expression.GetValue(ExpressionContext, Variables);
|
||||
}
|
||||
throw this._ExpressionParseException;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.BI.Expressions;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Documents;
|
||||
using Spring.Core.TypeResolution;
|
||||
using Spring.Expressions;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Disco.Services.Expressions
|
||||
{
|
||||
public sealed class Expression : List<IExpressionPart>
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public string Source { get; private set; }
|
||||
public bool IsDynamic { get; private set; }
|
||||
public int Ordinal { get; private set; }
|
||||
|
||||
private Expression(string Name, string Source, int Ordinal)
|
||||
{
|
||||
this.Name = Name;
|
||||
this.Source = Source;
|
||||
this.Ordinal = Ordinal;
|
||||
}
|
||||
|
||||
public static void InitializeExpressions()
|
||||
{
|
||||
TypeRegistry.RegisterType("DataExt", typeof(Extensions.DataExt));
|
||||
TypeRegistry.RegisterType("UserExt", typeof(Extensions.UserExt));
|
||||
TypeRegistry.RegisterType("DeviceExt", typeof(Extensions.DeviceExt));
|
||||
TypeRegistry.RegisterType("ImageExt", typeof(Extensions.ImageExt));
|
||||
}
|
||||
|
||||
public T EvaluateFirst<T>(object ExpressionContext, IDictionary Variables)
|
||||
{
|
||||
T result = default(T);
|
||||
if (this.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
object expressionResult = this[0].Evaluate(ExpressionContext, Variables);
|
||||
if (expressionResult != null)
|
||||
{
|
||||
if (expressionResult is T)
|
||||
{
|
||||
result = (T)expressionResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Expression returned an invalid type");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Expression evaluation resulted in an error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static IExpression Parse(string source)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Tuple<string, bool, object> Evaluate(object ExpressionContext, IDictionary Variables)
|
||||
{
|
||||
var resultValue = new StringBuilder();
|
||||
object resultObject = null;
|
||||
bool resultError = false;
|
||||
foreach (var expressionPart in this)
|
||||
{
|
||||
try
|
||||
{
|
||||
object partValue = expressionPart.Evaluate(ExpressionContext, Variables);
|
||||
if (partValue != null)
|
||||
{
|
||||
// Check for Result Objects
|
||||
if (partValue is IImageExpressionResult)
|
||||
resultObject = partValue;
|
||||
else
|
||||
resultValue.Append(partValue.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!expressionPart.ErrorsAllowed)
|
||||
{
|
||||
resultValue.Append("## ERROR # ");
|
||||
resultValue.Append(ex.Message);
|
||||
resultValue.Append(" ##");
|
||||
resultError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Tuple<string, bool, object>(resultValue.ToString(), resultError, resultObject);
|
||||
}
|
||||
public static Expression TokenizeSingleDynamic(string Name, string ExpressionSource, int Ordinal)
|
||||
{
|
||||
var e = new Expression(Name, ExpressionSource, Ordinal);
|
||||
if (ExpressionSource != null && !string.IsNullOrWhiteSpace(ExpressionSource))
|
||||
e.Add(new EvaluateExpressionPart(ExpressionSource));
|
||||
e.IsDynamic = true;
|
||||
return e;
|
||||
}
|
||||
public static Expression Tokenize(string Name, string ExpressionSource, int Ordinal)
|
||||
{
|
||||
var e = new Expression(Name, ExpressionSource, Ordinal);
|
||||
if (!ExpressionSource.Contains("{") || !ExpressionSource.Contains("}"))
|
||||
{
|
||||
e.Add(new TextExpressionPart(ExpressionSource));
|
||||
}
|
||||
else
|
||||
{
|
||||
var token = new StringBuilder();
|
||||
bool tokenEval = false;
|
||||
int tokenEvalDepth = 0;
|
||||
foreach (char c in ExpressionSource)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '{':
|
||||
{
|
||||
if (!tokenEval)
|
||||
{
|
||||
if (token.Length > 0)
|
||||
{
|
||||
e.Add(new TextExpressionPart(token.ToString()));
|
||||
token = new StringBuilder();
|
||||
}
|
||||
tokenEval = true;
|
||||
tokenEvalDepth = 0;
|
||||
}
|
||||
tokenEvalDepth++;
|
||||
token.Append(c);
|
||||
break;
|
||||
}
|
||||
case '}':
|
||||
{
|
||||
token.Append(c);
|
||||
if (tokenEval)
|
||||
{
|
||||
tokenEvalDepth--;
|
||||
if (tokenEvalDepth <= 0)
|
||||
{
|
||||
if (token.Length != 2 && (token.Length != 3 || token[1] != '@'))
|
||||
{
|
||||
e.Add(new EvaluateExpressionPart(token.ToString()));
|
||||
e.IsDynamic = true;
|
||||
token = new StringBuilder();
|
||||
}
|
||||
tokenEval = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
token.Append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (token.Length > 0)
|
||||
{
|
||||
e.Add(new TextExpressionPart(token.ToString()));
|
||||
}
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
public static IDictionary StandardVariables(DocumentTemplate AttachmentType, DiscoDataContext Database, User User, System.DateTime TimeStamp, DocumentState DocumentState)
|
||||
{
|
||||
return new Hashtable
|
||||
{
|
||||
|
||||
{
|
||||
"DataContext",
|
||||
Database
|
||||
},
|
||||
|
||||
{
|
||||
"User",
|
||||
User
|
||||
},
|
||||
|
||||
{
|
||||
"TimeStamp",
|
||||
TimeStamp
|
||||
},
|
||||
|
||||
{
|
||||
"AttachmentType",
|
||||
AttachmentType
|
||||
},
|
||||
|
||||
{
|
||||
"State",
|
||||
DocumentState
|
||||
}
|
||||
};
|
||||
}
|
||||
public static Dictionary<string, string> StandardVariableTypes()
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
|
||||
{
|
||||
"#DataContext",
|
||||
typeof(DiscoDataContext).AssemblyQualifiedName
|
||||
},
|
||||
|
||||
{
|
||||
"#User",
|
||||
typeof(User).AssemblyQualifiedName
|
||||
},
|
||||
|
||||
{
|
||||
"#TimeStamp",
|
||||
typeof(System.DateTime).AssemblyQualifiedName
|
||||
},
|
||||
|
||||
{
|
||||
"#AttachmentType",
|
||||
typeof(DocumentTemplate).AssemblyQualifiedName
|
||||
},
|
||||
|
||||
{
|
||||
"#State",
|
||||
typeof(DocumentState).AssemblyQualifiedName
|
||||
}
|
||||
};
|
||||
}
|
||||
public static Dictionary<string, string> ExtensionLibraryTypes()
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{
|
||||
"DataExt",
|
||||
typeof(Extensions.DataExt).AssemblyQualifiedName
|
||||
},
|
||||
|
||||
{
|
||||
"DeviceExt",
|
||||
typeof(Extensions.DeviceExt).AssemblyQualifiedName
|
||||
},
|
||||
|
||||
{
|
||||
"ImageExt",
|
||||
typeof(Extensions.ImageExt).AssemblyQualifiedName
|
||||
},
|
||||
|
||||
{
|
||||
"UserExt",
|
||||
typeof(Extensions.UserExt).AssemblyQualifiedName
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Disco.Services.Expressions
|
||||
{
|
||||
public static class ExpressionCache
|
||||
{
|
||||
private static ConcurrentDictionary<string, ConcurrentDictionary<string, Expression>> _Cache = new ConcurrentDictionary<string, ConcurrentDictionary<string, Expression>>();
|
||||
|
||||
public delegate Expression CreateValueDelegate();
|
||||
|
||||
public static ConcurrentDictionary<string, Expression> GetModule(string Module, bool Create = false)
|
||||
{
|
||||
ConcurrentDictionary<string, Expression> moduleCache;
|
||||
if (_Cache.TryGetValue(Module, out moduleCache))
|
||||
return moduleCache;
|
||||
else
|
||||
{
|
||||
if (Create)
|
||||
{
|
||||
moduleCache = new ConcurrentDictionary<string, Expression>();
|
||||
_Cache.TryAdd(Module, moduleCache);
|
||||
return moduleCache;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private static Expression GetModuleValue(string Module, string Key, CreateValueDelegate CreateValue)
|
||||
{
|
||||
ConcurrentDictionary<string, Expression> moduleCache = GetModule(Module, (CreateValue != null));
|
||||
if (moduleCache != null)
|
||||
{
|
||||
Expression expression;
|
||||
if (moduleCache.TryGetValue(Key, out expression))
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
if (CreateValue != null)
|
||||
{
|
||||
expression = CreateValue();
|
||||
Expression oldExpression;
|
||||
if (moduleCache.TryGetValue(Key, out oldExpression))
|
||||
moduleCache.TryUpdate(Key, expression, oldExpression);
|
||||
else
|
||||
moduleCache.TryAdd(Key, expression);
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Expression GetValue(string Module, string Key, CreateValueDelegate CreateValue)
|
||||
{
|
||||
return GetModuleValue(Module, Key, CreateValue);
|
||||
}
|
||||
|
||||
public static Expression GetValue(string Module, string Key)
|
||||
{
|
||||
return GetModuleValue(Module, Key, null);
|
||||
}
|
||||
|
||||
public static bool InvalidModule(string Module)
|
||||
{
|
||||
ConcurrentDictionary<string, Expression> moduleCache;
|
||||
return _Cache.TryRemove(Module, out moduleCache);
|
||||
}
|
||||
|
||||
public static bool InvalidateKey(string Module, string Key)
|
||||
{
|
||||
Expression expression;
|
||||
ConcurrentDictionary<string, Expression> moduleCache = GetModule(Module, false);
|
||||
if (moduleCache != null)
|
||||
{
|
||||
bool removeResult = moduleCache.TryRemove(Key, out expression);
|
||||
if (moduleCache.Count == 0)
|
||||
InvalidModule(Module);
|
||||
return removeResult;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SetValue(string Module, string Key, Expression Expression)
|
||||
{
|
||||
ConcurrentDictionary<string, Expression> moduleCache = GetModule(Module, true);
|
||||
|
||||
if (moduleCache.ContainsKey(Key))
|
||||
{
|
||||
Expression oldExpression;
|
||||
if (moduleCache.TryGetValue(Key, out oldExpression))
|
||||
{
|
||||
return moduleCache.TryUpdate(Key, Expression, oldExpression);
|
||||
}
|
||||
}
|
||||
return moduleCache.TryAdd(Key, Expression);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Services.Tasks;
|
||||
using Quartz;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services.Expressions
|
||||
{
|
||||
public class ExpressionCachePreloadTask : ScheduledTask
|
||||
{
|
||||
|
||||
public override string TaskName { get { return "Expression Cache - Preload 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)
|
||||
{
|
||||
// Run in Background 5 Second after Scheduled (on App Startup)
|
||||
TriggerBuilder triggerBuilder = TriggerBuilder.Create().StartAt(new DateTimeOffset(DateTime.Now).AddSeconds(5));
|
||||
|
||||
this.ScheduleTask(triggerBuilder);
|
||||
}
|
||||
|
||||
protected override void ExecuteTask()
|
||||
{
|
||||
// Cache Document Template Filter Expressions
|
||||
using (DiscoDataContext database = new DiscoDataContext())
|
||||
{
|
||||
foreach (var documentTemplate in database.DocumentTemplates.Where(dt => dt.FilterExpression != null && dt.FilterExpression != string.Empty))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(documentTemplate.FilterExpression))
|
||||
documentTemplate.FilterExpressionFromCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Disco.Services.Expressions
|
||||
{
|
||||
public class ExpressionTypeDescriptor
|
||||
{
|
||||
public string ExpressionType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public List<ExpressionTypeMemberDescriptor> Members { get; set; }
|
||||
|
||||
public static ExpressionTypeDescriptor Build(Type t, bool StaticDeclaredMembersOnly = true)
|
||||
{
|
||||
ExpressionTypeDescriptor i = new ExpressionTypeDescriptor
|
||||
{
|
||||
ExpressionType = t.AssemblyQualifiedName,
|
||||
Name = t.Name
|
||||
};
|
||||
i.Members = new List<ExpressionTypeMemberDescriptor>();
|
||||
|
||||
MemberInfo[] members;
|
||||
if (StaticDeclaredMembersOnly)
|
||||
members = t.GetMembers(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
else
|
||||
members = t.GetMembers(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
for (int j = 0; j < members.Length; j++)
|
||||
{
|
||||
MemberInfo member = members[j];
|
||||
if (member is PropertyInfo)
|
||||
{
|
||||
PropertyInfo pi = (PropertyInfo)member;
|
||||
if (!pi.IsSpecialName && pi.CanRead)
|
||||
{
|
||||
i.Members.Add(ExpressionTypeMemberDescriptor.Build(pi));
|
||||
}
|
||||
}
|
||||
if (member is MethodInfo)
|
||||
{
|
||||
MethodInfo mi2 = (MethodInfo)member;
|
||||
if (!mi2.IsSpecialName)
|
||||
{
|
||||
i.Members.Add(ExpressionTypeMemberDescriptor.Build(mi2));
|
||||
}
|
||||
}
|
||||
}
|
||||
i.Members = (
|
||||
from mi in i.Members
|
||||
orderby mi.Name
|
||||
select mi).ToList();
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Disco.Services.Expressions
|
||||
{
|
||||
public class ExpressionTypeMemberDescriptor
|
||||
{
|
||||
public const string FunctionKind = "function";
|
||||
public const string PropertyKind = "property";
|
||||
public const string ParameterKind = "parameter";
|
||||
|
||||
public string Kind { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string ReturnType { get; set; }
|
||||
public string ReturnExpressionType { get; set; }
|
||||
public List<ExpressionTypeMemberDescriptor> Parameters { get; set; }
|
||||
|
||||
public static ExpressionTypeMemberDescriptor Build(MethodInfo m)
|
||||
{
|
||||
ExpressionTypeMemberDescriptor md = new ExpressionTypeMemberDescriptor
|
||||
{
|
||||
Kind = "function",
|
||||
Name = m.Name,
|
||||
ReturnType = m.ReturnType.Name,
|
||||
ReturnExpressionType = m.ReturnType.AssemblyQualifiedName
|
||||
};
|
||||
md.Parameters = (
|
||||
from mdp in m.GetParameters()
|
||||
select Build(mdp)).ToList();
|
||||
return md;
|
||||
}
|
||||
public static ExpressionTypeMemberDescriptor Build(PropertyInfo p)
|
||||
{
|
||||
ExpressionTypeMemberDescriptor md = new ExpressionTypeMemberDescriptor
|
||||
{
|
||||
Kind = "property",
|
||||
Name = p.Name,
|
||||
ReturnType = p.PropertyType.Name,
|
||||
ReturnExpressionType = p.PropertyType.AssemblyQualifiedName
|
||||
};
|
||||
md.Parameters = (
|
||||
from mdp in p.GetIndexParameters()
|
||||
select Build(mdp)).ToList();
|
||||
return md;
|
||||
}
|
||||
public static ExpressionTypeMemberDescriptor Build(ParameterInfo pi)
|
||||
{
|
||||
return new ExpressionTypeMemberDescriptor
|
||||
{
|
||||
Kind = "parameter",
|
||||
Name = pi.Name,
|
||||
ReturnType = pi.ParameterType.Name,
|
||||
ReturnExpressionType = pi.ParameterType.AssemblyQualifiedName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Data;
|
||||
using System.Data.Odbc;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
namespace Disco.Services.Expressions.Extensions
|
||||
{
|
||||
public static class DataExt
|
||||
{
|
||||
#region SqlClient
|
||||
|
||||
private static SqlConnection BuildSqlConnection(string Server, string Database, string Username, string Password)
|
||||
{
|
||||
var dbConnectionStringBuilder = new SqlConnectionStringBuilder();
|
||||
dbConnectionStringBuilder.ApplicationName = "Disco";
|
||||
dbConnectionStringBuilder.DataSource = Server;
|
||||
dbConnectionStringBuilder.InitialCatalog = Database;
|
||||
dbConnectionStringBuilder.MultipleActiveResultSets = true;
|
||||
dbConnectionStringBuilder.PersistSecurityInfo = true;
|
||||
if (Username == null || Password == null)
|
||||
dbConnectionStringBuilder.IntegratedSecurity = true;
|
||||
else
|
||||
{
|
||||
dbConnectionStringBuilder.UserID = Username;
|
||||
dbConnectionStringBuilder.Password = Password;
|
||||
}
|
||||
|
||||
return new SqlConnection(dbConnectionStringBuilder.ConnectionString);
|
||||
}
|
||||
private static void BuildSqlParameters(SqlCommand dbCommand, Hashtable SqlParameters)
|
||||
{
|
||||
if (SqlParameters != null)
|
||||
{
|
||||
foreach (var sqlParameterKey in SqlParameters.Keys)
|
||||
{
|
||||
string key = sqlParameterKey.ToString();
|
||||
if (!key.StartsWith("@"))
|
||||
key = string.Concat("@", key);
|
||||
dbCommand.Parameters.AddWithValue(key, SqlParameters[sqlParameterKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static DataTable QuerySqlDatabase(string Server, string Database, string Username, string Password, string SqlQuery, Hashtable SqlParameters)
|
||||
{
|
||||
using (SqlConnection dbConnection = BuildSqlConnection(Server, Database, Username, Password))
|
||||
{
|
||||
using (SqlCommand dbCommand = new SqlCommand(SqlQuery, dbConnection))
|
||||
{
|
||||
BuildSqlParameters(dbCommand, SqlParameters);
|
||||
using (SqlDataAdapter dbAdapter = new SqlDataAdapter(dbCommand))
|
||||
{
|
||||
var dbTable = new DataTable();
|
||||
dbAdapter.Fill(dbTable);
|
||||
return dbTable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static DataTable QuerySqlDatabase(string Server, string Database, string SqlQuery, Hashtable SqlParameters)
|
||||
{
|
||||
return QuerySqlDatabase(Server, Database, null, null, SqlQuery, SqlParameters);
|
||||
}
|
||||
public static DataTable QuerySqlDatabase(string Server, string Database, string SqlQuery)
|
||||
{
|
||||
return QuerySqlDatabase(Server, Database, null, null, SqlQuery, null);
|
||||
}
|
||||
|
||||
public static object QuerySqlDatabaseScalar(string Server, string Database, string Username, string Password, string SqlQuery, Hashtable SqlParameters)
|
||||
{
|
||||
using (SqlConnection dbConnection = BuildSqlConnection(Server, Database, Username, Password))
|
||||
{
|
||||
using (SqlCommand dbCommand = new SqlCommand(SqlQuery, dbConnection))
|
||||
{
|
||||
BuildSqlParameters(dbCommand, SqlParameters);
|
||||
try
|
||||
{
|
||||
dbConnection.Open();
|
||||
return dbCommand.ExecuteScalar();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbConnection.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static object QuerySqlDatabaseScalar(string Server, string Database, string SqlQuery, Hashtable SqlParameters)
|
||||
{
|
||||
return QuerySqlDatabaseScalar(Server, Database, null, null, SqlQuery, SqlParameters);
|
||||
}
|
||||
public static object QuerySqlDatabaseScalar(string Server, string Database, string SqlQuery)
|
||||
{
|
||||
return QuerySqlDatabaseScalar(Server, Database, null, null, SqlQuery, null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ODBC
|
||||
|
||||
private static OdbcConnection BuildOdbcConnection(string ConnectionString)
|
||||
{
|
||||
return new OdbcConnection(ConnectionString);
|
||||
}
|
||||
private static void BuildOdbcParameters(OdbcCommand dbCommand, Hashtable OdbcParameters)
|
||||
{
|
||||
if (OdbcParameters != null)
|
||||
{
|
||||
foreach (var odbcParameterKey in OdbcParameters.Keys)
|
||||
{
|
||||
string key = odbcParameterKey.ToString();
|
||||
dbCommand.Parameters.AddWithValue(key, OdbcParameters[odbcParameterKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static DataTable QueryOdbcDatabase(string ConnectionString, string OdbcQuery, Hashtable OdbcParameters)
|
||||
{
|
||||
using (OdbcConnection dbConnection = BuildOdbcConnection(ConnectionString))
|
||||
{
|
||||
using (OdbcCommand dbCommand = new OdbcCommand(OdbcQuery, dbConnection))
|
||||
{
|
||||
BuildOdbcParameters(dbCommand, OdbcParameters);
|
||||
using (OdbcDataAdapter dbAdapter = new OdbcDataAdapter(dbCommand))
|
||||
{
|
||||
var dbTable = new DataTable();
|
||||
dbAdapter.Fill(dbTable);
|
||||
return dbTable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static DataTable QueryOdbcDatabase(string ConnectionString, string OdbcQuery)
|
||||
{
|
||||
return QueryOdbcDatabase(ConnectionString, OdbcQuery, null);
|
||||
}
|
||||
|
||||
public static object QueryOdbcDatabaseScalar(string ConnectionString, string OdbcQuery, Hashtable OdbcParameters)
|
||||
{
|
||||
using (OdbcConnection dbConnection = BuildOdbcConnection(ConnectionString))
|
||||
{
|
||||
using (OdbcCommand dbCommand = new OdbcCommand(OdbcQuery, dbConnection))
|
||||
{
|
||||
BuildOdbcParameters(dbCommand, OdbcParameters);
|
||||
try
|
||||
{
|
||||
dbConnection.Open();
|
||||
return dbCommand.ExecuteScalar();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbConnection.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static object QueryOdbcDatabaseScalar(string ConnectionString, string OdbcQuery)
|
||||
{
|
||||
return QueryOdbcDatabaseScalar(ConnectionString, OdbcQuery, null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Disco.Models.Repository;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services.Expressions.Extensions
|
||||
{
|
||||
public static class DeviceExt
|
||||
{
|
||||
public static object GetActiveDirectoryObjectValue(Device Device, string PropertyName, int Index = 0)
|
||||
{
|
||||
var adMachineAccount = Device.ActiveDirectoryAccount(PropertyName);
|
||||
if (adMachineAccount != null)
|
||||
return adMachineAccount.GetPropertyValues<object>(PropertyName).Skip(Index).FirstOrDefault();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string GetActiveDirectoryStringValue(Device Device, string PropertyName, int Index = 0)
|
||||
{
|
||||
var objectValue = GetActiveDirectoryObjectValue(Device, PropertyName, Index);
|
||||
string stringValue = objectValue as string;
|
||||
if (stringValue == null && objectValue != null)
|
||||
stringValue = objectValue.ToString();
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
public static int GetActiveDirectoryIntegerValue(Device Device, string PropertyName, int Index = 0)
|
||||
{
|
||||
var objectValue = GetActiveDirectoryObjectValue(Device, PropertyName, Index);
|
||||
if (objectValue == null)
|
||||
return default(int);
|
||||
else
|
||||
{
|
||||
int intValue;
|
||||
try
|
||||
{
|
||||
intValue = (int)Convert.ChangeType(objectValue, typeof(int));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
return intValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Expressions.Extensions.ImageResultImplementations;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services.Expressions.Extensions
|
||||
{
|
||||
public static class ImageExt
|
||||
{
|
||||
public static FileImageExpressionResult ImageFromFile(string AbsoluteFilePath)
|
||||
{
|
||||
return new FileImageExpressionResult(AbsoluteFilePath);
|
||||
}
|
||||
public static FileImageExpressionResult ImageFromDataStoreFile(string RelativeFilePath)
|
||||
{
|
||||
var configCache = new Disco.Data.Configuration.SystemConfiguration(null);
|
||||
string DataStoreLocation = configCache.DataStoreLocation;
|
||||
string AbsoluteFilePath = System.IO.Path.Combine(DataStoreLocation, RelativeFilePath);
|
||||
return new FileImageExpressionResult(AbsoluteFilePath);
|
||||
}
|
||||
public static FileImageExpressionResult JobAttachmentFirstImage(Job Job, DiscoDataContext Database)
|
||||
{
|
||||
var attachment = Job.JobAttachments.FirstOrDefault(ja => ja.MimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase));
|
||||
if (attachment != null)
|
||||
{
|
||||
var filename = attachment.RepositoryFilename(Database);
|
||||
return new FileImageExpressionResult(filename);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public static FileImageExpressionResult JobAttachmentLastImage(Job Job, DiscoDataContext Database)
|
||||
{
|
||||
var attachment = Job.JobAttachments.LastOrDefault(ja => ja.MimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase));
|
||||
if (attachment != null)
|
||||
{
|
||||
var filename = attachment.RepositoryFilename(Database);
|
||||
return new FileImageExpressionResult(filename);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public static FileImageExpressionResult JobAttachmentImage(JobAttachment JobAttachment, DiscoDataContext Database)
|
||||
{
|
||||
if (JobAttachment == null)
|
||||
throw new ArgumentNullException("JobAttachment");
|
||||
if (!JobAttachment.MimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
|
||||
throw new ArgumentException("Invalid Image MimeType for Attachment");
|
||||
|
||||
var filename = JobAttachment.RepositoryFilename(Database);
|
||||
return new FileImageExpressionResult(filename);
|
||||
}
|
||||
public static FileMontageImageExpressionResult JobAttachmentImageMontage(Job Job, DiscoDataContext Database)
|
||||
{
|
||||
if (Job == null)
|
||||
throw new ArgumentNullException("Job");
|
||||
if (Job.JobAttachments == null)
|
||||
throw new ArgumentException("Job.JobAttachments is null", "Job");
|
||||
|
||||
var attachments = Job.JobAttachments.Where(a => a.MimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
|
||||
if (attachments.Count > 0)
|
||||
{
|
||||
var attachmentFilepaths = attachments.Select(a => a.RepositoryFilename(Database)).ToList();
|
||||
|
||||
return new FileMontageImageExpressionResult(attachmentFilepaths);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public static FileMontageImageExpressionResult JobAttachmentsImageMontage(ArrayList JobAttachments, DiscoDataContext Database)
|
||||
{
|
||||
if (JobAttachments == null)
|
||||
throw new ArgumentNullException("JobAttachments");
|
||||
|
||||
var attachments = JobAttachments.Cast<JobAttachment>().Where(a => a.MimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
|
||||
if (attachments.Count > 0)
|
||||
{
|
||||
var attachmentFilepaths = attachments.Select(a => a.RepositoryFilename(Database)).ToList();
|
||||
|
||||
return new FileMontageImageExpressionResult(attachmentFilepaths);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static BitmapImageExpressionResult ImageFromStream(Stream ImageStream)
|
||||
{
|
||||
if (ImageStream == null)
|
||||
throw new ArgumentNullException("ImageStream");
|
||||
|
||||
return new BitmapImageExpressionResult(Bitmap.FromStream(ImageStream));
|
||||
}
|
||||
public static BitmapImageExpressionResult ImageFromByteArray(byte[] ImageByteArray)
|
||||
{
|
||||
if (ImageByteArray == null)
|
||||
throw new ArgumentNullException("ImageByteArray");
|
||||
|
||||
return new BitmapImageExpressionResult(Bitmap.FromStream(new MemoryStream(ImageByteArray)));
|
||||
}
|
||||
public static BitmapImageExpressionResult DeviceModelImage(DeviceModel DeviceModel)
|
||||
{
|
||||
if (DeviceModel == null)
|
||||
throw new ArgumentNullException("DeviceModel");
|
||||
|
||||
using (Stream deviceModelImage = DeviceModel.Image())
|
||||
{
|
||||
if (deviceModelImage == null)
|
||||
return null;
|
||||
else
|
||||
return ImageFromStream(deviceModelImage);
|
||||
}
|
||||
}
|
||||
public static BitmapImageExpressionResult OrganisationLogo()
|
||||
{
|
||||
var configCache = new Disco.Data.Configuration.SystemConfiguration(null);
|
||||
BitmapImageExpressionResult result;
|
||||
using (var orgLogo = configCache.OrganisationLogo)
|
||||
{
|
||||
result = ImageFromStream(orgLogo);
|
||||
}
|
||||
result.LosslessFormat = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
using Disco.Models.BI.Expressions;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
||||
{
|
||||
public abstract class BaseImageExpressionResult : IImageExpressionResult
|
||||
{
|
||||
public byte Quality { get; set; }
|
||||
public bool LosslessFormat { get; set; }
|
||||
public bool ShowField { get; set; }
|
||||
public string BackgroundColour { get; set; }
|
||||
public bool BackgroundPreferTransparent { get; set; }
|
||||
|
||||
public BaseImageExpressionResult()
|
||||
{
|
||||
this.LosslessFormat = false;
|
||||
this.Quality = 90;
|
||||
this.ShowField = false;
|
||||
this.BackgroundPreferTransparent = true;
|
||||
}
|
||||
|
||||
public abstract Stream GetImage(int Width, int Height);
|
||||
|
||||
protected Stream RenderImage(Image SourceImage, int Width, int Height)
|
||||
{
|
||||
if (SourceImage == null)
|
||||
throw new ArgumentNullException("SourceImage");
|
||||
if (Width <= 0)
|
||||
throw new ArgumentOutOfRangeException("Width", "Width must be > 0");
|
||||
if (Height <= 0)
|
||||
throw new ArgumentOutOfRangeException("Height", "Height must be > 0");
|
||||
|
||||
Brush backgroundBrush = null;
|
||||
if (!LosslessFormat || !BackgroundPreferTransparent)
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.BackgroundColour))
|
||||
backgroundBrush = Brushes.White;
|
||||
else
|
||||
backgroundBrush = new SolidBrush(ColorTranslator.FromHtml(this.BackgroundColour));
|
||||
}
|
||||
|
||||
using (Image resizedImage = SourceImage.ResizeImage(Width, Height, backgroundBrush))
|
||||
{
|
||||
return OutputImage(resizedImage);
|
||||
}
|
||||
}
|
||||
|
||||
protected Stream OutputImage(Image SourceImage)
|
||||
{
|
||||
MemoryStream imageStream = new MemoryStream();
|
||||
if (LosslessFormat)
|
||||
{ // Lossless Format - PNG
|
||||
SourceImage.SavePng(imageStream);
|
||||
}
|
||||
else
|
||||
{ // Lossy Format - JPG
|
||||
byte quality = Math.Min((byte)100, Math.Max((byte)1, this.Quality));
|
||||
SourceImage.SaveJpg(quality, imageStream);
|
||||
}
|
||||
imageStream.Position = 0;
|
||||
return imageStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
||||
{
|
||||
public class BitmapImageExpressionResult : BaseImageExpressionResult
|
||||
{
|
||||
public Image Image { get; set; }
|
||||
|
||||
public BitmapImageExpressionResult(Image Image)
|
||||
{
|
||||
if (Image == null)
|
||||
throw new ArgumentNullException("Image");
|
||||
|
||||
this.Image = Image;
|
||||
}
|
||||
|
||||
public override Stream GetImage(int Width, int Height)
|
||||
{
|
||||
return this.RenderImage(this.Image, Width, Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
||||
{
|
||||
public class FileImageExpressionResult : BaseImageExpressionResult
|
||||
{
|
||||
public string AbsoluteFilePath { get; set; }
|
||||
|
||||
public FileImageExpressionResult(string AbsoluteFilePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(AbsoluteFilePath))
|
||||
throw new ArgumentNullException("AbsoluteFilePath");
|
||||
if (!File.Exists(AbsoluteFilePath))
|
||||
throw new FileNotFoundException("Image not found", AbsoluteFilePath);
|
||||
|
||||
this.AbsoluteFilePath = AbsoluteFilePath;
|
||||
}
|
||||
|
||||
public override Stream GetImage(int Width, int Height)
|
||||
{
|
||||
using (Image SourceImage = Bitmap.FromFile(this.AbsoluteFilePath))
|
||||
{
|
||||
return this.RenderImage(SourceImage, Width, Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
||||
{
|
||||
public class FileMontageImageExpressionResult : BaseImageExpressionResult
|
||||
{
|
||||
public List<string> AbsoluteFilePaths { get; set; }
|
||||
public bool MontageHorizontalLayout { get; set; }
|
||||
public bool MontageVerticalLayout { get; set; }
|
||||
public bool MontageTableLayout { get; set; }
|
||||
public int Padding { get; set; }
|
||||
|
||||
public FileMontageImageExpressionResult(List<string> AbsoluteFilePaths)
|
||||
{
|
||||
if (AbsoluteFilePaths == null)
|
||||
throw new ArgumentNullException("AbsoluteFilePaths");
|
||||
if (AbsoluteFilePaths.Count == 0)
|
||||
throw new ArgumentException("AbsoluteFilePaths is empty", "AbsoluteFilePaths");
|
||||
|
||||
this.AbsoluteFilePaths = AbsoluteFilePaths;
|
||||
this.MontageTableLayout = true;
|
||||
this.Padding = 4;
|
||||
}
|
||||
|
||||
public override Stream GetImage(int Width, int Height)
|
||||
{
|
||||
List<Image> Images = new List<Image>();
|
||||
try
|
||||
{
|
||||
// Load Images
|
||||
foreach (string imageFilePath in this.AbsoluteFilePaths)
|
||||
Images.Add(Bitmap.FromFile(imageFilePath));
|
||||
|
||||
// Build Montage
|
||||
using (Bitmap montageImage = new Bitmap(Width, Height))
|
||||
{
|
||||
using (Graphics montageGraphics = Graphics.FromImage(montageImage))
|
||||
{
|
||||
montageGraphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
montageGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
montageGraphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
|
||||
// Draw Background
|
||||
if (!LosslessFormat || !BackgroundPreferTransparent)
|
||||
{
|
||||
Brush backgroundBrush = Brushes.White;
|
||||
if (!string.IsNullOrEmpty(this.BackgroundColour))
|
||||
backgroundBrush = new SolidBrush(ColorTranslator.FromHtml(this.BackgroundColour));
|
||||
montageGraphics.FillRectangle(backgroundBrush, montageGraphics.VisibleClipBounds);
|
||||
}
|
||||
|
||||
if (this.MontageHorizontalLayout)
|
||||
DoHorizontalLayout(Images, montageGraphics);
|
||||
else
|
||||
if (this.MontageVerticalLayout)
|
||||
DoVirticalLayout(Images, montageGraphics);
|
||||
else
|
||||
DoTableLayout(Images, montageGraphics);
|
||||
}
|
||||
|
||||
return this.OutputImage(montageImage);
|
||||
}
|
||||
}
|
||||
catch (Exception) { throw; }
|
||||
finally
|
||||
{
|
||||
// Dispose of any Images
|
||||
if (Images != null)
|
||||
foreach (Image i in Images)
|
||||
i.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void DoHorizontalLayout(List<Image> Images, Graphics MontageGraphics)
|
||||
{
|
||||
|
||||
float imageScale;
|
||||
float imagePosition = 0;
|
||||
int imagesWidthTotal = Images.Sum(i => i.Width);
|
||||
int imagesHeightMax = Images.Max(i => i.Height);
|
||||
int imagesPadding = ((Images.Count - 1) * this.Padding);
|
||||
|
||||
imageScale = (float)(MontageGraphics.VisibleClipBounds.Width - imagesPadding) / (float)imagesWidthTotal;
|
||||
if ((MontageGraphics.VisibleClipBounds.Height / (float)imagesHeightMax) < imageScale)
|
||||
imageScale = (float)MontageGraphics.VisibleClipBounds.Height / (float)imagesHeightMax;
|
||||
foreach (Image image in Images)
|
||||
{
|
||||
MontageGraphics.DrawImageResized(image, imageScale, imagePosition, 0);
|
||||
imagePosition += (imageScale * image.Width) + this.Padding;
|
||||
}
|
||||
}
|
||||
private void DoVirticalLayout(List<Image> Images, Graphics MontageGraphics)
|
||||
{
|
||||
float imageScale;
|
||||
float imagePosition = 0;
|
||||
int imagesWidthMax = Images.Max(i => i.Width);
|
||||
int imagesHeightTotal = Images.Sum(i => i.Height);
|
||||
int imagesPadding = ((Images.Count - 1) * this.Padding);
|
||||
|
||||
imageScale = (float)(MontageGraphics.VisibleClipBounds.Height - imagesPadding) / (float)imagesHeightTotal;
|
||||
if ((MontageGraphics.VisibleClipBounds.Width / (float)imagesWidthMax) < imageScale)
|
||||
imageScale = (float)MontageGraphics.VisibleClipBounds.Width / (float)imagesWidthMax;
|
||||
foreach (Image image in Images)
|
||||
{
|
||||
MontageGraphics.DrawImageResized(image, imageScale, 0, imagePosition);
|
||||
imagePosition += (imageScale * image.Height) + this.Padding;
|
||||
}
|
||||
}
|
||||
private void DoTableLayout(List<Image> Images, Graphics MontageGraphics)
|
||||
{
|
||||
var stageSize = MontageGraphics.VisibleClipBounds.Size.ToSize();
|
||||
var itemAverageSize = new SizeF(Images.Average(i => (float)i.Size.Width), Images.Average(i => (float)i.Size.Height));
|
||||
|
||||
var calculatedLayout = CalculateColumnCount(stageSize, itemAverageSize, Images.Count);
|
||||
|
||||
SizeF cellSize = new SizeF((MontageGraphics.VisibleClipBounds.Width - ((calculatedLayout.Item1 - 1) * this.Padding)) / calculatedLayout.Item1,
|
||||
(MontageGraphics.VisibleClipBounds.Height - ((calculatedLayout.Item2 - 1) * this.Padding)) / calculatedLayout.Item2);
|
||||
|
||||
int imageIndex = 0;
|
||||
for (int rowIndex = 0; rowIndex < calculatedLayout.Item2; rowIndex++)
|
||||
{
|
||||
for (int columnIndex = 0; columnIndex < calculatedLayout.Item1; columnIndex++)
|
||||
{
|
||||
if (imageIndex < Images.Count)
|
||||
{
|
||||
var image = Images[imageIndex];
|
||||
var cellPoint = new PointF((cellSize.Width * columnIndex) + (this.Padding * columnIndex), (cellSize.Height * rowIndex) + (this.Padding * rowIndex));
|
||||
MontageGraphics.Clip = new Region(new RectangleF(cellPoint, cellSize));
|
||||
MontageGraphics.DrawImageResized(image);
|
||||
imageIndex++;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Tuple<int, int, double> CalculateColumnCount(Size StageSize, SizeF ItemAverageSize, int ItemCount)
|
||||
{
|
||||
double? bestUsedSpace = null;
|
||||
int bestColumnCount = 1;
|
||||
int bestRowCount = 1;
|
||||
double bestItemRatio = 1;
|
||||
|
||||
for (int columnCount = 1; columnCount <= ItemCount; columnCount++)
|
||||
{
|
||||
int rowCount = (int)Math.Ceiling((double)ItemCount / (double)columnCount);
|
||||
|
||||
int requiredWidthPadding = (columnCount - 1) * this.Padding;
|
||||
int requiredHeightPadding = (rowCount - 1) * this.Padding;
|
||||
Size usableStageSize = new Size(StageSize.Width - requiredWidthPadding, StageSize.Height - requiredHeightPadding);
|
||||
double stageWidthRatio = (float)usableStageSize.Width / (float)usableStageSize.Height;
|
||||
double stageHeightRatio = (float)usableStageSize.Height / (float)usableStageSize.Width;
|
||||
|
||||
int requiredWidth = (int)Math.Ceiling(ItemAverageSize.Width * columnCount);
|
||||
int requiredHeight = (int)Math.Ceiling(ItemAverageSize.Height * rowCount);
|
||||
|
||||
int usedSpace = requiredWidth * requiredHeight;
|
||||
int stageArea = Math.Max((requiredWidth * (int)Math.Ceiling(requiredWidth * stageHeightRatio)),
|
||||
(requiredHeight * (int)Math.Ceiling(requiredHeight * stageWidthRatio)));
|
||||
double usedStageSpace = (double)usedSpace / stageArea;
|
||||
if (bestUsedSpace == null || bestUsedSpace < usedStageSpace)
|
||||
{
|
||||
bestUsedSpace = usedStageSpace;
|
||||
bestColumnCount = columnCount;
|
||||
bestRowCount = rowCount;
|
||||
bestItemRatio = Math.Min((double)usableStageSize.Width / (double)requiredWidth, (double)usableStageSize.Height / (double)requiredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
return new Tuple<int, int, double>(bestColumnCount, bestRowCount, bestItemRatio);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Users;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services.Expressions.Extensions
|
||||
{
|
||||
public static class UserExt
|
||||
{
|
||||
#region Active Directory Extensions
|
||||
public static object GetActiveDirectoryObjectValue(User User, string PropertyName, int Index = 0)
|
||||
{
|
||||
var adUserAccount = User.ActiveDirectoryAccount(PropertyName);
|
||||
if (adUserAccount != null)
|
||||
return adUserAccount.GetPropertyValues<object>(PropertyName).Skip(Index).FirstOrDefault();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string GetActiveDirectoryStringValue(User User, string PropertyName, int Index = 0)
|
||||
{
|
||||
var objectValue = GetActiveDirectoryObjectValue(User, PropertyName, Index);
|
||||
string stringValue = objectValue as string;
|
||||
if (stringValue == null && objectValue != null)
|
||||
stringValue = objectValue.ToString();
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
public static int GetActiveDirectoryIntegerValue(User User, string PropertyName, int Index = 0)
|
||||
{
|
||||
var objectValue = GetActiveDirectoryObjectValue(User, PropertyName, Index);
|
||||
if (objectValue == null)
|
||||
return default(int);
|
||||
else
|
||||
{
|
||||
int intValue;
|
||||
try
|
||||
{
|
||||
intValue = (int)Convert.ChangeType(objectValue, typeof(int));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
return intValue;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Authorization Testing Extensions
|
||||
public static bool HasAuthorization(User User, string Claim)
|
||||
{
|
||||
var authorization = UserService.GetAuthorization(User.UserId);
|
||||
|
||||
return authorization.Has(Claim);
|
||||
}
|
||||
|
||||
public static bool HasAuthorizationAll(User User, params string[] Claims)
|
||||
{
|
||||
var authorization = UserService.GetAuthorization(User.UserId);
|
||||
|
||||
return authorization.HasAll(Claims);
|
||||
}
|
||||
|
||||
public static bool HasAuthorizationAny(User User, params string[] Claims)
|
||||
{
|
||||
var authorization = UserService.GetAuthorization(User.UserId);
|
||||
|
||||
return authorization.HasAny(Claims);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Disco.Services.Expressions
|
||||
{
|
||||
public interface IExpressionPart
|
||||
{
|
||||
string RawSource { get; set; }
|
||||
string Source { get; set; }
|
||||
bool ErrorsAllowed { get; set; }
|
||||
bool ParseError { get; }
|
||||
string ParseErrorMessage { get; }
|
||||
bool IsDynamic { get; set; }
|
||||
object Evaluate(object ExpressionContext, IDictionary Variables);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Disco.Services.Expressions
|
||||
{
|
||||
public class TextExpressionPart : IExpressionPart
|
||||
{
|
||||
private string _Source;
|
||||
|
||||
bool IExpressionPart.ErrorsAllowed
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
set
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
string IExpressionPart.Source
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._Source;
|
||||
}
|
||||
set
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
string IExpressionPart.RawSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._Source;
|
||||
}
|
||||
set
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
bool IExpressionPart.IsDynamic
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
set
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
public bool ParseError
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public string ParseErrorMessage
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public TextExpressionPart(string Source)
|
||||
{
|
||||
this._Source = Source;
|
||||
}
|
||||
object IExpressionPart.Evaluate(object ExpressionContext, System.Collections.IDictionary Variables)
|
||||
{
|
||||
return this._Source;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class ImagingExtensions
|
||||
{
|
||||
|
||||
public static Bitmap RotateImage(this Image Source, float Angle, Brush BackgroundColor = null, bool ResizeIfOver45Deg = true)
|
||||
{
|
||||
var destWidth = Source.Width;
|
||||
var destHeight = Source.Height;
|
||||
var resizedDest = false;
|
||||
|
||||
if (ResizeIfOver45Deg && ((Angle > 45 && Angle < 135) || (Angle < -45 && Angle > -135)))
|
||||
{
|
||||
destWidth = Source.Height;
|
||||
destHeight = Source.Width;
|
||||
resizedDest = true;
|
||||
}
|
||||
|
||||
var destination = new Bitmap(destWidth, destHeight);
|
||||
destination.SetResolution(Source.HorizontalResolution, Source.VerticalResolution);
|
||||
|
||||
using (Graphics destinationGraphics = Graphics.FromImage(destination))
|
||||
{
|
||||
destinationGraphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
destinationGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
destinationGraphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
|
||||
if (BackgroundColor != null)
|
||||
destinationGraphics.FillRectangle(BackgroundColor, destinationGraphics.VisibleClipBounds);
|
||||
|
||||
float offsetWidth = destWidth / 2;
|
||||
float offsetHeight = destHeight / 2;
|
||||
|
||||
destinationGraphics.TranslateTransform(offsetWidth, offsetHeight);
|
||||
destinationGraphics.RotateTransform(Angle);
|
||||
|
||||
RectangleF destinationLocation;
|
||||
|
||||
if (resizedDest)
|
||||
destinationLocation = new RectangleF(
|
||||
offsetHeight * -1, offsetWidth * -1,
|
||||
destHeight, destWidth);
|
||||
else
|
||||
destinationLocation = new RectangleF(
|
||||
offsetWidth * -1, offsetHeight * -1,
|
||||
destWidth, destHeight);
|
||||
|
||||
destinationGraphics.DrawImage(Source, destinationLocation, new RectangleF(0, 0, Source.Width, Source.Height), GraphicsUnit.Pixel);
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
public static Bitmap ResizeImage(this Image Source, int TargetWidth, int TargetHeight, Brush BackgroundColor = null)
|
||||
{
|
||||
var destination = new Bitmap(TargetWidth, TargetHeight);
|
||||
destination.SetResolution(72, 72);
|
||||
using (Graphics destinationGraphics = Graphics.FromImage(destination))
|
||||
{
|
||||
destinationGraphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
destinationGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
destinationGraphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
|
||||
if (BackgroundColor != null)
|
||||
destinationGraphics.FillRectangle(BackgroundColor, destinationGraphics.VisibleClipBounds);
|
||||
|
||||
destinationGraphics.DrawImageResized(Source);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
public static RectangleF CalculateResize(int SourceWidth, int SourceHeight, int TargetWidth, int TargetHeight, out float scaleRatio)
|
||||
{
|
||||
scaleRatio = Math.Min((float)(TargetWidth) / SourceWidth, (float)(TargetHeight) / SourceHeight);
|
||||
|
||||
float width = SourceWidth * scaleRatio,
|
||||
height = SourceHeight * scaleRatio,
|
||||
x = 0,
|
||||
y = 0;
|
||||
|
||||
if (width < TargetWidth)
|
||||
x = (TargetWidth - width) / 2;
|
||||
|
||||
if (height < TargetHeight)
|
||||
y = (TargetHeight - height) / 2;
|
||||
|
||||
return new RectangleF(x, y, width, height);
|
||||
}
|
||||
|
||||
public static RectangleF CalculateResize(int SourceWidth, int SourceHeight, int TargetWidth, int TargetHeight)
|
||||
{
|
||||
float scaleRatio;
|
||||
|
||||
return CalculateResize(SourceWidth, SourceHeight, TargetHeight, TargetHeight, out scaleRatio);
|
||||
}
|
||||
|
||||
public static RectangleF CalculateResize(this Image Source, int TargetWidth, int TargetHeight, out float scaleRatio)
|
||||
{
|
||||
return CalculateResize(Source.Width, Source.Height, TargetWidth, TargetHeight, out scaleRatio);
|
||||
}
|
||||
|
||||
public static RectangleF CalculateResize(this Image Source, int TargetWidth, int TargetHeight)
|
||||
{
|
||||
return CalculateResize(Source.Width, Source.Height, TargetHeight, TargetHeight);
|
||||
}
|
||||
|
||||
public static void DrawImageResized(this Graphics graphics, Image SourceImage)
|
||||
{
|
||||
RectangleF clipBounds = graphics.VisibleClipBounds;
|
||||
var resizeBounds = SourceImage.CalculateResize((int)clipBounds.Width, (int)clipBounds.Height);
|
||||
|
||||
graphics.DrawImage(SourceImage, resizeBounds, new RectangleF(0, 0, SourceImage.Width, SourceImage.Height), GraphicsUnit.Pixel);
|
||||
}
|
||||
|
||||
public static void DrawImageResized(this Graphics graphics, Image SourceImage, float Scale, float LocationX, float LocationY)
|
||||
{
|
||||
RectangleF clipBounds = graphics.VisibleClipBounds;
|
||||
|
||||
float width = SourceImage.Width * Scale,
|
||||
height = SourceImage.Height * Scale,
|
||||
x = LocationX,
|
||||
y = LocationY;
|
||||
|
||||
x += clipBounds.Left;
|
||||
y += clipBounds.Top;
|
||||
|
||||
graphics.DrawImage(SourceImage, new RectangleF(x, y, width, height), new RectangleF(0, 0, SourceImage.Width, SourceImage.Height), GraphicsUnit.Pixel);
|
||||
}
|
||||
|
||||
public static void EmbedIconOverlay(this Image Source, Image Icon)
|
||||
{
|
||||
int top = Math.Max(0, Source.Height - Icon.Height);
|
||||
int left = Math.Max(0, Source.Width - Icon.Width);
|
||||
|
||||
using (Graphics sourceGraphics = Graphics.FromImage(Source))
|
||||
{
|
||||
sourceGraphics.DrawImage(Icon, left, top);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SavePng(this Image Source, string Filename)
|
||||
{
|
||||
using (FileStream outStream = new FileStream(Filename, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
SavePng(Source, outStream);
|
||||
outStream.Flush();
|
||||
outStream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SavePng(this Image Source, Stream OutStream)
|
||||
{
|
||||
Source.Save(OutStream, ImageFormat.Png);
|
||||
}
|
||||
|
||||
public static Stream SavePng(this Image Source)
|
||||
{
|
||||
MemoryStream outStream = new MemoryStream();
|
||||
Source.SavePng(outStream);
|
||||
outStream.Position = 0;
|
||||
return outStream;
|
||||
}
|
||||
|
||||
public static Stream SaveJpg(this Image Source, int Quality)
|
||||
{
|
||||
MemoryStream outStream = new MemoryStream();
|
||||
Source.SaveJpg(Quality, outStream);
|
||||
outStream.Position = 0;
|
||||
return outStream;
|
||||
}
|
||||
|
||||
public static void SaveJpg(this Image Source, int Quality, string Filename)
|
||||
{
|
||||
using (FileStream outStream = new FileStream(Filename, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
SaveJpg(Source, Quality, outStream);
|
||||
outStream.Flush();
|
||||
outStream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveJpg(this Image Source, int Quality, Stream OutStream)
|
||||
{
|
||||
ImageCodecInfo jpgCodec = ImageCodecInfo.GetImageEncoders().Where(c => c.MimeType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
|
||||
if (jpgCodec != null)
|
||||
{
|
||||
if (Quality < 0 || Quality > 100)
|
||||
throw new ArgumentOutOfRangeException("Quality", "Quality must be a positive integer <= 100");
|
||||
using (EncoderParameters ep = new EncoderParameters(1))
|
||||
{
|
||||
ep.Param[0] = new EncoderParameter(Encoder.Quality, Quality);
|
||||
Source.Save(OutStream, jpgCodec, ep);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback
|
||||
Source.Save(OutStream, ImageFormat.Jpeg);
|
||||
}
|
||||
}
|
||||
|
||||
public static Color InterpolateColours(this Color Start, Color End, double Progress)
|
||||
{
|
||||
if (Progress > 1 || Progress < 0)
|
||||
throw new ArgumentOutOfRangeException("Progress", "Progress must be >= 0 && <= 1");
|
||||
|
||||
return Color.FromArgb(
|
||||
(byte)(Start.A * (1 - Progress) + (End.A * Progress)),
|
||||
(byte)(Start.R * (1 - Progress) + (End.R * Progress)),
|
||||
(byte)(Start.G * (1 - Progress) + (End.G * Progress)),
|
||||
(byte)(Start.B * (1 - Progress) + (End.B * Progress))
|
||||
);
|
||||
}
|
||||
|
||||
public static RectangleF Multiply(this RectangleF Other, float Multiplier)
|
||||
{
|
||||
return new RectangleF(Other.X * Multiplier, Other.Y * Multiplier, Other.Width * Multiplier, Other.Height * Multiplier);
|
||||
}
|
||||
|
||||
public static RectangleF Divide(this RectangleF Other, float Divisor)
|
||||
{
|
||||
return new RectangleF(Other.X / Divisor, Other.Y / Divisor, Other.Width / Divisor, Other.Height / Divisor);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Interop.ActiveDirectory;
|
||||
using System.Collections.Generic;
|
||||
using System.DirectoryServices;
|
||||
using System.DirectoryServices.ActiveDirectory;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Interop.ActiveDirectory
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class ActiveDirectoryExtensions
|
||||
{
|
||||
@@ -98,6 +97,17 @@ namespace Disco.Services.Interop.ActiveDirectory
|
||||
|
||||
#endregion
|
||||
|
||||
public static ADUserAccount ActiveDirectoryAccount(this User User, params string[] AdditionalProperties)
|
||||
{
|
||||
return ActiveDirectory.RetrieveADUserAccount(User.UserId, AdditionalProperties);
|
||||
}
|
||||
|
||||
public static ADMachineAccount ActiveDirectoryAccount(this Device Device, params string[] AdditionalProperties)
|
||||
{
|
||||
if (ActiveDirectory.IsValidDomainAccountId(Device.DeviceDomainId))
|
||||
return ActiveDirectory.RetrieveADMachineAccount(Device.DeviceDomainId, AdditionalProperties: AdditionalProperties);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Disco.Services.Interop
|
||||
{
|
||||
public static class MimeTypes
|
||||
{
|
||||
public static string ResolveMimeType(string Filename)
|
||||
{
|
||||
string fileExtension;
|
||||
if (Filename.Contains("."))
|
||||
fileExtension = Filename.Substring(Filename.LastIndexOf(".") + 1).ToLower();
|
||||
else
|
||||
fileExtension = Filename.ToLower();
|
||||
|
||||
// Try Known Mime Types
|
||||
switch (fileExtension)
|
||||
{
|
||||
case "pdf":
|
||||
return "application/pdf";
|
||||
case "doc":
|
||||
return "application/msword";
|
||||
case "docx":
|
||||
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||
case "docm":
|
||||
return "application/vnd.ms-word.document.macroEnabled.12";
|
||||
case "xml":
|
||||
return "text/xml";
|
||||
case "xls":
|
||||
return "application/vnd.ms-excel";
|
||||
case "xlsx":
|
||||
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
case "xlsm":
|
||||
return "application/vnd.ms-excel.sheet.macroEnabled.12";
|
||||
case "csv":
|
||||
return "application/vnd.ms-excel";
|
||||
case "jpg":
|
||||
return "image/jpeg";
|
||||
case "gif":
|
||||
return "image/gif";
|
||||
case "png":
|
||||
return "image/png";
|
||||
case "bmp":
|
||||
return "image/bmp";
|
||||
case "avi":
|
||||
return "video/avi";
|
||||
case "mpeg":
|
||||
case "mpg":
|
||||
return "video/mpeg";
|
||||
case "mp3":
|
||||
return "audio/mpeg";
|
||||
case "mp4":
|
||||
return "video/mp4";
|
||||
case "wmv":
|
||||
return "video/x-ms-wmv";
|
||||
case "mov":
|
||||
return "video/quicktime";
|
||||
}
|
||||
|
||||
// Check System Registry
|
||||
try
|
||||
{
|
||||
RegistryKey regExtensionKey = Registry.ClassesRoot.OpenSubKey("." + fileExtension);
|
||||
if (regExtensionKey != null)
|
||||
{
|
||||
string regExtensionContentType = regExtensionKey.GetValue("Content Type") as string;
|
||||
if (regExtensionContentType != null)
|
||||
{
|
||||
return regExtensionContentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore Errors
|
||||
}
|
||||
|
||||
// Return Default
|
||||
return "unknown/unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Disco.Services.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Disco.Services.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap MimeType_doc48 {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("MimeType_doc48", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap MimeType_img16 {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("MimeType_img16", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap MimeType_pdf16 {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("MimeType_pdf16", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap MimeType_pdf48 {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("MimeType_pdf48", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap MimeType_unknown48 {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("MimeType_unknown48", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="MimeType_doc48" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\resources\mimetype-doc48.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="MimeType_img16" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\resources\mimetype-img16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="MimeType_pdf16" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\resources\mimetype-pdf16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="MimeType_pdf48" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\resources\mimetype-pdf48.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="MimeType_unknown48" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\resources\mimetype-unknown48.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
</root>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -16,6 +16,8 @@
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net40" />
|
||||
<package id="Newtonsoft.Json" version="6.0.3" targetFramework="net45" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="PdfiumViewer" version="2.10.0.0" targetFramework="net45" />
|
||||
<package id="PDFsharp" version="1.32.3057.0" targetFramework="net45" />
|
||||
<package id="RazorGenerator.Mvc" version="2.2.3" targetFramework="net45" />
|
||||
<package id="Rx-Core" version="2.2.5" targetFramework="net45" />
|
||||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" />
|
||||
@@ -24,4 +26,5 @@
|
||||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="net45" />
|
||||
<package id="SqlServerCompact" version="4.0.8854.1" targetFramework="net40" />
|
||||
<package id="WebActivatorEx" version="2.0.5" targetFramework="net45" />
|
||||
<package id="ZXing.Net" version="0.14.0.1" targetFramework="net45" />
|
||||
</packages>
|
||||
Binary file not shown.
Reference in New Issue
Block a user