I'd like to create an application where instead of only watching a root hierarchy folder, ... I watch each folder individually (with watch subfolders turned off)
However, I can't seem to be able to get the delete logic right. I want to be able to delete any folder in the hierarchy, either internally within the application, or externally, say via windows explorer.
When I try either of these, it appears I get locking issues, as the delete commands fail to execute.
If I disable the watching, then everything seems to work. So I'm assuming the problem is trying to delete a folder that is being watched.
Is there anyway of getting around this? I'd ideally like the fileSystemWatcher to automatically stop watching when the folder its watching has been deleted.
public MainWindow()
{
InitializeComponent();
fsw1 = new FileSystemWatcher()
{
Path = "Folder",
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess | NotifyFilters.FileName | NotifyFilters.DirectoryName,
IncludeSubdirectories = false,
Filter = "",
EnableRaisingEvents = true
};
fsw1.Deleted += new FileSystemEventHandler(OnFileSystemItemDeleted);
fsw2 = new FileSystemWatcher()
{
Path = "Folder/Folder",
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess | NotifyFilters.FileName | NotifyFilters.DirectoryName,
IncludeSubdirectories = false,
Filter = "",
EnableRaisingEvents = true
};
fsw2.Deleted += new FileSystemEventHandler(OnFileSystemItemDeleted);
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
//fsw1.EnableRaisingEvents = false;
//fsw2.EnableRaisingEvents = false;
System.IO.Directory.Delete("Folder", true);
}
void OnFileSystemItemDeleted(object sender, FileSystemEventArgs e)
{
}
FileSystemWatcher fsw1, fsw2;
Currently, you are using fsw1 and fsw2 in order to watch data of any type that might get deleted at any point of time.
Although, i do not see the point of creating a FSW for each folder, here is an answer that might help:
In order to watch for the folders themselves, you need to create a FSW for each folder you would like to observe.
You can then setup the NotifyFilter of the FSW to DirectoryName as follows: folderSystemWatcher.NotifyFilter = NotifyFilter.DirectoryName
see an example of this here.
So one of the FSWs tell you that a folder is deleted, you can then stop, dispose or do whatever to the FSW that is watching that deleted folder.
I did not try that out, but this is how i would have done it, i guess...
Related
I am trying to figure out, how to watch only certain way of files:
e.g.
Folder will populate files with the same name, but different extension (.zip, .another)
The file will have a certain name e.g. ASK_QUESTION_VERSION_XXXXXXX.zip, where the XXXXXX will change.
Since the folder will have different files populated e.g. DIFFERENT_FILE_XXXXXX.zip, with similar naming, I would only want to watch for the first files being placed.
How can I filter this here? Here is a look at the code:
public void watch(List<string> servicesInvolved, int timeout)
{
watcher.Path = sourcePath;
watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName
| NotifyFilters.DirectoryName | NotifyFilters.CreationTime;
watcher.Filter = "*.*";
// The below did not work, e.g. compilation errors, could not add two filters.
//watcher.Filter.Add("*.zip");
//watcher.Filters.Add("FileName*");
watcher.Created += (sender, e) => OnChanged(sender, e, servicesInvolved, timeout);
watcher.EnableRaisingEvents = true;
}
This is somehow a simple answer as per #CodeCaster watcher.Filter = MyFolder*Name*.zip;
I am new to C# and have to develop a Windows Form application in C#. This application should track the following things.
Monitor the CD / DVD drives both external and internal.
Monitor the files which are created, modified and deleted on the CD/DVD drives.
I am able to get system notification for CD/DVD drive insertion by RegisterNotification and by tracking WM_DEVICECHANGE messages in the WndProc method.
The above implementation lets me know when a new device has been attached to the PC.
The problem I am facing is how track the file changes which happen on the CD/DVD (Writes / Modification). One option is to poll for the files in the CD / DVD as a background job. But, this will be as the last option.
I have found IMAPIthrough which we can write to CD/DVDs but I just need to monitor the file changes for audit purposes.
Kindly point me to right direction on how to receive file changes on the CD/DVD notification in my program ?
I have tried FileSystemWatcher but it doesn't seem to work with CD/DVD drives.
Updated on 07-Feb-2018:
The another approach I could find was via WMIqueries which are attached to WMI Events. I have found a question Best way to detect dvd insertion in drive c# which could also hold the answer. I wanted to know if the detection of DVD file system modification is feasible in WMI and if any experts can share the query for the same. I hope if Arshad would be able to help in this area.
Approach 1 : Using FileSystemWatcher
public void ListOpticalDiscDrivesAndWatchRootDirectory()
{
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
if (drive.IsReady && drive.DriveType == DriveType.CDRom)
{
var rootDirectory = drive.RootDirectory.ToString();
Console.WriteLine(rootDirectory);
Watch(rootDirectory);
}
}
}
private void Watch(string path)
{
var watcher = new FileSystemWatcher
{
Path = path,
NotifyFilter = NotifyFilters.Attributes |
NotifyFilters.CreationTime |
NotifyFilters.DirectoryName |
NotifyFilters.FileName |
NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.Security |
NotifyFilters.Size,
Filter = "*.*",
EnableRaisingEvents = true
};
watcher.Changed += new FileSystemEventHandler(OnChanged);
}
private void OnChanged(object source, FileSystemEventArgs e)
{
Console.WriteLine("Something changed!");
}
Approach 2 : Using WMI
There's a code project sample (VBScript) describing how to use WMI for file system monitoring. I used the query from that sample in the C# snippet below :
using System;
using System.Management;
public class OpticalDriveWatcher
{
private ManagementEventWatcher _wmiWatcher = new ManagementEventWatcher();
public ManagementEventWatcher WmiWatcher
{
get { return _wmiWatcher; }
}
private void OnWmiEventReceived(object sender, EventArrivedEventArgs e)
{
Console.WriteLine("WMI event!");
}
public void WatchWithWMI(string path)
{
string queryString = "Select * From __InstanceOperationEvent "
+ "WITHIN 2 "
+ "WHERE TargetInstance ISA 'CIM_DataFile' "
+ $"And TargetInstance.Drive='{path}'";
WqlEventQuery wmiQuery = new WqlEventQuery(queryString);
WmiWatcher.Query = wmiQuery;
WmiWatcher.Start();
}
}
The catch is that CIM_DataFile returns only instances of files on local fixed disks. You can call this as follows
var detector = new OpticalDriveDetector();
var drive = "I:"; //You could get the optical drive you want to watch with DriveInfo as described in approach 1
detector.WatchWithWMI(drive);
detector.WmiWatcher.EventArrived += detector.OnWmiEventReceived;
Both approaches worked fine for me when I tested with a DVD-RAM.
I have a console app which is checking a logfile for newly updated data, if yes, then copies them in a db.
Currently I'm using FileSystemWatcher to track any changes in the log.
What I would like to do is, to track if a new logfile is created, if yes, then to invoke myMethod() in the new logfile.
Since I'm new in C# I have a question, should a create a second fileSystemWatcher, or there's a way to update the current one in order to include also this case? I already checked stackoverflow and although I found many posts on fileSystemWatcher, I'm not really sure on how to proceed.
Here's my code:
foreach (string path in paths)
{
if (path.Length > 0)
{
logFile.read(path, errorListAndLevel);
FileSystemWatcher logWatcher = new FileSystemWatcher();
logWatcher.Path = Path.GetDirectoryName(path);
logWatcher.Filter = Path.GetFileName(path);
logWatcher.IncludeSubdirectories = false;
logWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size;
logWatcher.Changed += new FileSystemEventHandler(delegate (object sender, FileSystemEventArgs e)
{
logFile.myMethod(errorListAndLevel, e.FullPath.ToString());
});
logWatcher.EnableRaisingEvents = true;
}
}
I thought of creating a logWatcher.Created... but it isn't working.
logWatcher.Created += new FileSystemEventHandler(delegate (object sender, FileSystemEventArgs e)
{
logFile.myMethod(errorListAndLevel, e.FullPath.ToString());
});
The problem is the following line
logWatcher.Filter = Path.GetFileName(path);
It filters all files and makes the fileWatcher only whatch that one file. Now this makes the fileWatcher not calling the event Created unless a second a file with the exact same name will be created.
When you comment that line it should work fine.
Additionaly you could do this instead:
logWatcher.Filter = "*.log";
This will filter all files with the ending .log which is what you want from what I can see.
I have an application that requires two files to process data. A zip file containing the actual data then a control file that says what to do with said data.
These files are downloaded via sftp to a staging directory. Once the zip file is complete, I need to check and see if the control file is there as well. They share a naming prefix only(Eg. 100001_ABCDEF_123456.zip is paired with 100001_ABCDEF_control_file.ctl.
I am trying to find a way to wait for the zip file to finishing downloading then move the files on the fly, while maintaining the directory structure as that is important for the next step in processing.
Currently I am waiting till the sftp worker finishes then calling robocopy to move everything. I would like a more polished approach.
I have tried several things and I get the same results. Files download but never move. For some reason I just cannot get the compare to work correctly.
I have tried using a FileSystemWatcher to look for the rename from filepart to zip but it seems to miss several downloads and for some reason the function dies when I get to my foreach to search the directory for the control file.
Below is the FileSystemWatcher event, I am calling this for created and changed.
Also below is the setup for the filesystemwatcher.
watcher.Path = #"C:\Sync\";
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
watcher.Filter = "*.zip";
watcher.NotifyFilter = NotifyFilters.Attributes |
NotifyFilters.CreationTime |
NotifyFilters.FileName |
NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.Size |
NotifyFilters.Security |
NotifyFilters.CreationTime |
NotifyFilters.DirectoryName;
watcher.Created += Watcher_Changed;
watcher.Changed += Watcher_Changed;
private void Watcher_Changed(object sender, FileSystemEventArgs e)
{
var dir = new DirectoryInfo(e.FullPath.Substring(0, e.FullPath.Length - e.Name.Length));
var files = dir.GetFiles();
FileInfo zipFile = new FileInfo(e.FullPath);
foreach (FileInfo file in files)
{
MessageBox.Show(file.Extension);
if (file.Extension == "ctl" && file.Name.StartsWith(e.Name.Substring(0, (e.Name.Length - 14))))
{
file.CopyTo(#"C:\inp\");
zipFile.CopyTo(#"C:\inp\");
}
}
}
Watcher_Changed is going to get called for all sorts of things, and not every time it's called will you want to react to it.
The first thing you should do in the event handler is try to exclusively open zipFile. If you cannot do it, ignore this event and wait for another event. If this is an FTP server, every time a new chunk of data is written to disk, you'll get a changed event. You could also put something on a "retry" queue or use some other mechanism to check to see if the file available at a later time. I have a similar need in our system, and we try every 5 seconds after we notice a first change. Only once we can exclusively open the file for writing, do we allow it to move on to the next step.
I would tighten up your assumptions about what the filename looks like. You're limiting the search to *.zip, but don't depend on only your .zip files existing in that target directory. Validate that the parsing you're doing of the filename isn't hitting unexpected values. You may also want to check that dir.Exists() before calling dir.GetFiles(). That could be throwing exceptions.
As to missing events, see this good answer on buffer overflows: FileSystemWatcher InternalBufferOverflow
The FileSystemWatcher class is notoriously tricky to use correctly, because you will get multiple events for a single file that is being written to, moved or copied, as #WillStoltenberg also mentioned in his answer.
I have found that it is much easier just to setup a task that runs periodically (e.g. every 30 seconds). For your problem, you could easily do something like the below. Note that a similar implementation using a Timer, instead of the Task.Delay, may be preferable.
public class MyPeriodicWatcher
{
private readonly string _watchPath;
private readonly string _searchMask;
private readonly Func<string, string> _commonPrefixFetcher;
private readonly Action<FileInfo, FileInfo> _pairProcessor;
private readonly TimeSpan _checkInterval;
private readonly CancellationToken _cancelToken;
public MyPeriodicWatcher(
string watchPath,
string searchMask,
Func<string, string> commonPrefixFetcher,
Action<FileInfo, FileInfo> pairProcessor,
TimeSpan checkInterval,
CancellationToken cancelToken)
{
_watchPath = watchPath;
_searchMask = string.IsNullOrWhiteSpace(searchMask) ? "*.zip" : searchMask;
_pairProcessor = pairProcessor;
_commonPrefixFetcher = commonPrefixFetcher;
_cancelToken = cancelToken;
_checkInterval = checkInterval;
}
public Task Watch()
{
while (!_cancelToken.IsCancellationRequested)
{
try
{
foreach (var file in Directory.EnumerateFiles(_watchPath, _searchMask))
{
var pairPrefix = _commonPrefixFetcher(file);
if (!string.IsNullOrWhiteSpace(pairPrefix))
{
var match = Directory.EnumerateFiles(_watchPath, pairPrefix + "*.ctl").FirstOrDefault();
if (!string.IsNullOrEmpty(match) && !_cancelToken.IsCancellationRequested)
_pairProcessor(
new FileInfo(Path.Combine(_watchPath, file)),
new FileInfo(Path.Combine(_watchPath, match)));
}
if (_cancelToken.IsCancellationRequested)
break;
}
if (_cancelToken.IsCancellationRequested)
break;
Task.Delay(_checkInterval, _cancelToken).Wait().ConfigureAwait(false);
}
catch (OperationCanceledException)
{
break;
}
}
}
}
You will need to provide it with
the path to monitor
the search mask for the first file (i.e. *.zip)
a function delegate that gets the common file name prefix from the zip file name
an interval
the delegate that will perform the moving and receives the FileInfo for the pair to be processed / moved.
and a cancellation token to cleanly cancel monitoring.
In your pairProcessor delegate, catch IO exceptions, and check for a sharing violation (which likely means writing the file has not yet completed).
For some reason, my FileSystemWatcher is not firing any events whatsoever. I want to know any time a new file is created, deleted or renamed in my directory. _myFolderPath is being set correctly, I have checked.
Here is my current code:
public void Setup() {
var fileSystemWatcher = new FileSystemWatcher(_myFolderPath);
fileSystemWatcher.NotifyFilter = NotifyFilters.LastAccess |
NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
fileSystemWatcher.Changed += FileSystemWatcherChanged;
fileSystemWatcher.Created += FileSystemWatcherChanged;
fileSystemWatcher.Deleted += FileSystemWatcherChanged;
fileSystemWatcher.Renamed += FileSystemWatcherChanged;
fileSystemWatcher.Filter = "*.*";
fileSystemWatcher.EnableRaisingEvents = true;
}
private void FileSystemWatcherChanged(object sender, FileSystemEventArgs e)
{
MessageBox.Show("Queue changed");
listBoxQueuedForms.Items.Clear();
foreach (var fileInfo in Directory.GetFiles(_myFolderPath, "*.*", SearchOption.TopDirectoryOnly))
{
listBoxQueuedForms.Items.Add(fileInfo));
}
}
You seem to be creating the FileSystemWatcher as a local variable in the setup method. This will of course go out of scope at the end of the method and may well be getting tidied up at that point, thus removing the watches.
Try creating the FSW at a point where it will be persisted (eg a program level variable) and see if that sorts you out.
My problem was that I expected certain actions to cause the FileSystemWatcher Changed event to fire. For example, moving a file (clicking and dragging) from the desktop to the watched location did not raise an event but copying an existing file and pasting a new copy of it (there by creating a new file to the file system and not simply moving an existing one) caused the Changed event to be raised.
My solution was to add every NotifyFilter to my FileSystemWatcher. This way I am notified in all cases where the FileSystemWatcher is able to notify me.
NOTE that it isn't entirely intuitive/obvious as to which filters will notify you for specific cases. For example, I expected that if I included FileName that I would be notified of any changes to an existing file's name...instead Attributes seem to handle that case.
watcher.NotifyFilter = NotifyFilters.Attributes |
NotifyFilters.CreationTime |
NotifyFilters.FileName |
NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.Size |
NotifyFilters.Security;
Use this setter to enable the trigger:
watcher.EnableRaisingEvents = true;
My issue was that I expected it watches subdirectories also but it doesn't do it by default. If you would like to monitor also subdirectories then set IncludeSubdirectories property to true (it's false by default):
fileSystemWatcher.IncludeSubdirectories = true;
We just had a very similar problem, where moving a folder did not trigger the expected events. The solution was to hard copy the entire folder, rather than just moving it.
DirectoryCopy(".", ".\\temp", True)
Private Shared Sub DirectoryCopy( _
ByVal sourceDirName As String, _
ByVal destDirName As String, _
ByVal copySubDirs As Boolean)
' Get the subdirectories for the specified directory.
Dim dir As DirectoryInfo = New DirectoryInfo(sourceDirName)
If Not dir.Exists Then
Throw New DirectoryNotFoundException( _
"Source directory does not exist or could not be found: " _
+ sourceDirName)
End If
Dim dirs As DirectoryInfo() = dir.GetDirectories()
' If the destination directory doesn't exist, create it.
If Not Directory.Exists(destDirName) Then
Directory.CreateDirectory(destDirName)
End If
' Get the files in the directory and copy them to the new location.
Dim files As FileInfo() = dir.GetFiles()
For Each file In files
Dim temppath As String = Path.Combine(destDirName, file.Name)
file.CopyTo(temppath, False)
Next file
' If copying subdirectories, copy them and their contents to new location.
If copySubDirs Then
For Each subdir In dirs
Dim temppath As String = Path.Combine(destDirName, subdir.Name)
DirectoryCopy(subdir.FullName, temppath, true)
Next subdir
End If
End Sub