将条目号附加到 FileSystemWatcher 输出

Posted

技术标签:

【中文标题】将条目号附加到 FileSystemWatcher 输出【英文标题】:appending an entry number to FileSystemWatcher output 【发布时间】:2018-08-06 23:24:34 【问题描述】:

好的,

在这里可能会被视为一个非常业余的帖子,我并不真正擅长 VB 或程序员,但我正在“边做边学”驱动器。

我正在开发一个应用程序,它为用户创建目录,并使用 FileSystemWatcher 跟踪创建的目录和对这些目录的更改。

到目前为止,我已经有了这个小 sn-p(已更新以回答您的 cmets Jimi):

Button_Click 的私有子:

    watchfolder = New System.IO.FileSystemWatcher()
    watchfolder.Path = TodaysScanFolder
    watchfolder.Filter = ""
    watchfolder.IncludeSubdirectories = True

    watchfolder.NotifyFilter = IO.NotifyFilters.DirectoryName
    watchfolder.NotifyFilter = watchfolder.NotifyFilter Or
                       IO.NotifyFilters.FileName
    watchfolder.NotifyFilter = watchfolder.NotifyFilter Or
                       IO.NotifyFilters.Attributes

    AddHandler watchfolder.Created, AddressOf logchange
    AddHandler watchfolder.Deleted, AddressOf logchange

    watchfolder.EnableRaisingEvents = True
    btnStartScan.Enabled = False
    btnStopScan.Enabled = True
End Sub

    Public Sub logchange(ByVal source As Object, ByVal e As _
                        System.IO.FileSystemEventArgs)

    If e.ChangeType = IO.WatcherChangeTypes.Created Then
        txt_folderactivity.Text &= "File " & e.FullPath &
                                 " has been created" & vbCrLf
    End If
    If e.ChangeType = IO.WatcherChangeTypes.Deleted Then
        txt_folderactivity.Text &= "File " & e.FullPath &
                                " has been deleted" & vbCrLf
    End If
End Sub

对于每个额外的活动,我想在此之前将输出附加到 txt_folderactivity.Text 和 1。然后是 2、3,以此类推。

您会看到此文本框,更改时会将输出写入文本文件,如下所示:

Private Sub txt_folderactivity_TextChanged(sender As Object, e As EventArgs) Handles txt_folderactivity.TextChanged
    str = txt_folderactivity.Text
    My.Computer.FileSystem.WriteAllText(LogFileLoc, str, True)
End Sub

我希望该文本文件的每一行/条目从 1、2、3 等开始。另外,正如我所说的 VB 初学者,似乎拼凑了一些看起来有点凌乱但功能强大且对业务至关重要的东西,任何有关 VB 学习资源的良好开端的指针都会非常有帮助。谢谢大家

【问题讨论】:

一些观察:您尚未指定定义了哪些NotifyFiltersChanged() 事件可能会引发不止一次,具体取决于这些过滤器。您错过了Renamed() 事件。这是故意的吗? TextChanged() 事件是不必要的。您需要更新日志,然后使用最后一个条目更新您的提示(文本框)。您的应用程序将处理多少事件(业务关键)?如果 +1/sec.,考虑重构异步。执行,否则您将丢失事件。监控网络路径时,FSW 可能不可靠。 关于数字索引器:您只需要一个整数变量(类范围),它会在事件处理程序中递增,并在每个条目前加上 .ToString() 关于NotifyFilters 的注释。在监视文件更改时,正如我之前所说,Changed() 事件将触发两次,例如,如果您指定 NotifyFilters.LastWriteNotifyFilters.Size 并且修改了监视的文件。总是有可能(尽管经常被忽略)使用多个 FileSystemWatcher 实例来接收不同类型的 .events 的通知,这些事件通常都会引发 FSW Changed() 事件。 只是一个建议,但我发现在日志文件中将时间提前到毫秒(例如DateTime.UtcNow.ToString("HH:mm:ss.fff"))而不是行号更有用 - 后者可以显示在任何合理的文本中编辑器。 你好 Jimi,我已经用要求的信息更新了我的原始帖子。出于此应用程序的目的,我不需要跟踪文件的更改,例如重命名,只需跟踪它们的创建和删除。最终版本将在多台机器上运行,在一天 8-12 小时内跟踪大约 3000 个文件的创建和删除。我真正需要的是输出总共创建的文件数,与最终剩余的文件数。真的觉得我需要对 VB 做一些更深入的阅读,因为事实证明这个应用程序非常具有挑战性。你有什么相关的读物推荐吗? 【参考方案1】:

这是一个继承自FileSystemWatcher的类。 它负责配置、启用和禁用事件的引发,并具有基本的日志记录和报告功能。

FileSystemWatcher的配置:

