From fedc45a88b2a7509069b1b7989c7d9ddc5a4a045 Mon Sep 17 00:00:00 2001 From: Jonathan Jenne Date: Wed, 6 Feb 2019 15:31:22 +0100 Subject: [PATCH] jj: filewatcher --- Filesystem/FileWatcher.vb | 98 ++++++++++++++++++----------- Filesystem/FileWatcherFilters.vb | 61 ++++++++++++++++++ Filesystem/FileWatcherProperties.vb | 10 +++ Filesystem/Filesystem.vbproj | 2 + TestGUI/FolderWatcher.vb | 25 +++++--- 5 files changed, 149 insertions(+), 47 deletions(-) create mode 100644 Filesystem/FileWatcherFilters.vb create mode 100644 Filesystem/FileWatcherProperties.vb diff --git a/Filesystem/FileWatcher.vb b/Filesystem/FileWatcher.vb index 215a4413..6f82047a 100644 --- a/Filesystem/FileWatcher.vb +++ b/Filesystem/FileWatcher.vb @@ -1,25 +1,33 @@ Imports System.IO +Imports DigitalData.Modules.Filesystem +Imports DigitalData.Modules.Filesystem.FileWatcherFilters Imports DigitalData.Modules.Logging + Public Class FileWatcher + ' Internals Private ReadOnly _Logger As Logger Private ReadOnly _Watchers As List(Of FileSystemWatcher) - Private ReadOnly _Files As Dictionary(Of String, FileProperties) + Private ReadOnly _Files As Dictionary(Of String, FileWatcherProperties) + Private ReadOnly _Filters As List(Of BaseFileFilter) + ' Options Private _Path As String - Public OfficeFilters As New List(Of String) From {"docx", "xlsx", "pptx"} + ' Public Events + Public Event FileSaved(ByVal FullName As String, ByVal IsSpecial As Boolean) - Public Sub New(LogConfig As LogConfig, Path As String) + Public Sub New(LogConfig As LogConfig, Path As String, Optional Filters As List(Of BaseFileFilter) = Nothing) _Logger = LogConfig.GetLogger() - _Files = New Dictionary(Of String, FileProperties) + _Files = New Dictionary(Of String, FileWatcherProperties) _Watchers = New List(Of FileSystemWatcher) + _Filters = IIf(IsNothing(Filters), GetDefaultFilters(), Filters) _Path = Path - For Each oFilePath As String In Directory.EnumerateFiles(_Path) + For Each oFilePath In Directory.EnumerateFiles(_Path) Try If IO.File.Exists(oFilePath) Then - _Files.Add(oFilePath, New FileProperties With { + _Files.Add(oFilePath, New FileWatcherProperties With { .CreatedAt = DateTime.Now, .ChangedAt = Nothing }) @@ -31,23 +39,39 @@ Public Class FileWatcher Next End Sub - Public Sub Add(Filter As String, Optional SpecialTreatmentFilters As List(Of String) = Nothing) + Public Sub Add(Filter As String) _Watchers.Add(CreateWatcher(Filter)) End Sub - Public Sub Add(Filters As List(Of String), Optional SpecialTreatmentFilters As List(Of String) = Nothing) - For Each oFilter In Filters - _Watchers.Add(CreateWatcher(oFilter)) + Public Sub Start() + For Each oWatcher In _Watchers + oWatcher.EnableRaisingEvents = True Next End Sub + Public Sub [Stop]() + For Each oWatcher In _Watchers + If Not IsNothing(oWatcher) Then + oWatcher.EnableRaisingEvents = False + oWatcher.Dispose() + End If + Next + End Sub + + Private Function GetDefaultFilters() + Return New List(Of BaseFileFilter) From { + New TempFileFilter, + New OfficeFileFilter + } + End Function + Private Function CreateWatcher(Filter As String) Dim oWatcher = New FileSystemWatcher() With { .Path = _Path, .Filter = Filter, - .NotifyFilter = NotifyFilters.CreationTime _ - Or NotifyFilters.LastAccess _ + .NotifyFilter = NotifyFilters.LastAccess _ Or NotifyFilters.LastWrite _ + Or NotifyFilters.FileName _ Or NotifyFilters.Size _ Or NotifyFilters.FileName _ Or NotifyFilters.Attributes @@ -62,16 +86,29 @@ Public Class FileWatcher End Function Private Sub HandleFileCreated(sender As Object, e As FileSystemEventArgs) - _Files.Add(e.FullPath, New FileProperties()) - _Logger.Info("[Created] " & e.FullPath) + _Files.Add(e.FullPath, New FileWatcherProperties()) + _Logger.Debug("[Created] " & e.FullPath) End Sub + + ''' + ''' This may fire twice for a single save operation, + ''' see: https://blogs.msdn.microsoft.com/oldnewthing/20140507-00/?p=1053/ + ''' Private Sub HandleFileChanged(sender As Object, e As FileSystemEventArgs) _Files.Item(e.FullPath).ChangedAt = DateTime.Now - _Logger.Info("[Changed] " & e.FullPath) + _Logger.Debug("[Changed] " & e.FullPath) + + Dim oShouldRaiseSave As Boolean = Not _Filters.Any(Function(oFilter) + Return oFilter.ShouldFilter(e) + End Function) + + If oShouldRaiseSave Then + RaiseEvent FileSaved(e.FullPath, False) + End If End Sub Private Sub HandleFileDeleted(sender As Object, e As FileSystemEventArgs) _Files.Remove(e.FullPath) - _Logger.Info("[Removed] " & e.FullPath) + _Logger.Debug("[Removed] " & e.FullPath) End Sub Private Sub HandleFileRenamed(sender As Object, e As RenamedEventArgs) @@ -80,31 +117,16 @@ Public Class FileWatcher _Files.Add(e.FullPath, oProperties) ' Soll eine umbenannte datei als NEU gelten? - _Logger.Info("[Renamed] {0} --> {1}", e.OldFullPath, e.FullPath) - End Sub + Dim oShouldRaiseSave = _Filters.Any(Function(oFilter) + Return oFilter.ShouldRaiseSave(e) + End Function) - Public Sub Start() - For Each oWatcher In _Watchers - oWatcher.EnableRaisingEvents = True - Next - End Sub + If oShouldRaiseSave Then + RaiseEvent FileSaved(e.OldFullPath, True) + End If - Public Sub [Stop]() - For Each oWatcher In _Watchers - If Not IsNothing(oWatcher) Then - oWatcher.EnableRaisingEvents = False - oWatcher.Dispose() - End If - Next + _Logger.Debug("[Renamed] {0} --> {1}", e.OldFullPath, e.FullPath) End Sub - Public Class FileProperties - Public CreatedAt As DateTime - Public ChangedAt As DateTime - Public Sub New() - CreatedAt = DateTime.Now - ChangedAt = Nothing - End Sub - End Class End Class diff --git a/Filesystem/FileWatcherFilters.vb b/Filesystem/FileWatcherFilters.vb new file mode 100644 index 00000000..84f70b62 --- /dev/null +++ b/Filesystem/FileWatcherFilters.vb @@ -0,0 +1,61 @@ +Imports System.IO + +''' +''' Built-in filters for FileWatcher that are useful for correctly detecting changes on Office documents (currently Office 2016) +''' +Public Class FileWatcherFilters + ''' + ''' Base Filter that all filters must inherit from + ''' Provides two functions that may be overridden and some useful file extension lists + ''' + Public MustInherit Class BaseFileFilter + Public TempFiles As New List(Of String) From {".tmp", ""} + + Public Overridable Function ShouldFilter(e As FileSystemEventArgs) As Boolean + Return False + End Function + Public Overridable Function ShouldRaiseSave(e As RenamedEventArgs) As Boolean + Return False + End Function + End Class + + ''' + ''' Simple Filter that filters changes made on temporary files + ''' + Public Class TempFileFilter + Inherits BaseFileFilter + + Public Overrides Function ShouldFilter(e As FileSystemEventArgs) As Boolean + Dim oFileInfo As New FileInfo(e.FullPath) + Return TempFiles.Contains(oFileInfo.Extension) + End Function + End Class + + ''' + ''' Filter to detect changes on Office files + ''' + Public Class OfficeFileFilter + Inherits BaseFileFilter + + Public OfficeFiles As New List(Of String) From {".docx", ".pptx", ".xlsx"} + + Public Overrides Function ShouldFilter(e As FileSystemEventArgs) As Boolean + Dim oFileInfo As New FileInfo(e.FullPath) + Return OfficeFiles.Contains(oFileInfo.Extension) And oFileInfo.Name.StartsWith("~") + End Function + + Public Overrides Function ShouldRaiseSave(e As RenamedEventArgs) As Boolean + Dim oIsTransform = OfficeFiles.Any(Function(Extension As String) + Return e.OldName.EndsWith(Extension) + End Function) + + ' Check if it is renamed to a temp file + Dim oIsTempFile = TempFiles.Any(Function(Extension) + Return e.Name.EndsWith(Extension) + End Function) + + + Return oIsTransform And oIsTempFile + End Function + End Class +End Class diff --git a/Filesystem/FileWatcherProperties.vb b/Filesystem/FileWatcherProperties.vb new file mode 100644 index 00000000..5eadecbe --- /dev/null +++ b/Filesystem/FileWatcherProperties.vb @@ -0,0 +1,10 @@ +Public Class FileWatcherProperties + Public Property CreatedAt As DateTime + Public Property ChangedAt As DateTime + Public ReadOnly Property HasChanged As Boolean + Public Sub New() + CreatedAt = DateTime.Now + ChangedAt = Nothing + HasChanged = False + End Sub +End Class diff --git a/Filesystem/Filesystem.vbproj b/Filesystem/Filesystem.vbproj index f7fd9859..720246c0 100644 --- a/Filesystem/Filesystem.vbproj +++ b/Filesystem/Filesystem.vbproj @@ -81,6 +81,8 @@ + + True diff --git a/TestGUI/FolderWatcher.vb b/TestGUI/FolderWatcher.vb index 2dff7c01..aa1dc9c3 100644 --- a/TestGUI/FolderWatcher.vb +++ b/TestGUI/FolderWatcher.vb @@ -1,30 +1,37 @@ Imports System.ComponentModel Imports System.IO Imports DigitalData.Modules.Filesystem +Imports DigitalData.Modules.Filesystem.FileWatcherFilters Imports DigitalData.Modules.Logging Public Class FolderWatcher Private _LogConfig As LogConfig + Private _Logger As Logger Private _Watcher As FileWatcher - Public Sub FileCreated(_sender As Object, _e As FileSystemEventArgs) - ListBox1.Items.Add($"File created: {_e.FullPath}") - End Sub - - Public Sub FileChanged(_sender As Object, _e As FileSystemEventArgs) - ListBox1.Items.Add($"File changed: {_e.FullPath}") - End Sub - + Private Sub FolderWatcher_Load(sender As Object, e As EventArgs) Handles Me.Load _LogConfig = New LogConfig(LogConfig.PathType.CurrentDirectory, Nothing, "MAIN") - _Watcher = New FileWatcher(_LogConfig, "E:\Watcher") + _LogConfig.Debug = True + _Logger = _LogConfig.GetLogger() + Dim oFilters As New List(Of BaseFileFilter) From { + New TempFileFilter, + New OfficeFileFilter + } + _Watcher = New FileWatcher(_LogConfig, "E:\Watcher", oFilters) _Watcher.Add("*.*") + AddHandler _Watcher.FileSaved, AddressOf HandleFileSaved + _Watcher.Start() End Sub + Private Sub HandleFileSaved(FullName As String, IsSpecial As Boolean) + _Logger.Info("{1}File Saved: {0}", FullName, IIf(IsSpecial, "Special ", "")) + End Sub + Private Sub FolderWatcher_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing _Watcher.Stop() End Sub