237 lines
8.6 KiB
VB.net
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 |