.BeginInit() - .EndInit()BeginInit() 用于防止在 FileSystemWatcher 设置完成之前引发事件。

.SynchronizingObjectSynchronizingObject 设置为表单组件。由于FileSystemWatcher 事件是从系统线程池编组的,因此可以确保在创建组件的同一线程上调用事件委托。 如果 SynchronizingObject 为 null,则访问组件可能会导致异常,或者更糟的是,导致静默失败。

.InternalBufferSize 内部缓冲区用于存储事件寄存器和文件路径。大于默认值(8192 字节)的缓冲区可以防止缓冲区溢出,这会危及事件的上升。这里设置为 32768 字节。

.Path 对于网络驱动器/共享,请使用 UNC 路径。如何使用它:

定义一个引用FileWatcher Class的公共对象:

Public FileWatch As FileWatcher

然后你可以在需要的时候初始化它:

FileWatch = New FileWatcher("[Path to watch]", 
                            "*",  'All files
                            "[Path to LogFile]", 
                            Me,   'Synchronizing Object
                            Me.[TextBox control used as monitor])

[TextBox control used as monitor] 可以是 Nothing 如果没有使用。

FileWatcher 不会立即开始引发/记录事件; EnableRaisingEvents属性在初始化方法中设置为False。 您可以使用它的方法启动和停止它的活动:

Me.FileWatch.StartWatcher()
Me.FileWatch.StopWatcher()

启动和停止FileWatcher 是一个已注册(记录)的活动。List(Of DateTime) 用于存储这些事件。请参阅FW_EventLogger Class

其他存储信息有:

已删除的文件数。 创建的文件数 注册事件的总数(用于比较)。 日志文件的大小。

ActivityReport() 属性返回一个简单的报告:

Dictionary(Of String, String) = FileWatcher.ActivityReport()

这是日志文件的样子:

00000001 - 2018/02/28 21:00:25 - File D:\Temp\New Text Document.txt has been created
00000002 - 2018/02/28 21:00:29 - File D:\Temp\New Microsoft Access Database.accdb has been created
00000003 - 2018/02/28 21:00:34 - File D:\Temp\New WinZip File.zip has been created
00000004 - 2018/02/28 21:00:44 - File D:\Temp\New Microsoft Access Database.accdb has been deleted
00000005 - 2018/02/28 21:00:44 - File D:\Temp\New Text Document.txt has been deleted
00000006 - 2018/02/28 21:00:44 - File D:\Temp\New WinZip File.zip has been deleted

限制: 如果请求(将 TextBox 控件引用传递给类初始化),则 UI 会通过 同步对象。如果 UI 线程由于某种原因很忙,则 底层 FileSystemWatcher 正在缓冲的事件将堆积起来。这 可以(并且因为它可以)导致事件丢失。这是 内部缓冲区设置为默认值的 4 倍的原因。无论如何,它 如果监视的活动很高,那将永远不够。在正常 条件下,它可以处理 10/秒。事件没有问题。除此之外, 使用 FIFO 队列缓冲区的异步代理方法必须 放置在事件侦听器和数据消费者之间。

基于:

Visual Studio 2013, Update 5
.Net Framework 4.7.1

更新为:

Visual Studio 2017, 15.8.4
.Net Framework 4.7.1

Imports System.Collections.Generic
Imports System.IO
Imports System.Windows.Forms


