2025-08-13 09:53:44 +02:00

237 lines
8.6 KiB
VB.net

Imports System.IO
Imports System.Text
Imports System.Timers
Imports DigitalData.Modules.Logging
Imports DigitalData.Modules.Base
Namespace DocumentResultList
Public Class Watcher
Inherits BaseClass
Private WithEvents FileOpenTimer As New Timer
Private FileEx As Modules.Filesystem.File
Private EnableWatching As Boolean = True
' TODO: Hashes for checking if the opened file was modified externally
Private HashOriginalFile As String = Nothing
Private HashOpenedFile As String = Nothing
Private Const FILE_OPEN_HANDLE_INTERVAL = 2000
''' <summary>
''' List of opened files containing the filepath that was opened and the document id
''' </summary>
Private ReadOnly OpenFiles As New List(Of OpenFile)
''' <summary>
''' List of files that changed and are processed by the client, ie. updated
''' </summary>
Private ReadOnly ProcessedFiles As New List(Of OpenFile)
Public Event FileChanged As EventHandler(Of FileChangedArgs)
Public Event FileOpened As EventHandler(Of FileOpenedArgs)
Public Class OpenFile
Public Document As Document
Public ProcessId As Integer
Public FilePath As String
Public CurrentlyProcessing As Boolean = False
Public Exited As Boolean = False
End Class
Public Class FileChangedArgs
Public File As OpenFile
End Class
Public Class FileOpenedArgs
Public File As OpenFile
End Class
Public Sub New(pLogConfig As LogConfig)
MyClass.New(pLogConfig, True)
End Sub
Public Sub New(pLogConfig As LogConfig, pEnableWatching As Boolean)
MyBase.New(pLogConfig)
FileEx = New Modules.Filesystem.File(pLogConfig)
EnableWatching = pEnableWatching
End Sub
Public Async Function OpenDocument(pDocument As Document) As Task(Of Boolean)
Dim oResult As Tuple(Of Integer, String) = Nothing
If pDocument.FullPath IsNot Nothing AndAlso pDocument.FullPath.Trim <> String.Empty Then
oResult = OpenFileFromPath(pDocument)
ElseIf pDocument.Extension IsNot Nothing AndAlso pDocument.Contents IsNot Nothing Then
oResult = Await OpenFileFromByteArray(pDocument)
End If
If IsNothing(oResult) Then
Logger.Warn("Process Id was empty. File [{0}] could not be opened.", pDocument.Id)
Return False
End If
If EnableWatching = False Then
Logger.Debug("File was opened. Watching and Events are disabled.")
Return True
End If
Dim oProcessId = oResult.Item1
Dim oFilePath = oResult.Item2
Logger.Debug("File [{0}] opened with ProcessId [{1}]", oFilePath, oProcessId)
Dim oOpenFile = New OpenFile With {
.Document = pDocument,
.FilePath = oFilePath,
.ProcessId = oProcessId
}
OpenFiles.Add(oOpenFile)
RaiseEvent FileOpened(Me, New FileOpenedArgs With {.File = oOpenFile})
If FileOpenTimer.Enabled = False Then
' Waiting a while before actually starting the timer to allow for
' opening the file without checking for use already.
Await Task.Delay(FILE_OPEN_HANDLE_INTERVAL)
FileOpenTimer.Interval = FILE_OPEN_HANDLE_INTERVAL
FileOpenTimer.Start()
End If
Return True
End Function
Public Sub FileSaved(pOpenFile As OpenFile)
OpenFiles.Remove(pOpenFile)
ProcessedFiles.Remove(pOpenFile)
End Sub
Private Async Function OpenFileFromByteArray(pDocument As Document) As Task(Of Tuple(Of Integer, String))
Try
Dim oTempPath = Path.Combine(Path.GetTempPath(), Constants.TEMP_PATH_SUBFOLDER)
Dim oDirectory = Directory.CreateDirectory(oTempPath)
Dim oFileName = $"{pDocument.Id}-{Now.GetUnixTimestamp}.{pDocument.Extension}"
Dim oFilePath = Path.Combine(oTempPath, oFileName)
Using oMemoryStream As New MemoryStream(pDocument.Contents)
Using oFileStream As New FileStream(oFilePath, FileMode.Create, FileAccess.Write)
Await oMemoryStream.CopyToAsync(oFileStream)
End Using
End Using
Dim oProcessId = DoOpenFile(oFilePath)
Return New Tuple(Of Integer, String)(oProcessId, oFilePath)
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Private Function OpenFileFromPath(pDocument As Document) As Tuple(Of Integer, String)
Try
Dim oProcessId = DoOpenFile(pDocument.FullPath)
Dim oResult = New Tuple(Of Integer, String)(oProcessId, pDocument.FullPath)
Return oResult
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Private Function DoOpenFile(pFilePath As String) As Integer
Try
Dim _Process = New Process
_Process.StartInfo.FileName = pFilePath
_Process.EnableRaisingEvents = True
If EnableWatching = True Then
AddHandler _Process.Exited, AddressOf Process_Exited
End If
_Process.Start()
Return _Process.Id
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Private Function Process_Exited(sender As Object, e As EventArgs) As Boolean
Logger.Debug("Process is exited")
Dim oProcess As Process = sender
Dim oOpenFile = OpenFiles.
Where(Function(file) file.ProcessId = oProcess.Id).
SingleOrDefault()
oOpenFile.Exited = True
Return True
End Function
Private Sub FileOpenTimer_Elapsed() Handles FileOpenTimer.Elapsed
Try
For Each oOpenFile In OpenFiles
' All files that are currently processe/updated on the outside,
' will not be checked again.
Dim oFileIsProcessed = ProcessedFiles.Contains(oOpenFile)
Logger.Debug($"File is processed: [{oFileIsProcessed}]")
If oFileIsProcessed = True Then
Continue For
End If
' Check if the file is currently in use, and skip if it is.
Dim oIsLocked = FileEx.TestFileIsLocked(oOpenFile.FilePath)
Dim oIsExited = oOpenFile.Exited
Logger.Debug($"File is locked: [{oIsLocked}]")
Logger.Debug($"File is exited: [{oIsExited}]")
If oIsLocked Or oIsExited = False Then
Continue For
End If
' If this point is reached, we assume the file was closed again after opening it.
' ------
Logger.Debug($"File Closed")
' Compute the current hash of the file and compare it with the one
' in the database.
Dim oOldHash = oOpenFile.Document.FileHash
Dim oNewHash = FileEx.GetChecksum(oOpenFile.FilePath)
Logger.Debug($"Old Hash: [{oOldHash}]")
Logger.Debug($"New Hash: [{oNewHash}]")
' If the the file did not change, remove it from the watch list
If oNewHash.Equals(oOldHash) Then
ProcessedFiles.Remove(oOpenFile)
OpenFiles.Remove(oOpenFile)
Continue For
End If
' If this point is reached, we assume the file changed.
' ------
Logger.Debug($"File Changed")
' The File changed, so mark the file as being processed/updated
' and notify any listeners of the FileChanged event
ProcessedFiles.Add(oOpenFile)
RaiseEvent FileChanged(Me, New FileChangedArgs() With {.File = oOpenFile})
Next
Catch ex As Exception
Logger.Error(ex)
End Try
End Sub
End Class
End Namespace