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,58 +39,10 @@ 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)) - Next - End Sub - - Private Function CreateWatcher(Filter As String) - Dim oWatcher = New FileSystemWatcher() With { - .Path = _Path, - .Filter = Filter, - .NotifyFilter = NotifyFilters.CreationTime _ - Or NotifyFilters.LastAccess _ - Or NotifyFilters.LastWrite _ - Or NotifyFilters.Size _ - Or NotifyFilters.FileName _ - Or NotifyFilters.Attributes - } - - AddHandler oWatcher.Created, AddressOf HandleFileCreated - AddHandler oWatcher.Changed, AddressOf HandleFileChanged - AddHandler oWatcher.Deleted, AddressOf HandleFileDeleted - AddHandler oWatcher.Renamed, AddressOf HandleFileRenamed - - Return oWatcher - End Function - - Private Sub HandleFileCreated(sender As Object, e As FileSystemEventArgs) - _Files.Add(e.FullPath, New FileProperties()) - _Logger.Info("[Created] " & e.FullPath) - End Sub - Private Sub HandleFileChanged(sender As Object, e As FileSystemEventArgs) - _Files.Item(e.FullPath).ChangedAt = DateTime.Now - _Logger.Info("[Changed] " & e.FullPath) - End Sub - Private Sub HandleFileDeleted(sender As Object, e As FileSystemEventArgs) - _Files.Remove(e.FullPath) - _Logger.Info("[Removed] " & e.FullPath) - End Sub - - Private Sub HandleFileRenamed(sender As Object, e As RenamedEventArgs) - Dim oProperties = _Files.Item(e.OldFullPath) - _Files.Remove(e.OldFullPath) - _Files.Add(e.FullPath, oProperties) - ' Soll eine umbenannte datei als NEU gelten? - - _Logger.Info("[Renamed] {0} --> {1}", e.OldFullPath, e.FullPath) - End Sub - Public Sub Start() For Each oWatcher In _Watchers oWatcher.EnableRaisingEvents = True @@ -98,13 +58,75 @@ Public Class FileWatcher Next End Sub - Public Class FileProperties - Public CreatedAt As DateTime - Public ChangedAt As DateTime + 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.LastAccess _ + Or NotifyFilters.LastWrite _ + Or NotifyFilters.FileName _ + Or NotifyFilters.Size _ + Or NotifyFilters.FileName _ + Or NotifyFilters.Attributes + } + + AddHandler oWatcher.Created, AddressOf HandleFileCreated + AddHandler oWatcher.Changed, AddressOf HandleFileChanged + AddHandler oWatcher.Deleted, AddressOf HandleFileDeleted + AddHandler oWatcher.Renamed, AddressOf HandleFileRenamed + + Return oWatcher + End Function + + Private Sub HandleFileCreated(sender As Object, e As FileSystemEventArgs) + _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.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.Debug("[Removed] " & e.FullPath) + End Sub + + Private Sub HandleFileRenamed(sender As Object, e As RenamedEventArgs) + Dim oProperties = _Files.Item(e.OldFullPath) + _Files.Remove(e.OldFullPath) + _Files.Add(e.FullPath, oProperties) + ' Soll eine umbenannte datei als NEU gelten? + + Dim oShouldRaiseSave = _Filters.Any(Function(oFilter) + Return oFilter.ShouldRaiseSave(e) + End Function) + + If oShouldRaiseSave Then + RaiseEvent FileSaved(e.OldFullPath, True) + End If + + _Logger.Debug("[Renamed] {0} --> {1}", e.OldFullPath, e.FullPath) + End Sub + - 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