diff --git a/Controls.DocumentViewer/App.config b/Controls.DocumentViewer/App.config index b7160380..0927f884 100644 --- a/Controls.DocumentViewer/App.config +++ b/Controls.DocumentViewer/App.config @@ -23,7 +23,7 @@ - + diff --git a/Controls.DocumentViewer/DocumentViewer.Designer.vb b/Controls.DocumentViewer/DocumentViewer.Designer.vb index e08d99f5..f68aebe7 100644 --- a/Controls.DocumentViewer/DocumentViewer.Designer.vb +++ b/Controls.DocumentViewer/DocumentViewer.Designer.vb @@ -24,7 +24,6 @@ Partial Class DocumentViewer Private Sub InitializeComponent() Me.components = New System.ComponentModel.Container() Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(DocumentViewer)) - Me.GdViewer = New GdPicture14.GdViewer() Me.OpenFileDialog = New System.Windows.Forms.OpenFileDialog() Me.BarManager1 = New DevExpress.XtraBars.BarManager(Me.components) Me.ToolbarDocumentViewer = New DevExpress.XtraBars.Bar() @@ -74,82 +73,6 @@ Partial Class DocumentViewer CType(Me.RepositoryItemSearchControl1, System.ComponentModel.ISupportInitialize).BeginInit() Me.SuspendLayout() ' - 'GdViewer - ' - Me.GdViewer.AllowDropFile = False - Me.GdViewer.AnimateGIF = True - Me.GdViewer.AnnotationDropShadow = False - Me.GdViewer.AnnotationEnableMultiSelect = True - Me.GdViewer.AnnotationResizeRotateHandlesColor = System.Drawing.Color.FromArgb(CType(CType(0, Byte), Integer), CType(CType(0, Byte), Integer), CType(CType(128, Byte), Integer)) - Me.GdViewer.AnnotationResizeRotateHandlesScale = 1.0! - Me.GdViewer.AnnotationSelectionLineColor = System.Drawing.Color.FromArgb(CType(CType(255, Byte), Integer), CType(CType(0, Byte), Integer), CType(CType(0, Byte), Integer)) - Me.GdViewer.AutoScrollMargin = New System.Drawing.Size(0, 0) - Me.GdViewer.AutoScrollMinSize = New System.Drawing.Size(0, 0) - Me.GdViewer.BackColor = System.Drawing.SystemColors.AppWorkspace - Me.GdViewer.BackgroundImage = Nothing - Me.GdViewer.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None - Me.GdViewer.ClipAnnotsToPageBounds = True - Me.GdViewer.ClipRegionsToPageBounds = True - Me.GdViewer.ContinuousViewMode = True - Me.GdViewer.DisplayQuality = GdPicture14.DisplayQuality.DisplayQualityAutomatic - Me.GdViewer.DisplayQualityAuto = True - Me.GdViewer.Dock = System.Windows.Forms.DockStyle.Fill - Me.GdViewer.DocumentAlignment = GdPicture14.ViewerDocumentAlignment.DocumentAlignmentMiddleCenter - Me.GdViewer.DocumentPosition = GdPicture14.ViewerDocumentPosition.DocumentPositionMiddleCenter - Me.GdViewer.DrawPageBorders = True - Me.GdViewer.EnableDeferredPainting = True - Me.GdViewer.EnabledProgressBar = True - Me.GdViewer.EnableFuzzySearch = False - Me.GdViewer.EnableICM = False - Me.GdViewer.EnableMenu = True - Me.GdViewer.EnableMouseWheel = True - Me.GdViewer.EnableTextSelection = True - Me.GdViewer.ForceScrollBars = False - Me.GdViewer.ForceTemporaryMode = False - Me.GdViewer.ForeColor = System.Drawing.Color.Black - Me.GdViewer.Gamma = 1.0! - Me.GdViewer.HQAnnotationRendering = True - Me.GdViewer.IgnoreDocumentResolution = False - Me.GdViewer.KeepDocumentPosition = False - Me.GdViewer.Location = New System.Drawing.Point(0, 33) - Me.GdViewer.LockViewer = False - Me.GdViewer.MagnifierHeight = 90 - Me.GdViewer.MagnifierWidth = 160 - Me.GdViewer.MagnifierZoomX = 2.0! - Me.GdViewer.MagnifierZoomY = 2.0! - Me.GdViewer.MouseButtonForMouseMode = GdPicture14.MouseButton.MouseButtonLeft - Me.GdViewer.MouseMode = GdPicture14.ViewerMouseMode.MouseModePan - Me.GdViewer.MouseWheelMode = GdPicture14.ViewerMouseWheelMode.MouseWheelModeVerticalScroll - Me.GdViewer.Name = "GdViewer" - Me.GdViewer.PageBordersColor = System.Drawing.Color.Black - Me.GdViewer.PageBordersPenSize = 1 - Me.GdViewer.PageDisplayMode = GdPicture14.PageDisplayMode.MultiplePagesView - Me.GdViewer.PdfDisplayFormField = True - Me.GdViewer.PdfEnableFileLinks = True - Me.GdViewer.PdfEnableLinks = True - Me.GdViewer.PdfIncreaseTextContrast = False - Me.GdViewer.PdfShowDialogForPassword = True - Me.GdViewer.PdfShowOpenFileDialogForDecryption = True - Me.GdViewer.PdfVerifyDigitalCertificates = False - Me.GdViewer.PreserveViewRotation = True - Me.GdViewer.RectBorderColor = System.Drawing.Color.Black - Me.GdViewer.RectBorderSize = 1 - Me.GdViewer.RectIsEditable = True - Me.GdViewer.RegionsAreEditable = True - Me.GdViewer.RenderGdPictureAnnots = True - Me.GdViewer.ScrollBars = True - Me.GdViewer.ScrollLargeChange = CType(50, Short) - Me.GdViewer.ScrollSmallChange = CType(1, Short) - Me.GdViewer.SilentMode = True - Me.GdViewer.Size = New System.Drawing.Size(841, 522) - Me.GdViewer.TabIndex = 0 - Me.GdViewer.TabStop = False - Me.GdViewer.ViewRotation = System.Drawing.RotateFlipType.RotateNoneFlipNone - Me.GdViewer.Zoom = 1.0R - Me.GdViewer.ZoomCenterAtMousePosition = False - Me.GdViewer.ZoomMode = GdPicture14.ViewerZoomMode.ZoomMode100 - Me.GdViewer.ZoomStep = 25 - ' 'OpenFileDialog ' Me.OpenFileDialog.FileName = "OpenFileDialog1" @@ -478,11 +401,6 @@ Partial Class DocumentViewer Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font Me.BackColor = System.Drawing.SystemColors.ControlLightLight - Me.Controls.Add(Me.lblInfo) - Me.Controls.Add(Me.RichEditControl1) - Me.Controls.Add(Me.lbFileNotLoaded) - Me.Controls.Add(Me.SpreadsheetControl1) - Me.Controls.Add(Me.GdViewer) Me.Controls.Add(Me.barDockControlLeft) Me.Controls.Add(Me.barDockControlRight) Me.Controls.Add(Me.barDockControlBottom) diff --git a/Controls.DocumentViewer/DocumentViewer.vb b/Controls.DocumentViewer/DocumentViewer.vb index 89d0633b..1441527d 100644 --- a/Controls.DocumentViewer/DocumentViewer.vb +++ b/Controls.DocumentViewer/DocumentViewer.vb @@ -66,6 +66,8 @@ Public Class DocumentViewer Private _TempFiles As New List(Of String) Private Sub DocumentViewer_Load(sender As Object, e As EventArgs) Handles Me.Load + ' Ensure search is initialized once the control (and GdViewer) exists + EnsureSearchInitialized() UpdateMainUi() End Sub 'hallo @@ -110,7 +112,8 @@ Public Class DocumentViewer _licenseKey = pLicenseKey _licenseManager.RegisterKEY(_licenseKey) _Annotations = New Annotations(pLogConfig) - _Search = New Search(pLogConfig, GdViewer) + ' Defer creating Search until GdViewer is ready + EnsureSearchInitialized() _ToolbarSettings = pToolbarSettings Dim oConfigPath = ConfigPath() @@ -122,6 +125,17 @@ Public Class DocumentViewer End Try End Function + ' Create the Search helper only when both the log config and the GdViewer control exist + Private Sub EnsureSearchInitialized() + Try + If _Search Is Nothing AndAlso _logConfig IsNot Nothing AndAlso GdViewer IsNot Nothing Then + _Search = New Search(_logConfig, GdViewer) + End If + Catch ex As Exception + _logger?.Error(ex) + End Try + End Sub + ''' ''' Load a file from a path and display it ''' @@ -223,7 +237,17 @@ Public Class DocumentViewer Public Sub CloseDocument() Try - GdViewer.CloseDocument() + ' Null-sicher schließen + If GdViewer IsNot Nothing Then + Try + GdViewer.CloseDocument() + Catch exInner As Exception + _logger?.Warn("Fehler beim Schließen von GdViewer") + _logger?.Error(exInner) + End Try + Else + _logger?.Debug("CloseDocument: GdViewer ist Nothing – nichts zu schließen") + End If _FileInfo = Nothing _FilePath = Nothing @@ -299,23 +323,23 @@ Public Class DocumentViewer GdViewer.Focus() UpdateMainUi() End Sub - Private Sub btnFirstPage_Click(ByVal sender As System.Object, ByVal e As EventArgs) Handles buttonFirstPage.ItemClick + Private Sub BtnFirstPage_Click(ByVal sender As System.Object, ByVal e As EventArgs) Handles buttonFirstPage.ItemClick GdViewer.DisplayFirstPage() End Sub - Private Sub btnPreviousPage_Click(ByVal sender As System.Object, ByVal e As EventArgs) Handles buttonPrevPage.ItemClick + Private Sub BtnPreviousPage_Click(ByVal sender As System.Object, ByVal e As EventArgs) Handles buttonPrevPage.ItemClick GdViewer.DisplayPreviousPage() End Sub - Private Sub btnNextPage_Click(ByVal sender As System.Object, ByVal e As EventArgs) Handles buttonNextPage.ItemClick + Private Sub BtnNextPage_Click(ByVal sender As System.Object, ByVal e As EventArgs) Handles buttonNextPage.ItemClick GdViewer.DisplayNextPage() End Sub - Private Sub btnLastPage_Click(ByVal sender As System.Object, ByVal e As EventArgs) Handles buttonLastPage.ItemClick + Private Sub BtnLastPage_Click(ByVal sender As System.Object, ByVal e As EventArgs) Handles buttonLastPage.ItemClick GdViewer.DisplayLastPage() End Sub - Private Sub tbCurrentPage_Leave(ByVal sender As System.Object, ByVal e As EventArgs) Handles txtCurrentPage.EditValueChanged + Private Sub TbCurrentPage_Leave(ByVal sender As System.Object, ByVal e As EventArgs) Handles txtCurrentPage.EditValueChanged Dim page As Integer = 0 If Integer.TryParse(txtCurrentPage.EditValue, page) Then If page > 0 And page <= GdViewer.PageCount Then @@ -352,11 +376,11 @@ Public Class DocumentViewer GdViewer.PrintDialog() End Sub - Private Sub btnRotateLeft_Click(sender As Object, e As EventArgs) Handles buttonRotateLeft.ItemClick + Private Sub BtnRotateLeft_Click(sender As Object, e As EventArgs) Handles buttonRotateLeft.ItemClick GdViewer.Rotate(RotateFlipType.Rotate270FlipNone) End Sub - Private Sub btnRotateRight_Click(sender As Object, e As EventArgs) Handles buttonRotateRight.ItemClick + Private Sub BtnRotateRight_Click(sender As Object, e As EventArgs) Handles buttonRotateRight.ItemClick GdViewer.Rotate(RotateFlipType.Rotate90FlipNone) End Sub @@ -381,7 +405,7 @@ Public Class DocumentViewer End If End Sub - Private Sub btnSettings_Click(ByVal sender As Object, ByVal e As EventArgs) Handles buttonSettings.ItemClick + Private Sub BtnSettings_Click(ByVal sender As Object, ByVal e As EventArgs) Handles buttonSettings.ItemClick Using frmSettings As New frmViewerSettings(GdViewer) frmSettings.ShowDialog(Me) End Using @@ -468,6 +492,12 @@ Public Class DocumentViewer End Sub Private Function DoLoadFile(FilePath As String, Optional ViewOverride As String = "") As Boolean Try + ' Ensure the embedded GdViewer control exists before using it + If Not EnsureViewerReady() Then + _logger?.Warn("GdViewer control is not initialized yet. Delaying load.") + Return False + End If + lblInfo.Visible = False Dim oFileInfo = New FileInfo(FilePath) Dim oExtension As String = oFileInfo.Extension.ToUpper @@ -503,12 +533,6 @@ Public Class DocumentViewer GdViewer.Visible = False SpreadsheetControl1.Dock = DockStyle.Fill - 'Case ".EML", ".DOC", ".DOCX", ".ODT", ".RTF", ".TXT" - ' RichEditControl1.LoadDocument(FilePath, GetDocumentFormat(oExtension)) - - ' RichEditControl1.Visible = True - ' GdViewer.Visible = False - ' RichEditControl1.Dock = DockStyle.Fill Case Else Select Case oExtension.ToUpper Case ".EML", ".DOC", ".DOCX", ".XLS", ".XLSX", ".ODT", ".RTF", ".TXT" @@ -555,6 +579,32 @@ Public Class DocumentViewer End Try End Function + ' Ensures the embedded GdViewer control exists and is added to the visual tree + Private Function EnsureViewerReady() As Boolean + Try + ' If the control field is Nothing (e.g., designer not yet created), try to lazy-create and add it + If GdViewer Is Nothing Then + ' Attempt to find an existing instance by name in Controls collection + Dim existing = Me.Controls.OfType(Of GdPicture14.GdViewer)().FirstOrDefault() + If existing IsNot Nothing Then + ' Assign the designer field via reflection if needed, otherwise use it directly + GdViewer = existing + Else + ' Last resort: create a new viewer and add it + Dim viewer = New GdPicture14.GdViewer() + viewer.Dock = DockStyle.Fill + Me.Controls.Add(viewer) + GdViewer = viewer + End If + End If + + Return GdViewer IsNot Nothing + Catch ex As Exception + _logger?.Error(ex) + Return False + End Try + End Function + Private Sub FitToPage() GdViewer.ZoomMode = ViewerZoomMode.ZoomModeFitToViewer End Sub @@ -703,18 +753,21 @@ Public Class DocumentViewer End Sub Private Sub btnSearch2_ItemClick(sender As Object, e As XtraBars.ItemClickEventArgs) Handles btnSearch2.ItemClick - If Not String.IsNullOrEmpty(txtSearch.EditValue) Then + EnsureSearchInitialized() + If _Search IsNot Nothing AndAlso Not String.IsNullOrEmpty(txtSearch.EditValue) Then _Search.SearchAll(txtSearch.EditValue?.ToString) End If End Sub Private Sub btnPrevHighlight_ItemClick(sender As Object, e As XtraBars.ItemClickEventArgs) Handles btnPrevHighlight.ItemClick - _Search.PrevHighlight() + EnsureSearchInitialized() + _Search?.PrevHighlight() End Sub Private Sub btnNextHighlight_ItemClick(sender As Object, e As XtraBars.ItemClickEventArgs) Handles btnNextHighlight.ItemClick - _Search.NextHighlight() + EnsureSearchInitialized() + _Search?.NextHighlight() End Sub Private Sub txtSearch_EditValueChanged(sender As Object, e As EventArgs) Handles txtSearch.EditValueChanged diff --git a/Controls.DocumentViewer/DocumentViewer.vbproj b/Controls.DocumentViewer/DocumentViewer.vbproj index bef79e71..e0494014 100644 --- a/Controls.DocumentViewer/DocumentViewer.vbproj +++ b/Controls.DocumentViewer/DocumentViewer.vbproj @@ -96,86 +96,74 @@ ..\packages\DocumentFormat.OpenXml.Framework.3.2.0\lib\net46\DocumentFormat.OpenXml.Framework.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.barcode.1d.writer.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.barcode.1d.writer.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.barcode.2d.writer.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.barcode.2d.writer.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.CAD.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.CAD.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.CAD.DWG.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.CAD.DWG.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.Common.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Common.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.Document.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Document.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.Email.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Email.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.HTML.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.HTML.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.Imaging.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Imaging.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.Imaging.Formats.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Imaging.Formats.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.Imaging.Formats.Conversion.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Imaging.Formats.Conversion.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.Imaging.Rendering.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Imaging.Rendering.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.MSOfficeBinary.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.MSOfficeBinary.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.OpenDocument.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.OpenDocument.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.OpenXML.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.OpenXML.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.OpenXML.Templating.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.OpenXML.Templating.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.PDF.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.PDF.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.RTF.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.RTF.dll - - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.SVG.dll + + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.SVG.dll - ..\packages\GdPicture.14.3.19\lib\net462\GdPicture.NET.14.wia.gateway.dll + ..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.wia.gateway.dll True - - ..\packages\IndexRange.1.0.3\lib\net45\IndexRange.dll - ..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll - - ..\packages\Microsoft.Bcl.HashCode.6.0.0\lib\net462\Microsoft.Bcl.HashCode.dll - - - ..\packages\GdPicture.14.3.19\lib\net462\NativeSDK.Settings.dll - - - ..\packages\GdPicture.14.3.19\lib\net462\NativeSDK.Settings.Edition.dll - ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll @@ -185,8 +173,8 @@ ..\packages\NLog.5.0.5\lib\net46\NLog.dll - - ..\packages\OpenMcdf.3.0.0\lib\netstandard2.0\OpenMcdf.dll + + ..\packages\OpenMcdf.2.4.1\lib\net40\OpenMcdf.dll @@ -206,8 +194,8 @@ ..\packages\System.CodeDom.8.0.0\lib\net462\System.CodeDom.dll - - ..\packages\System.Collections.Immutable.9.0.0\lib\net462\System.Collections.Immutable.dll + + ..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll @@ -359,11 +347,11 @@ - + Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}". - + \ No newline at end of file diff --git a/Controls.DocumentViewer/My Project/AssemblyInfo.vb b/Controls.DocumentViewer/My Project/AssemblyInfo.vb index 1f14e62d..3d6979a4 100644 --- a/Controls.DocumentViewer/My Project/AssemblyInfo.vb +++ b/Controls.DocumentViewer/My Project/AssemblyInfo.vb @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - - + + diff --git a/Controls.DocumentViewer/packages.config b/Controls.DocumentViewer/packages.config index 46ecac0c..18dc42d5 100644 --- a/Controls.DocumentViewer/packages.config +++ b/Controls.DocumentViewer/packages.config @@ -3,24 +3,22 @@ - - - + + - - + - + diff --git a/GUIs.Common/app.config b/GUIs.Common/app.config index 9081ad45..0f74ee4e 100644 --- a/GUIs.Common/app.config +++ b/GUIs.Common/app.config @@ -103,6 +103,10 @@ + + + + diff --git a/GUIs.Test.TestGUI/My Project/licenses.licx b/GUIs.Test.TestGUI/My Project/licenses.licx index e69de29b..cb7c55fb 100644 --- a/GUIs.Test.TestGUI/My Project/licenses.licx +++ b/GUIs.Test.TestGUI/My Project/licenses.licx @@ -0,0 +1 @@ +DevExpress.XtraEditors.TextEdit, DevExpress.XtraEditors.v21.2, Version=21.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a diff --git a/GUIs.Test.TestGUI/MyApplication.vb b/GUIs.Test.TestGUI/MyApplication.vb new file mode 100644 index 00000000..68fe6785 --- /dev/null +++ b/GUIs.Test.TestGUI/MyApplication.vb @@ -0,0 +1,49 @@ +Imports DigitalData.Modules.Database +Imports DigitalData.Modules.EDMI.API +Imports DigitalData.Modules.EDMI.API.Modules +Imports DigitalData.Modules.Logging +Imports DigitalData.Modules.ZooFlow + +Namespace My + ''' + ''' Extends the My Namespace + ''' Example: My.LogConfig + ''' + + Module Extension + Property LogConfig As LogConfig +#Region "Database" + Property Database As DatabaseWithFallback + Property DatabaseECM As MSSQLServer + Property DatabaseIDB As MSSQLServer + End Module + +#End Region + Partial Friend Class MyApplication + Public Property Skin As String = "" + Public Property Palette As String = "" + Public Property GlobixDropAreaStyle As String = "PROGRESSIVE" + + Public Property Settings As New State.SettingsState + Public Property User As New State.UserState + Public Property Service As New State.ServiceState + Public Property Modules As New Dictionary(Of String, State.ModuleState) + Public Property ModulesActive As New List(Of String) + + + Public CommandLineFunction As String + Public CommandLineArguments As New Dictionary(Of String, String) + + Public Function GetEnvironment() As Environment + Return New Environment With { + .Database = My.DatabaseECM, + .DatabaseIDB = My.DatabaseIDB, + .Modules = My.Application.Modules, + .Service = My.Application.Service, + .Settings = My.Application.Settings, + .User = My.Application.User + } + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/GUIs.Test.TestGUI/TestGUI.vbproj b/GUIs.Test.TestGUI/TestGUI.vbproj index 9469e29a..f036ba40 100644 --- a/GUIs.Test.TestGUI/TestGUI.vbproj +++ b/GUIs.Test.TestGUI/TestGUI.vbproj @@ -94,6 +94,10 @@ ..\..\DDModules\Database\bin\Debug\DigitalData.Modules.Database.dll + + False + P:\Projekte DIGITAL DATA\DIGITAL DATA - Entwicklung\DLL_Bibliotheken\Digital Data\DD_Modules\DigitalData.Modules.EDMI.API.dll + ..\..\DDModules\Encryption\bin\Debug\DigitalData.Modules.Encryption.dll @@ -118,6 +122,10 @@ ..\..\DDModules\Windream\bin\Debug\DigitalData.Modules.Windream.dll + + False + ..\..\DDModules\ZooFlow\bin\Debug\DigitalData.Modules.ZooFlow.dll + ..\packages\DocumentFormat.OpenXml.3.2.0\lib\net46\DocumentFormat.OpenXml.dll @@ -407,6 +415,7 @@ Settings.settings True + @@ -488,10 +497,6 @@ {a8c3f298-76ab-4359-ab3c-986e313b4336} EDMIService - - {7deec36e-ea5f-4711-ad1e-fd8894f4ad77} - DDZUGFeRDService - diff --git a/GUIs.Test.TestGUI/frmFilesystem.Designer.vb b/GUIs.Test.TestGUI/frmFilesystem.Designer.vb index 7c530ea3..49790b0a 100644 --- a/GUIs.Test.TestGUI/frmFilesystem.Designer.vb +++ b/GUIs.Test.TestGUI/frmFilesystem.Designer.vb @@ -23,28 +23,141 @@ Partial Class frmFilesystem _ Private Sub InitializeComponent() Me.Button1 = New System.Windows.Forms.Button() + Me.Button2 = New System.Windows.Forms.Button() + Me.GroupBox1 = New System.Windows.Forms.GroupBox() + Me.Button3 = New System.Windows.Forms.Button() + Me.TextBox1 = New System.Windows.Forms.TextBox() + Me.SaveFileDialog1 = New System.Windows.Forms.SaveFileDialog() + Me.txtServiceAddress = New DevExpress.XtraEditors.TextEdit() + Me.Label1 = New System.Windows.Forms.Label() + Me.txtServicePort = New DevExpress.XtraEditors.TextEdit() + Me.Button4 = New System.Windows.Forms.Button() + Me.txtStatus = New System.Windows.Forms.TextBox() + Me.GroupBox1.SuspendLayout() + CType(Me.txtServiceAddress.Properties, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.txtServicePort.Properties, System.ComponentModel.ISupportInitialize).BeginInit() Me.SuspendLayout() ' 'Button1 ' - Me.Button1.Location = New System.Drawing.Point(12, 12) + Me.Button1.Location = New System.Drawing.Point(59, 343) Me.Button1.Name = "Button1" - Me.Button1.Size = New System.Drawing.Size(75, 23) + Me.Button1.Size = New System.Drawing.Size(288, 23) Me.Button1.TabIndex = 0 - Me.Button1.Text = "Button1" + Me.Button1.Text = "CreateDateDirectory" Me.Button1.UseVisualStyleBackColor = True ' + 'Button2 + ' + Me.Button2.Location = New System.Drawing.Point(6, 71) + Me.Button2.Name = "Button2" + Me.Button2.Size = New System.Drawing.Size(288, 23) + Me.Button2.TabIndex = 1 + Me.Button2.Text = "Stream simple file" + Me.Button2.UseVisualStyleBackColor = True + ' + 'GroupBox1 + ' + Me.GroupBox1.Controls.Add(Me.Button3) + Me.GroupBox1.Controls.Add(Me.TextBox1) + Me.GroupBox1.Controls.Add(Me.Button2) + Me.GroupBox1.Location = New System.Drawing.Point(36, 153) + Me.GroupBox1.Name = "GroupBox1" + Me.GroupBox1.Size = New System.Drawing.Size(709, 157) + Me.GroupBox1.TabIndex = 2 + Me.GroupBox1.TabStop = False + Me.GroupBox1.Text = "Stream simple file" + ' + 'Button3 + ' + Me.Button3.Location = New System.Drawing.Point(6, 19) + Me.Button3.Name = "Button3" + Me.Button3.Size = New System.Drawing.Size(181, 23) + Me.Button3.TabIndex = 3 + Me.Button3.Text = "1. Choose file" + Me.Button3.UseVisualStyleBackColor = True + ' + 'TextBox1 + ' + Me.TextBox1.Location = New System.Drawing.Point(6, 45) + Me.TextBox1.Name = "TextBox1" + Me.TextBox1.Size = New System.Drawing.Size(669, 20) + Me.TextBox1.TabIndex = 2 + ' + 'txtServiceAddress + ' + Me.txtServiceAddress.Location = New System.Drawing.Point(36, 37) + Me.txtServiceAddress.Name = "txtServiceAddress" + Me.txtServiceAddress.Size = New System.Drawing.Size(485, 20) + Me.txtServiceAddress.TabIndex = 5 + ' + 'Label1 + ' + Me.Label1.AutoSize = True + Me.Label1.Location = New System.Drawing.Point(33, 21) + Me.Label1.Name = "Label1" + Me.Label1.Size = New System.Drawing.Size(81, 13) + Me.Label1.TabIndex = 6 + Me.Label1.Text = "ServiceAdresse" + ' + 'txtServicePort + ' + Me.txtServicePort.EditValue = New Decimal(New Integer() {9000, 0, 0, 0}) + Me.txtServicePort.Location = New System.Drawing.Point(36, 63) + Me.txtServicePort.Name = "txtServicePort" + Me.txtServicePort.Properties.EditValueChangedFiringMode = DevExpress.XtraEditors.Controls.EditValueChangedFiringMode.Buffered + Me.txtServicePort.Properties.MaskSettings.Set("MaskManagerType", GetType(DevExpress.Data.Mask.NumericMaskManager)) + Me.txtServicePort.Size = New System.Drawing.Size(485, 20) + Me.txtServicePort.TabIndex = 7 + ' + 'Button4 + ' + Me.Button4.Location = New System.Drawing.Point(36, 89) + Me.Button4.Name = "Button4" + Me.Button4.Size = New System.Drawing.Size(75, 23) + Me.Button4.TabIndex = 8 + Me.Button4.Text = "Connect" + Me.Button4.UseVisualStyleBackColor = True + ' + 'txtStatus + ' + Me.txtStatus.Location = New System.Drawing.Point(527, 37) + Me.txtStatus.Name = "txtStatus" + Me.txtStatus.Size = New System.Drawing.Size(100, 20) + Me.txtStatus.TabIndex = 9 + ' 'frmFilesystem ' Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font Me.ClientSize = New System.Drawing.Size(800, 450) + Me.Controls.Add(Me.txtStatus) + Me.Controls.Add(Me.Button4) + Me.Controls.Add(Me.txtServicePort) + Me.Controls.Add(Me.Label1) + Me.Controls.Add(Me.txtServiceAddress) + Me.Controls.Add(Me.GroupBox1) Me.Controls.Add(Me.Button1) Me.Name = "frmFilesystem" Me.Text = "frmFilesystem" + Me.GroupBox1.ResumeLayout(False) + Me.GroupBox1.PerformLayout() + CType(Me.txtServiceAddress.Properties, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.txtServicePort.Properties, System.ComponentModel.ISupportInitialize).EndInit() Me.ResumeLayout(False) + Me.PerformLayout() End Sub Friend WithEvents Button1 As Button + Friend WithEvents Button2 As Button + Friend WithEvents GroupBox1 As GroupBox + Friend WithEvents Button3 As Button + Friend WithEvents TextBox1 As TextBox + Friend WithEvents SaveFileDialog1 As SaveFileDialog + Friend WithEvents txtServiceAddress As DevExpress.XtraEditors.TextEdit + Friend WithEvents Label1 As Label + Friend WithEvents txtServicePort As DevExpress.XtraEditors.TextEdit + Friend WithEvents Button4 As Button + Friend WithEvents txtStatus As TextBox End Class diff --git a/GUIs.Test.TestGUI/frmFilesystem.resx b/GUIs.Test.TestGUI/frmFilesystem.resx index 1af7de15..4008a9ee 100644 --- a/GUIs.Test.TestGUI/frmFilesystem.resx +++ b/GUIs.Test.TestGUI/frmFilesystem.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file diff --git a/GUIs.Test.TestGUI/frmFilesystem.vb b/GUIs.Test.TestGUI/frmFilesystem.vb index ad4cfa91..b352ebbb 100644 --- a/GUIs.Test.TestGUI/frmFilesystem.vb +++ b/GUIs.Test.TestGUI/frmFilesystem.vb @@ -1,8 +1,29 @@ -Imports DigitalData.Modules.Logging +Imports System.Collections.Generic +Imports System.IO +Imports System.Numerics ' Verweis auf System.Numerics assembly hinzufügen! +Imports System.Security.Cryptography +Imports DigitalData.GUIs.Common +Imports DigitalData.Modules.Base.IDB.Constants +Imports DigitalData.Modules.Database +Imports DigitalData.Modules.EDMI.API +Imports DigitalData.Modules.EDMI.API.EDMIServiceReference +Imports DigitalData.Modules.Logging +Imports DigitalData.Modules.ZooFlow.State Public Class frmFilesystem + Private Client As Client + Private Logger As Logger + + Private Const STATUS_CONNECTED = "Connection Established" + Private Const STATUS_CONNECTING = "Trying to create connection..." + Private Const STATUS_FAILED = "Connection Failed!" + + Private ConnectionChanged As Boolean = False + + Public Property ServiceAddress As String = "" + Public Property ServiceOnline As Boolean = False Private LogConfig As LogConfig - Private Filesystem As DigitalData.Modules.Filesystem.File + Private MYDD_Filesystem As DigitalData.Modules.Filesystem.File Private ShortName As String = "E:\some_test_file.txt" Private LongName As String = "E:\some_test_file_with_some_more_data_and_with_some_more_data_and_with_some_more_data_and_with_some_more_data_and_with_some_more_data_and_with_some_more_data_and_with_some_more_data_and_with_some_more_data_and_with_some_more_data_and_with_some_more.txt" @@ -10,7 +31,8 @@ Public Class frmFilesystem Private Sub frmFilesystem_Load(sender As Object, e As EventArgs) Handles MyBase.Load Try LogConfig = New LogConfig(LogConfig.PathType.Temp) - Filesystem = New DigitalData.Modules.Filesystem.File(LogConfig) + Logger = LogConfig.GetLogger() + MYDD_Filesystem = New DigitalData.Modules.Filesystem.File(LogConfig) 'Using oWriter = IO.File.CreateText("E:\some_test_file.txt") @@ -34,6 +56,138 @@ Public Class frmFilesystem End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click - Dim oDateString = Filesystem.CreateDateDirectory("E:\") + Dim oDateString = MYDD_Filesystem.CreateDateDirectory("E:\") + End Sub + + Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click + If SaveFileDialog1.ShowDialog = DialogResult.OK Then + ' Fallback: einfache Textdatei schreiben + System.IO.File.WriteAllText(SaveFileDialog1.FileName, "Some text data") + End If + End Sub + + Private Async Function Button2_ClickAsync(sender As Object, e As EventArgs) As Task Handles Button2.Click + ' Import a file via EDMI Globix ImportFile API to test Service.EDMIService ImportFileMethod + Try + If Client Is Nothing OrElse ServiceOnline = False Then + MsgBox("Bitte zuerst Verbindung zum EDMI Service herstellen.", MsgBoxStyle.Exclamation, Text) + Return + End If + + Using ofd As New OpenFileDialog() + ofd.Title = "Datei für Import wählen" + ofd.Filter = "Alle Dateien (*.*)|*.*" + If ofd.ShowDialog() <> DialogResult.OK Then + Return + End If + + Dim oFilePath = ofd.FileName + Dim fi As New FileInfo(oFilePath) + Dim fileBytes As Byte() = IO.File.ReadAllBytes(oFilePath) + + ' Basic user context for testing + Dim user As New DigitalData.Modules.ZooFlow.State.UserState() With { + .UserName = Environment.UserName, + .Language = "de-DE", + .LanguageId = 1031 + } + + Dim oObjectStore As String = "SelectedDocType.ObjectStore" + Dim oIDBDoctypeId As Long = 1 + Dim oObjectKind As String = "DOC" + Dim oProfileId As Integer = 1 + Dim oAttributes As List(Of UserAttributeValue) = Nothing + Dim oOptions As New Options.ImportFileOptions + + Logger.Debug("FilePath: [{0}]", oFilePath) + Logger.Debug("ObjectStore: [{0}]", oObjectStore) + Logger.Debug("ObjectKind: [{0}]", oObjectKind) + Logger.Debug("ProfileId: [{0}]", oProfileId) + Logger.Debug("IDB DoctypeId: [{0}]", oIDBDoctypeId) + + Logger.Info("Running Import") + + Dim oResult = Await My.Application.Service.Client.Globix_ImportFileAsync( + oFilePath, oProfileId, oAttributes, oObjectStore, oObjectKind, oIDBDoctypeId, oOptions) + + Logger.Info("Import result: [{0}]", oResult.OK) + Logger.Info("Imported file got ObjectId [{0}]", oResult.ObjectId) + + If oResult.OK Then + MsgBox("Alles OK") + Else + Logger.Warn("Import failed with message: [{0}] and details [{1}]", oResult.ErrorMessage, oResult.ErrorDetails) + Dim oMsg As String, oTitle As String + If My.Application.User.Language = "de-DE" Then + oMsg = $"Die Datei wurde nicht verarbeitet.{vbNewLine}{vbNewLine}Fehler: {oResult.ErrorMessage}" + oTitle = "Achtung" + Else + oMsg = $"Unexpected Error in FileFlow{vbNewLine}{vbNewLine}Fehler: {oResult.ErrorMessage}" + oTitle = "Attention" + End If + + + MsgBox("Fehler") + End If + End Using + Catch ex As Exception + If Logger IsNot Nothing Then Logger.Error(ex) + MsgBox("Fehler beim Datei-Import.", MsgBoxStyle.Critical, Text) + End Try + End Function + + + Public Class SecureStorageHandler + + + + End Class + + Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click + Try + Dim oIPAddress = txtServiceAddress.Text + Dim oPort = Integer.Parse(txtServicePort.Text) + + Client = New Client(LogConfig, oIPAddress, oPort) + + txtStatus.Text = STATUS_CONNECTING + Dim oResult = Client.Connect() + + If oResult = True Then + ServiceAddress = $"{oIPAddress}:{oPort}" + ServiceOnline = True + txtStatus.Text = STATUS_CONNECTED + My.Application.Service.Client = Client + Logger.Debug("Loading client config..") + Dim oClientConfig = My.Application.Service.Client.ClientConfig + Logger.Debug("Establishing ECM connection..") + Dim oECMConnectionString = oClientConfig.ConnectionStringECM + My.DatabaseECM = New MSSQLServer(My.LogConfig, oECMConnectionString) + Logger.Debug("Establishing IDB connection..") + Dim oIDBConnectionString = oClientConfig.ConnectionStringIDB + My.DatabaseIDB = New MSSQLServer(My.LogConfig, oIDBConnectionString) + + Logger.Debug("Establishing Database connection with fallback..") + My.Database = New DatabaseWithFallback(LogConfig, My.Application.Service.Client, My.DatabaseECM, My.DatabaseIDB) + Else + ServiceAddress = "" + ServiceOnline = True + txtStatus.Text = STATUS_FAILED + ' TODO: Make a connection test that is as elaborate as this one :D + 'Select Case oResult + ' Case ClassService.ConnectionTestResult.NotFound + ' lblStatus.Text = "Dienst konnte nicht gefunden werden. Bitte überprüfen sie Addresse und Port." + ' Case ClassService.ConnectionTestResult.EmptyURI + ' lblStatus.Text = "Bitte tragen Sie eine gültige Dienst Adresse ein." + ' Case ClassService.ConnectionTestResult.Authentication + ' lblStatus.Text = "Authentifizierungsfehler. Prüfen Sie, ob sich Ihr Gerät in der korrekten Domäne befindet." + ' Case Else + ' lblStatus.Text = "Unbekannter Fehler." + 'End Select + End If + Catch ex As Exception + If Logger IsNot Nothing Then Logger.Error(ex) + MsgBox("Fehler beim Verbindungsaufbau", MsgBoxStyle.Critical, Text) + End Try End Sub End Class \ No newline at end of file diff --git a/Service.EDMIService/EDMIService.vbproj b/Service.EDMIService/EDMIService.vbproj index 5edf3300..1c749f52 100644 --- a/Service.EDMIService/EDMIService.vbproj +++ b/Service.EDMIService/EDMIService.vbproj @@ -226,6 +226,7 @@ + Component diff --git a/Service.EDMIService/Methods/IDB/GetFileObject/GetFileObjectMethod.vb b/Service.EDMIService/Methods/IDB/GetFileObject/GetFileObjectMethod.vb index 9b735d52..613c68c2 100644 --- a/Service.EDMIService/Methods/IDB/GetFileObject/GetFileObjectMethod.vb +++ b/Service.EDMIService/Methods/IDB/GetFileObject/GetFileObjectMethod.vb @@ -2,6 +2,7 @@ Imports DigitalData.Modules.Logging Imports DigitalData.Modules.Language Imports System.IO +Imports DigitalData.Services.EDMIService.Security Namespace Methods.IDB.GetFileObject Public Class GetFileObjectMethod @@ -69,6 +70,29 @@ Namespace Methods.IDB.GetFileObject End Function Private Function LoadFileContents(pFilePath As String) As Byte() + Try + Dim password = Environment.GetEnvironmentVariable("DD_FILE_ENCRYPTION_PASSWORD") + If String.IsNullOrWhiteSpace(password) Then + Logger.Warn("No encryption password set (DD_FILE_ENCRYPTION_PASSWORD). Attempting legacy plain read for file [{0}]", pFilePath) + Return ReadPlain(pFilePath) + End If + + Try + ' Try decrypt first (preferred path) + Logger.Debug("Attempting AES decrypt for file [{0}]", pFilePath) + Return SecureFileHandler.DecryptFileToBytes(pFilePath, password) + Catch exDec As Exception + Logger.Warn("Decrypt failed for file [{0}]. Falling back to plain read. Reason: {1}", pFilePath, exDec.Message) + Logger.Error(exDec) + Return ReadPlain(pFilePath) + End Try + + Catch ex As Exception + Logger.Error(ex) + Return Nothing + End Try + End Function + Private Function LoadFileContents_Old(pFilePath As String) As Byte() Try Using oFileStream As New FileStream(pFilePath, FileMode.Open, FileAccess.Read) Using oMemoryStream As New MemoryStream() @@ -85,6 +109,15 @@ Namespace Methods.IDB.GetFileObject End Try End Function + + Private Function ReadPlain(pFilePath As String) As Byte() + Using oFileStream As New FileStream(pFilePath, FileMode.Open, FileAccess.Read, FileShare.Read) + Using oMemoryStream As New MemoryStream() + oFileStream.CopyTo(oMemoryStream) + Return oMemoryStream.ToArray() + End Using + End Using + End Function End Class End Namespace \ No newline at end of file diff --git a/Service.EDMIService/Methods/IDB/NewFile/NewFileMethod.vb b/Service.EDMIService/Methods/IDB/NewFile/NewFileMethod.vb index 98fe1a49..9c38046a 100644 --- a/Service.EDMIService/Methods/IDB/NewFile/NewFileMethod.vb +++ b/Service.EDMIService/Methods/IDB/NewFile/NewFileMethod.vb @@ -3,6 +3,7 @@ Imports DigitalData.Modules.Base.IDB.Constants Imports DigitalData.Modules.Database Imports DigitalData.Modules.Database.MSSQLServer.TransactionMode Imports DigitalData.Modules.Logging +Imports DigitalData.Services.EDMIService.Security Namespace Methods.IDB.NewFile Public Class NewFileMethod @@ -22,6 +23,197 @@ Namespace Methods.IDB.NewFile Logger.Debug("Running [NewFileMethod].") Dim oFilePath As String = Nothing + Try + If pData.File Is Nothing Then + Throw New ArgumentNullException(NameOf(pData.File)) + End If + + If pData.KindType Is Nothing Then + Throw New ArgumentNullException(NameOf(pData.KindType)) + End If + + If pData.StoreName Is Nothing Then + Throw New ArgumentNullException(NameOf(pData.StoreName)) + End If + + If pData.User Is Nothing Then + Throw New ArgumentNullException(NameOf(pData.User)) + End If + + If IsNothing(pData.IDBDoctypeId) Then + Throw New ArgumentNullException(NameOf(pData.IDBDoctypeId)) + End If + + Logger.Debug("Checking if checksum already exists..") + Dim oExistingObjectId = Helpers.TestFileChecksumExists(pData.File.FileChecksum) + If oExistingObjectId > 0 Then + Return New NewFileResponse(oExistingObjectId) + End If + + Logger.Debug("Creating New ObjectId..") + Dim oObjectId = Helpers.NewObjectIdWithTransaction(pData.KindType, pData.User.UserName, Connection, Transaction) + If oObjectId = 0 Then + LogAndThrow("Could not create new ObjectId!") + End If + Logger.Debug("New ObjectId [{0}] created!", oObjectId) + + ' Find ObjectStore by Title + Logger.Debug("Checking for DataStore [{0}].", pData.StoreName) + Dim oStore = GlobalState.ObjectStores. + Where(Function(store) store.Title = pData.StoreName). + SingleOrDefault() + + If oStore Is Nothing Then + LogAndThrow($"DataStore [{pData.StoreName}] does not exist. Exiting.") + End If + Logger.Debug("Using DataStore [{0}].", pData.StoreName) + + ' Get Store base and final path + Logger.Debug("Store BasePath is [{0}]", oStore.Path) + Dim oFinalPath = Helpers.GetFileObjectPath(oStore, pData.File.FileImportedAt) + + ' Ensure target directory exists + Try + If Not IO.Directory.Exists(oFinalPath) Then + IO.Directory.CreateDirectory(oFinalPath) + End If + Catch exDir As Exception + LogAndThrow(exDir, $"Target directory [{oFinalPath}] could not be created.") + End Try + + ' Get filename + Dim oKeepFileName As Boolean = False + If oStore.IsArchive Then + Logger.Debug("Object Store is an archive: [{0}]", oStore.IsArchive) + oKeepFileName = True + End If + + Dim oFileName As String = GetFileObjectFileName(oObjectId, pData.File.FileName, oKeepFileName) + Logger.Debug("Filename is [{0}]", oFileName) + + oFilePath = IO.Path.Combine(oFinalPath, oFileName) + Dim oFileObjectInfo As IO.FileInfo = New IO.FileInfo(oFilePath) + + Dim oFileObjectName As String = oFileObjectInfo.Name + Logger.Debug("File Information for [{0}]:", oFileObjectName) + + Dim oFileObjectSize As Long = pData.File.FileContents.Length ' original (plaintext) size + Logger.Debug("Original Size: [{0}]", oFileObjectSize) + + Dim oOriginalExtension As String = pData.File.FileInfoRaw.Extension.Substring(1) + Logger.Debug("Original Extension: [{0}]", oOriginalExtension) + + Logger.Debug("Checksum: [{0}]", pData.File.FileChecksum) + + ' Retrieve encryption password (environment variable) + Dim encryptionPassword As String = Environment.GetEnvironmentVariable("DD_FILE_ENCRYPTION_PASSWORD") + If String.IsNullOrWhiteSpace(encryptionPassword) Then + LogAndThrow("Encryption password not configured (env DD_FILE_ENCRYPTION_PASSWORD).") + End If + + ' Perform encryption with strict failure handling + Try + Logger.Info("Encrypting and saving file to path [{0}]", oFilePath) + SecureFileHandler.EncryptFileFromBytes(pData.File.FileContents, oFilePath, encryptionPassword) + Catch exEnc As Exception + LogAndThrow(exEnc, $"Could not encrypt/write file [{oFilePath}] to disk!") + End Try + + ' Post-encryption validation: file must exist and contain at least header bytes + Try + Dim fi As New IO.FileInfo(oFilePath) + If Not fi.Exists Then + LogAndThrow($"Encrypted file was not created at [{oFilePath}].") + End If + ' Minimum file size:1 (version) +4 (iterations) +32 (salt) =37 bytes + If fi.Length < 37 Then + LogAndThrow($"Encrypted file at [{oFilePath}] is invalid or truncated (size {fi.Length}).") + End If + Logger.Debug("Encrypted physical file size: [{0}]", fi.Length) + Catch exVal As Exception + ' LogAndThrow above will throw; any other IO errors should also abort here + LogAndThrow(exVal, "Encrypted file validation failed.") + End Try + + '--------------------------------------------------------------------------- + + Logger.Info("Creating IDB FileObject for ObjectId [{0}].", oObjectId) + ' Insert into DB (store original plaintext size for consistency) + Dim oSQL As String = $"EXEC PRIDB_NEW_IDBFO + '{oFinalPath}', + '{oFileObjectName}', + '{oOriginalExtension}', + {oFileObjectSize}, + '{pData.File.FileChecksum}' , + '{pData.User.UserName}', + '{oObjectId}', + {oStore.Id}, + {pData.IDBDoctypeId}" + + Dim oResult As Boolean = DatabaseIDB.ExecuteNonQueryWithConnectionObject(oSQL, Connection, ExternalTransaction, Transaction) + + If oResult = False Then + LogAndThrow("IDB FileObject could not be created!") + End If + + '--------------------------------------------------------------------------- + + Dim oSystemAttributes As New Dictionary(Of String, Object) From { + {Attributes.ATTRIBUTE_ORIGIN_FILENAME, pData.File.FileName}, + {Attributes.ATTRIBUTE_ORIGIN_CREATED, pData.File.FileCreatedAt}, + {Attributes.ATTRIBUTE_ORIGIN_CHANGED, pData.File.FileChangedAt} + } + + For Each oAttribute As KeyValuePair(Of String, Object) In oSystemAttributes + Try + ' Dont write empty attributes + If oAttribute.Value Is Nothing Then + Continue For + End If + + Dim oSuccess = Helpers.SetAttributeValueWithTransaction(Connection, Transaction, oObjectId, oAttribute.Key, oAttribute.Value, pData.User.Language, pData.User.UserName) + If oSuccess Then + Logger.Debug("System Attribute [{0}] written with value [{1}]", oAttribute.Key, oAttribute.Value) + Else + Logger.Warn("System attribute value could not be written") + End If + Catch ex As Exception + LogAndThrow(ex, $"System attribute [{oAttribute.Key}] could not be written!") + End Try + Next + + '--------------------------------------------------------------------------- + + ' Finally, commit the transaction + Transaction?.Commit() + + Return New NewFileResponse(oObjectId) + + Catch ex As Exception + Logger.Warn("Error occurred while creating file!") + Logger.Error(ex) + + Logger.Info("Cleaning up files.") + If Not IsNothing(oFilePath) AndAlso IO.File.Exists(oFilePath) Then + Try + IO.File.Delete(oFilePath) + Catch exInner As Exception + Logger.Warn("Error while cleaning up files.") + Logger.Error(exInner) + End Try + End If + + Logger.Info("Rolling back transaction.") + Transaction?.Rollback() + + Return New NewFileResponse(ex) + + End Try + End Function + Public Function Run_Old(pData As NewFileRequest) As NewFileResponse + Logger.Debug("Running [NewFileMethod Old].") + Dim oFilePath As String = Nothing + Try If pData.File Is Nothing Then Throw New ArgumentNullException(NameOf(pData.File)) @@ -181,7 +373,6 @@ Namespace Methods.IDB.NewFile End Try End Function - Private Function GetFileObjectFileName(IDB_OBJ_ID As Long, pFilename As String, pKeepFilename As Boolean) As String ' TODO: save actual extensions If pKeepFilename Then diff --git a/Service.EDMIService/Security/SecureFileHandler.vb b/Service.EDMIService/Security/SecureFileHandler.vb new file mode 100644 index 00000000..cc4f470f --- /dev/null +++ b/Service.EDMIService/Security/SecureFileHandler.vb @@ -0,0 +1,146 @@ +Imports System.IO +Imports System.Security.Cryptography + +Namespace Security + ''' + ''' Provides secure AES file encryption and decryption using PBKDF2 (Rfc2898DeriveBytes) for key derivation. + ''' File format: + ''' [1 byte Version][4 bytes IterationCount (Int32, big-endian)][32 bytes Salt][Encrypted Payload] + ''' + Public NotInheritable Class SecureFileHandler + Private Sub New() + End Sub + + Private Const CURRENT_VERSION As Byte = 1 + Private Const SALT_LENGTH As Integer = 32 + Private Const KEY_SIZE_BYTES As Integer = 32 ' AES-256 + Private Const IV_SIZE_BYTES As Integer = 16 ' AES Block size (128 bit) + Private Const DEFAULT_ITERATIONS As Integer = 100000 + Private Const BUFFER_SIZE As Integer = 81920 '80KB streaming buffer + + ''' + ''' Encrypts the provided byte array and writes an encrypted file to the target path using streaming. + ''' + Public Shared Sub EncryptFileFromBytes(sourceData() As Byte, targetFilePath As String, password As String, Optional iterations As Integer = DEFAULT_ITERATIONS) + If sourceData Is Nothing Then Throw New ArgumentNullException(NameOf(sourceData)) + If String.IsNullOrWhiteSpace(password) Then Throw New ArgumentNullException(NameOf(password)) + Using fsOut = New FileStream(targetFilePath, FileMode.Create, FileAccess.Write, FileShare.None) + Dim salt = GenerateRandomBytes(SALT_LENGTH) + + ' Write header: Version, Iterations, Salt + fsOut.WriteByte(CURRENT_VERSION) + WriteInt32BigEndian(fsOut, iterations) + fsOut.Write(salt, 0, salt.Length) + + Using keyDerivation = New Rfc2898DeriveBytes(password, salt, iterations) + Dim key = keyDerivation.GetBytes(KEY_SIZE_BYTES) + Dim iv = keyDerivation.GetBytes(IV_SIZE_BYTES) + + Dim aesAlg As System.Security.Cryptography.Aes = System.Security.Cryptography.Aes.Create() + Try + aesAlg.KeySize = KEY_SIZE_BYTES * 8 + aesAlg.BlockSize = IV_SIZE_BYTES * 8 + aesAlg.Mode = CipherMode.CBC + aesAlg.Padding = PaddingMode.PKCS7 + aesAlg.Key = key + aesAlg.IV = iv + + Using crypto = aesAlg.CreateEncryptor() + Using cs = New CryptoStream(fsOut, crypto, CryptoStreamMode.Write) + Using msIn = New MemoryStream(sourceData, writable:=False) + Dim buffer(BUFFER_SIZE - 1) As Byte + Dim read As Integer + Do + read = msIn.Read(buffer, 0, buffer.Length) + If read <= 0 Then Exit Do + cs.Write(buffer, 0, read) + Loop + End Using + cs.FlushFinalBlock() + End Using + End Using + Finally + aesAlg.Dispose() + End Try + End Using + End Using + End Sub + + ''' + ''' Decrypts the encrypted file and returns the plaintext bytes. + ''' + Public Shared Function DecryptFileToBytes(encryptedFilePath As String, password As String) As Byte() + If String.IsNullOrWhiteSpace(encryptedFilePath) Then Throw New ArgumentNullException(NameOf(encryptedFilePath)) + If String.IsNullOrWhiteSpace(password) Then Throw New ArgumentNullException(NameOf(password)) + Using fsIn = New FileStream(encryptedFilePath, FileMode.Open, FileAccess.Read, FileShare.Read) + Dim version = CByte(fsIn.ReadByte()) + If version <> CURRENT_VERSION Then Throw New InvalidDataException("Unsupported file version.") + Dim iterations = ReadInt32BigEndian(fsIn) + Dim salt = New Byte(SALT_LENGTH - 1) {} + ReadExact(fsIn, salt, 0, salt.Length) + + Using keyDerivation = New Rfc2898DeriveBytes(password, salt, iterations) + Dim key = keyDerivation.GetBytes(KEY_SIZE_BYTES) + Dim iv = keyDerivation.GetBytes(IV_SIZE_BYTES) + + Dim aesAlg As System.Security.Cryptography.Aes = System.Security.Cryptography.Aes.Create() + Try + aesAlg.KeySize = KEY_SIZE_BYTES * 8 + aesAlg.BlockSize = IV_SIZE_BYTES * 8 + aesAlg.Mode = CipherMode.CBC + aesAlg.Padding = PaddingMode.PKCS7 + aesAlg.Key = key + aesAlg.IV = iv + + Using crypto = aesAlg.CreateDecryptor() + Using cs = New CryptoStream(fsIn, crypto, CryptoStreamMode.Read) + Using msOut = New MemoryStream() + Dim buffer(BUFFER_SIZE - 1) As Byte + Dim read As Integer + Do + read = cs.Read(buffer, 0, buffer.Length) + If read <= 0 Then Exit Do + msOut.Write(buffer, 0, read) + Loop + Return msOut.ToArray() + End Using + End Using + End Using + Finally + aesAlg.Dispose() + End Try + End Using + End Using + End Function + + Private Shared Function GenerateRandomBytes(length As Integer) As Byte() + Dim data = New Byte(length - 1) {} + Using rng = RandomNumberGenerator.Create() + rng.GetBytes(data) + End Using + Return data + End Function + + Private Shared Sub WriteInt32BigEndian(stream As Stream, value As Integer) + Dim bytes = BitConverter.GetBytes(value) + If BitConverter.IsLittleEndian Then Array.Reverse(bytes) + stream.Write(bytes, 0, bytes.Length) + End Sub + + Private Shared Function ReadInt32BigEndian(stream As Stream) As Integer + Dim bytes = New Byte(3) {} + ReadExact(stream, bytes, 0, 4) + If BitConverter.IsLittleEndian Then Array.Reverse(bytes) + Return BitConverter.ToInt32(bytes, 0) + End Function + + Private Shared Sub ReadExact(stream As Stream, buffer As Byte(), offset As Integer, count As Integer) + Dim totalRead As Integer = 0 + While totalRead < count + Dim read = stream.Read(buffer, offset + totalRead, count - totalRead) + If read <= 0 Then Throw New EndOfStreamException("Unexpected end of stream.") + totalRead += read + End While + End Sub + End Class +End Namespace