Public Class FileWatcher
    Inherits FileSystemWatcher

    Private EventLogger As FW_EventLogger
    Private Prompt As TextBox = Nothing

    Public Sub New()
        Me.New("", "", "", Nothing, Nothing)
    End Sub

    Public Sub New(fswPath As String, fswFilter As String, logFile As String, SyncObject As Form, SyncPrompt As TextBox)
        Me.Prompt = SyncPrompt
        Me.EventLogger = New FW_EventLogger With .LogFileName = logFile

        SetupFileWatcher(fswPath, fswFilter, SyncObject)
    End Sub

    Public Sub StartWatcher()
        Me.EventLogger.TimeStart.Add(DateTime.UtcNow)
        If Me.Prompt IsNot Nothing Then
            Me.Prompt.AppendText(String.Format("Logger Start Time: 0" +
                                 Environment.NewLine, DateTime.UtcNow.ToString()))
        End If
        Me.EnableRaisingEvents = True
    End Sub

    Public Sub StopWatcher()
        Me.EnableRaisingEvents = False
        Me.EventLogger.TimeStop.Add(DateTime.UtcNow)
        If Me.Prompt IsNot Nothing Then
            Me.Prompt.AppendText(String.Format("Logger Stop Time: 0" +
                                 Environment.NewLine, DateTime.UtcNow.ToString()))
        End If
    End Sub

    Public ReadOnly Property ActivityReport() As Dictionary(Of String, String)
        Get
            Return Me.CreateActivityReport()
        End Get
    End Property

    Public Property PromptControl As TextBox
        Get
            Return Me.Prompt
        End Get
        Set(value As TextBox)
            Me.Prompt = value
        End Set
    End Property


    Public Sub SetupFileWatcher(fwPath As String, fwFilter As String, SyncObject As Form)
        If fwPath.Length = 0 Then
            Return
        End If

        Me.BeginInit()
        Me.SynchronizingObject = SyncObject
        Me.InternalBufferSize = 32768
        Me.IncludeSubdirectories = True
        Me.Filter = fwFilter
        Me.Path = fwPath
        Me.NotifyFilter = NotifyFilters.FileName Or NotifyFilters.CreationTime
        Me.EnableRaisingEvents = False

        'Set the handler to the events you want to receive
        AddHandler Me.Created, New FileSystemEventHandler(AddressOf Me.OnCreated)
        AddHandler Me.Deleted, New FileSystemEventHandler(AddressOf Me.OnDeleted)
        'The other events, should they become necessary.
        'this.Changed += new FileSystemEventHandler(this.OnChanged);
        'this.Renamed += new RenamedEventHandler(this.OnRenamed);

        Me.EndInit()
    End Sub

    Private Function CreateActivityReport() As Dictionary(Of String, String)
        With Me.EventLogger
            Dim log As New Dictionary(Of String, String)
            log.Add("Created", .FileCreated.ToString())
            log.Add("Deleted", .FileDeleted.ToString())
            log.Add("TotalEvents", .EventsLogged.ToString())
            log.Add("LogFileSize", If(File.Exists(.LogFileName), New FileInfo(.LogFileName).Length.ToString(), "N/A"))
            log.Add("StartTime", If(.TimeStart.Count > 0, .TimeStart.First().ToString(), "Not Started"))
            log.Add("LastStopTime", If(.TimeStop.Count > 0, .TimeStop.Last().ToString(), "Never"))
            log.Add("Status", If(Me.EnableRaisingEvents = True, "Running", "Stopped"))
            Return log
        End With
    End Function

    Protected Overloads Sub OnCreated(sender As Object, e As FileSystemEventArgs)
        Dim Msg As String = "File " & e.FullPath & " has been created"
        Me.EventLogger.Update(Msg, FW_EventLogger.EventType.FileCreated)
        If Me.Prompt IsNot Nothing Then
            Me.Prompt.AppendText(Msg + Environment.NewLine)
            Me.Prompt.ScrollToCaret()
        End If
    End Sub

    Protected Overloads Sub OnDeleted(sender As Object, e As FileSystemEventArgs)
        Dim Msg As String = "File " & e.FullPath & " has been deleted"
        Me.EventLogger.Update(Msg, FW_EventLogger.EventType.FileDeleted)
        If Me.Prompt IsNot Nothing Then
            Me.Prompt.AppendText(Msg + Environment.NewLine)
            Me.Prompt.ScrollToCaret()
        End If
    End Sub

    'The Event Logger Class
    Private Class FW_EventLogger

        Sub New()
            Me.TimeStart = New List(Of DateTime)
            Me.TimeStop = New List(Of DateTime)
        End Sub

        Public Enum EventType As Integer
            FileCreated = 0
            FileDeleted
        End Enum

        Public Property FileDeleted As Integer
        Public Property FileCreated As Integer
        Public Property EventsLogged As Integer
        Public Property TimeStart As List(Of DateTime)
        Public Property TimeStop As List(Of DateTime)
        Public Property LogFileName As String

        Public Sub Update(NewEvent As String, TypeOfEvent As EventType)

            If Me.LogFileName <> String.Empty Then
                If TypeOfEvent = EventType.FileCreated Then Me.FileCreated += 1
                If TypeOfEvent = EventType.FileDeleted Then Me.FileDeleted += 1
                Me.EventsLogged += 1
                Using LogFileWriter As StreamWriter = New StreamWriter(Me.LogFileName, True, Encoding.UTF8)
                    LogFileWriter.WriteLine(Me.EventsLogged.ToString().PadLeft(8, "0"c) +
                                            " - 0 - 1", DateTime.UtcNow.ToString("yyyy/MM/dd hh:mm:ss"), NewEvent)
                End Using
            End If
        End Sub
    End Class

End Class

【讨论】:

以上是关于将条目号附加到 FileSystemWatcher 输出的主要内容,如果未能解决你的问题,请参考以下文章

使用 Visual Studio 2019 将 FileSystemWatcher 作为 Windows 服务

将抓取的数据附加到 JSON 文件

自动将不同表中的 ID 号添加到新条目中?

FileSystemWatcher 无法正常工作

FileSystemWatcher .Net 3.5

使用 FileSystemWatcher 监视目录