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 DigitalData.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 ''' ''' List of opened files containing the filepath that was opened and the document id ''' Private ReadOnly OpenFiles As New List(Of OpenFile) ''' ''' List of files that changed and are processed by the client, ie. updated ''' 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 DigitalData.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