I have a filesystemwatcher watching a folder where cameras save the files. It's supposed to add the name/date to a database so they can be deleted at a later date.
watcher = New System.IO.FileSystemWatcher()
Try
watcher.Path = watchFolder
watcher.NotifyFilter = IO.NotifyFilters.DirectoryName
watcher.NotifyFilter = watcher.NotifyFilter Or IO.NotifyFilters.FileName
watcher.NotifyFilter = watcher.NotifyFilter Or IO.NotifyFilters.Attributes
AddHandler watcher.Created, AddressOf addFile
watcher.IncludeSubdirectories = True
watcher.EnableRaisingEvents = True
Catch ex As System.IO.IOException
writeLog("watcher IO error:" & ex.Message)
Catch ex As NullReferenceException
writeLog("watcher NullReferenceException: " & ex.Message)
writeLog("watcher Stack Trace: " & vbCrLf & ex.StackTrace)
Catch ex As Exception
writeLog("watcher error:" & ex.Message)
End Try
Private Sub addFile(ByVal source As Object, ByVal e As System.IO.FileSystemEventArgs)
writeLog("examining: " & e.FullPath)
Try
If e.ChangeType = IO.WatcherChangeTypes.Created Then
Try
Dim fdir As String = IO.Path.GetDirectoryName(e.FullPath)
For Each kvp As KeyValuePair(Of String, Integer) In camList
Dim v1 As String = kvp.Key
Dim v2 As Integer = kvp.Value
If fdir = v1 Then
Using connection As New SQLiteConnection(conn)
connection.Open()
Dim sql As String = "INSERT INTO mycfg (f_name, d_date) VALUES (@value1, @value2)"
Dim fdate As Date = Now.AddDays(v2)
Dim sdate As String = fdate.ToString("yyyy/MM/dd", CultureInfo.InvariantCulture)
Using command As New SQLiteCommand(sql, connection)
command.Parameters.AddWithValue("@value1", e.FullPath)
command.Parameters.AddWithValue("@value2", sdate)
Dim rowsAffected As Integer = command.ExecuteNonQuery()
writeLog(e.FullPath & "-" & $"{rowsAffected} row(s) inserted.")
End Using
End Using
End If
Next
Catch ex As Exception
writeLog("error: " & ex.Message)
End Try
End If
Catch ex As System.IO.IOException
writeLog("watcher IO error2:" & ex.Message)
Catch ex As NullReferenceException
writeLog("watcher NullReferenceException2: " & ex.Message)
writeLog("watcher Stack Trace2: " & vbCrLf & ex.StackTrace)
Catch ex As Exception
writeLog("watcher error2:" & ex.Message)
End Try
End Sub
If I put an Exit Sub
after writeLog at the start of the addFile sub then it will log the new file created but when I remove it I get the following error in my windows event log and the service stops before it can write to my log file.
Application: CamWatcher.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.IO.FileNotFoundException
at CamWatcher.Service1.addFile(System.Object, System.IO.FileSystemEventArgs)
at System.IO.FileSystemWatcher.OnCreated(System.IO.FileSystemEventArgs)
at System.IO.FileSystemWatcher.NotifyFileSystemEventArgs(Int32, System.String)
at System.IO.FileSystemWatcher.CompletionStatusChanged(UInt32, UInt32, System.Threading.NativeOverlapped*)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)
The files are video files that are created but not closed until the time limit is up on the camera or lack of motion detected. I am thinking that might be the problem but I am not sure how to handle that problem. Nothing is being done to the file itself in my code so I am a bit baffled as to how the watcher can detect it then crash because it isn't there.
The stack trace tells us the error happens in the addFile()
method. Significantly, the writeLog()
method is not part of the stack trace, which is the only other custom method in use. This lets eliminate access to a log file as the cause of the error.
With that in mind, there is only one possible line of code in the addFile()
method directly that could throw a FileNotFoundException
:
connection.Open()
That line could throw if the sqlite file is missing or similarly not available. Nothing else in addFile()
method does any file I/O. Even the Path.GetDirectoryName()
method is just string operations. This means the concern that a video file may still be in use shouldn't matter, because we're not actually trying to do anything with the file itself: only the file name, and we already have that in memory.
This also helps explain why the exception isn't caught by your final exception handler, as the exception is likely faulting the sqlite dll. While it's not clear which provider you are using, the Microsoft.Data.Sqlite
and System.Data.Sqlite
providers are not documented to throw this exception; they would instead throw an SqliteException
. This means you are likely using the Devart.Data.Sqlite
provider. Note this provider's Open() method documentation does not share what exceptions it throws.
So make sure your connection string is correct and the sqlite file is available to the user account running the service, and potentially switch to Microsoft's provider for hopefully better exception behavior.
fwiw, I also suggest two changes to the method structure. First, use a guard clause for the IO.WatcherChangeTypes.Created
test. Second, it would be somewhat strange to have a List(Of KeyValuePair(Of String Integer))
instead of a Dictionary(Of String, Integer)
. And if you really do have the latter, it seems like the camlist
loop should be a dictionary lookup. This will also let you remove one level of try/catch nesting:
Private Sub addFile(ByVal source As Object, ByVal e As System.IO.FileSystemEventArgs)
writeLog($"examining: {e.FullPath}")
If e.ChangeType <> IO.WatcherChangeTypes.Created Then Exit Sub
Dim sql As String = "INSERT INTO mycfg (f_name, d_date) VALUES (@value1, @value2)"
Try
Dim fdir As String = IO.Path.GetDirectoryName(e.FullPath)
Dim v2 As Integer
If Not camList.TryGetValue(fdir, v2) Then Exit Sub
Using connection As New SQLiteConnection(conn), _
command As New SQLiteCommand(sql, conn)
command.Parameters.AddWithValue("@value1", e.FullPath)
command.Parameters.AddWithValue("@value2", Now.AddDays(v2).ToString("yyyy/MM/dd", CultureInfo.InvariantCulture))
connection.Open()
Dim rowsAffected As Integer = command.ExecuteNonQuery()
writeLog($"{e.FullPath}-{rowsAffected} row(s) inserted.")
End Using
Catch ex As System.IO.IOException
writeLog("watcher IO error2:" & ex.Message)
Catch ex As NullReferenceException
writeLog("watcher NullReferenceException2: " & ex.Message)
writeLog("watcher Stack Trace2: " & vbCrLf & ex.StackTrace)
Catch ex As Exception
writeLog("watcher error2:" & ex.Message)
End Try
End Sub
And we have now notably reduced the method length, indentation, and general complexity.
But if that really is a List(Of KeyValuePair(Of String, Integer))
, I would use a .Where()
operation like this:
Private Sub addFile(ByVal source As Object, ByVal e As System.IO.FileSystemEventArgs)
writeLog($"examining: {e.FullPath}")
If e.ChangeType <> IO.WatcherChangeTypes.Created Then Exit Sub
Dim sql As String = "INSERT INTO mycfg (f_name, d_date) VALUES (@value1, @value2)"
Try
Using connection As New SQLiteConnection(conn), _
command As New SQLiteCommand(sql, conn)
command.Parameters.AddWithValue("@value1", e.FullPath)
Dim v2Param As SqliteParameter = command.Parameters.Add("@value2", SqliteType.DateTime)
conn.Open()
Dim pairs = camList.Where(Function(kvp) kvp.Key = IO.Path.GetDirectoryName(e.FullPath))
For Each kvp As KeyValuePair(Of String, Integer) In pairs
v2param.Value = Today.AddDays(v2).ToString("yyyy/MM/dd", , CultureInfo.InvariantCulture)
Dim rowsAffected As Integer = command.ExecuteNonQuery()
writeLog($"{e.FullPath}-{rowsAffected} row(s) inserted.")
Next
End Using
Catch ex As System.IO.IOException
writeLog("watcher IO error2:" & ex.Message)
Catch ex As NullReferenceException
writeLog("watcher NullReferenceException2: " & ex.Message)
writeLog("watcher Stack Trace2: " & vbCrLf & ex.StackTrace)
Catch ex As Exception
writeLog("watcher error2:" & ex.Message)
End Try
End Sub
And this will be more efficient, because you only need to open/close the connection once.