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