386 lines
17 KiB
VB.net
386 lines
17 KiB
VB.net
Imports System.Data.SqlClient
|
|
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
|
|
Inherits BaseMethod
|
|
|
|
Private ReadOnly Connection As SqlConnection
|
|
Private ReadOnly Transaction As SqlTransaction
|
|
|
|
Public Sub New(pLogConfig As LogConfig, pDatabaseIDB As MSSQLServer, pDatabaseECM As MSSQLServer, pGlobalState As GlobalState)
|
|
MyBase.New(pLogConfig, pDatabaseIDB, pDatabaseECM, pGlobalState)
|
|
|
|
Connection = DatabaseIDB.GetConnection()
|
|
Transaction = Connection.BeginTransaction()
|
|
End Sub
|
|
|
|
Public Function Run(pData As NewFileRequest) As NewFileResponse
|
|
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))
|
|
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)
|
|
|
|
' 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
|
|
Logger.Debug("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)
|
|
|
|
Try
|
|
Using oStream = New IO.FileStream(oFilePath, IO.FileMode.Create, IO.FileAccess.Write)
|
|
Logger.Info("Saving file to path [{0}]", oFilePath)
|
|
oStream.Write(pData.File.FileContents, 0, oFileObjectSize)
|
|
oStream.Flush(True)
|
|
oStream.Close()
|
|
End Using
|
|
Catch ex As Exception
|
|
LogAndThrow(ex, $"Could not write file [{oFilePath}] to disk!")
|
|
End Try
|
|
|
|
'---------------------------------------------------------------------------
|
|
|
|
Logger.Info("Creating IDB FileObject for ObjectId [{0}].", oObjectId)
|
|
' Insert into DB
|
|
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
|
|
Private Function GetFileObjectFileName(IDB_OBJ_ID As Long, pFilename As String, pKeepFilename As Boolean) As String
|
|
' TODO: save actual extensions
|
|
If pKeepFilename Then
|
|
Return pFilename
|
|
Else
|
|
Return $"{IDB_OBJ_ID}.ddfo"
|
|
End If
|
|
End Function
|
|
End Class
|
|
|
|
End Namespace |