Why do i get an IOException (The process cannot access the file because it is being used by another process.), when i copy the same file 3 Times into Folder A?
class FileWatcher {
FileSystemWatcher fsw;
FileInfo file;
string destination = #"C:\FileMover\B\";
Random random;
public FileWatcher() {
fsw = new FileSystemWatcher();
random = new Random();
fsw.Path = #"C:\FileMover\A";
fsw.Created += fsw_Created;
fsw.EnableRaisingEvents = true;
}
void fsw_Created(object sender, FileSystemEventArgs e) {
string destinationFileName = destination + e.Name;
if (!File.Exists(destinationFileName)) {
file = new FileInfo(e.FullPath);
file.MoveTo(destinationFileName);
}
else {
file = new FileInfo(e.FullPath);
file.MoveTo(destinationFileName + random.Next());
}
file = null;
}
}
Main:
class Program {
static void Main(string[] args) {
FileWatcher watcher = new FileWatcher();
while (Console.ReadKey().Key != ConsoleKey.Q) {
}
}
}
After 2 times folder B contains 2 files (sourceFileName and sourceFileName1274968236)
When i debug the section no Exception will be thrown.
Created might be raised before the file is flushed (?) into the destination. Try using Changed and check for e.ChangeType==WatcherChangeTypes.Created.
ref: http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.created(v=vs.110).aspx
Edit:
The Changed event is raised after Created, if this is due to a race-condition, probably will help but problem may still happen.
The OnCreated event is raised as soon as a file is created. If a file
is being copied or transferred into a watched directory, the OnCreated
event will be raised immediately, followed by one or more OnChanged
events.
Found this: Wait Until File Is Completely Written
and this: Is there a way to check if a file is in use?
Using tips from #celerno and the links he shared, here is what I found to work:
public FileWatcher()
{
fsw = new FileSystemWatcher();
random = new Random();
fsw.Path = #"C:\FileMover\A";
fsw.Created += fsw_Created;
fsw.Changed += fsw_Created;
fsw.EnableRaisingEvents = true;
}
and change the fsw_Created to:
if(!IsFileLocked(fi)){
if (!File.Exists(destinationFileName))
{
fi = new FileInfo(e.FullPath);
fi.MoveTo(destinationFileName);
}
else
{
fi = new FileInfo(e.FullPath);
string fileName = destinationFileName + random.Next();
fi.MoveTo(fileName);
}
fi = null;
}
Where IsFileLocked is directly taken from the link that was mentioned by #celerno.
I dropped many many copies of the file and it never threw an exception. I'm guessing an Changed event happens right after the file gets released, otherwise, if it is locked at the time of the attempted move, the Created never gets called, so the file just sits there. It appears you need both in this situation.
I just wanted to edit in order to add that the exception seemed to only get thrown when you're copying a file of the same name over and over into the source directory. Guessing the lock on that filename just needs to be released before you can copy another with the same name.
Related
I am a beginner in C# WPF, and I am trying to create an app, which uses DataTable and writes this DataTable to an XML file using DataTable.WriteXML() method. Then the XML file is being read by the DataGrid from the ListView (selectedList).
private void refreshDataGrid(DataGrid dataGridName, ListView selectedList, string path)
{
FileStream file;
//check if the item from ListView is selected
if (selectedList.SelectedItem != null)
{
try
{
file = File.Open(path + "\\" + selectedList.SelectedItem.ToString() + ".xml", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
}
catch (Exception x)
{
MessageBox.Show(x.Message.ToString());
return;
}
//clear the defaultData DataSet so it doesn't have any content
defaultData.Tables.Clear();
//read XML file
defaultData.ReadXml(file);
file.Close();
dataGridName.ItemsSource = defaultData.Tables[0].DefaultView;
}
DataGrid is editable by the user and when he does Edit the content, It is saved to an XML file, and then FileWatcher is watching for changes in Last Access to the file, so when the content of a file is changing, the datagrid will update.
FileSystemWatcher fileWatcher = new();
private void CreateFileWatcher(string path)
{
fileWatcher.Path = path;
fileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess | NotifyFilters.Size | NotifyFilters.FileName;
fileWatcher.Filter = "*.xml";
fileWatcher.Changed += FileWatcher_Changed;
fileWatcher.EnableRaisingEvents = true;
}
// Event for filewatcher throws an Exception
private void FileWatcher_Changed(object sender, FileSystemEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)(() =>
{
try
{
refreshDataGrid(dataGridName, userListView, path);
}
catch(Exception)
{
return;
}
}));
}
So the issue is, when i create a NEW XML FILE while the app is running, and then user opens this file in a DataGrid and edits a field in the DataGrid, the data is saved to an XML file as it should be, and FileWatcher is Invoked, so when the FileWatcher is trying to refresh the Datagrid I get this message:
Could not find UIElementAutomationPeer.cs
You need to find
UIElementAutomationPeer.cs to view the source for the current call
stack frame System.ArgumentNullException: „Value cannot be null.
Arg_ParamName_Name”.
Unhandled exception of type „System.ArgumentNullException” in PresentationCore.dll
Value cannot be null.
When i create an XML file, and restart the app, everything works normally. Also when the file was created before launching the app, FileWatcher is working fine. I guess newly created files (during app runtime) are not updated to the app itself, and it needs to refresh the UI but I don't know how. My FileWatcher is also global, so maybe that could be an issue, or should I refresh the FileWatcher somehow? Any help would be appreciated.
EDIT: I think there is a problem while creating a file in my program, probably the app wants to use the same file twice or can't access it, here is the code:
private void saveTableToXML()
{
DataTable dt = new DataTable();
dt = ((DataView)dataGridName.ItemsSource).ToTable();
try
{
FileStream file = File.Create(path + "\\.xml");
dt.WriteXml(file, XmlWriteMode.WriteSchema,false);
file.Close();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
}
By default a Script written by the machine saves the files to Local/Server Path folder, but due to network issue, both the folders are not in sync. I have witten a C# Window Service program using FileSystemWatcher, DiffEngine, System.Timers and PingService as below coding to handle this.
To monitor a local folder OnChange Event, Ping server IP whether is success/fail before Compare/Copy to Server path, When Ping Fail it will goes to logtemp folder, system timer handle this and Ping again before re-dump the logtemp files.
I do not know how to use threading for this. Where should be my system timer code when the ping fails?
protected override void OnStart(string[] args)
{ //all watcher config here//
watcher.Path = "path";
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Filter = "filename_company-Pg1_Product*";
watcher.Changed += new FileSystemEventHandler(LogFileSystemChanges);
watcher.EnableRaisingEvents = true;}
private void LogFileSystemChanges(object sender, FileSystemEventArgs e)
{
FileInfo sourcepath = new FileInfo(e.FullPath);
FileInfo destPath = new FileInfo(Path.Combine(dFile, e.Name));
FileInfo _tempPath = new FileInfo(Path.Combine(tempPath, e.Name));
if (PingService())
//PingService Bool Type....Ping Specific IP Before Compare/Copy
{
if (!destPath.Exists)
{
LogEvent(destPath + " DOES NOT EXIST!! ");
CopyFunction.CopyFile(sourcepath, destPath, true, true);
}
else
{
if (BinaryDiff(sFile, Path.Combine(dFile, e.Name)))
//DiffEngine If Source & Diff are Different is TRUE.
{
CopyFunction.CopyFile(sourcepath, destPath, true, true);
}
}
string msg = string.Format("Filename {0} are {1} now at {2} ", _
e.Name, e.ChangeType, DateTime.Now.ToString());
LogEvent(msg);
}
else
{
CopyFunction.CopyFile(sourcepath, _tempPath, true, true);
}
}
Use NServiceBus (with what they call a Saga)
Its an open source project that lets you foolproof your code against cases where the network is down.
Or are you just asking how to make a thread?
If so see MSDN on examples for creating threads
I want to monitor a log file of our PBX for changes. I made a small program that does just that with a FileSystemWatcher.
Now it's getting strange: The FileSystemWatcher never fires the Changed-Event when I simply start the program. Despite the fact that the log file really has changed. But when I open the directory in the Windows Explorer where the log file is located, the program works as expected. But only as long as the Explorer Window stays open... what the..?
Operating System: Windows Server 2008 R2
EDIT: Sorry, here is the code:
class Program
{
static void Main(string[] args)
{
new LogFileWatcher(#"C:\PBX\Dial.log");
System.Console.Read();
}
}
public class LogFileWatcher
{
public string LogFilePath { get; private set; }
private DateTime _lastLogFileWriteTime;
public LogFileWatcher(string path)
{
LogFilePath = path;
var directoryName = Path.GetDirectoryName(LogFilePath);
var fileName = Path.GetFileName(LogFilePath);
var fsw = new FileSystemWatcher { Path = directoryName, Filter = fileName };
fsw.Changed += fsw_Changed;
fsw.EnableRaisingEvents = true;
}
private void fsw_Changed(object sender, FileSystemEventArgs e)
{
// Get and fix the last write time of the log file
var fixLastWriteTime = File.GetLastWriteTime(LogFilePath);
// Don't do anything when file didn't change since last time
if (fixLastWriteTime == _lastLogFileWriteTime) return;
Console.WriteLine("File changed on: {0} - ID:{1}", DateTime.Now.ToLongTimeString(), Guid.NewGuid());
// Save last write time of the log file
_lastLogFileWriteTime = fixLastWriteTime;
}
}
EDIT2: Maybe this is important: The log file is in use by the PBX Windows-Service! I can open it with Notepad though.
For optimization reasons, the FileStream.Flush()-Method doesn't flush metadata anymore (Vista and later Microsoft operating systems). Therefore the FileSystemWatcher gets no file notification and won't fire the Changed-Method.
http://connect.microsoft.com/VisualStudio/feedback/details/94772/filestream-flush-does-not-flush-the-file-in-the-correct-way-to-work-with-filesystemwatcher-or-native-readdirectorychangesw
Consider this code:
string dir = Environment.CurrentDirectory + #"\a";
Directory.CreateDirectory(dir);
FileSystemWatcher watcher = new FileSystemWatcher(dir);
watcher.IncludeSubdirectories = false;
watcher.EnableRaisingEvents = true;
Console.WriteLine("Deleting " + dir);
Directory.Delete(dir, true);
if (Directory.Exists(dir))
{
Console.WriteLine("Getting dirs of " + dir);
Directory.GetDirectories(dir);
}
Console.ReadLine();
Interestingly this throws an UnauthorizedAccessException on Directory.GetDirectories(dir).
Deleting the watched directory returns without error, but Directory.Exists() still returns true and the directory is still listed. Furthermore accessing the directory yields "Access denied" for any program. Once the .NET application with the FileSystemWatcher exits the directory vanishes.
How can I watch a directory while still allowing it to be properly deleted?
You did in fact delete the directory. But the directory won't be physically removed from the file system until the last handle that references it is closed. Any attempt to open it in between (like you did with GetDirectories) will fail with an access denied error.
The same mechanism exists for files. Review FileShare.Delete
Try this line:
if (new DirectoryInfo(dir).Exists)
instead of:
if (Directory.Exists(dir))
You should use FileSystemInfo.Refresh. After .Refresh() .Exists shows correct result:
var dir = new DirectoryInfo(path);
// delete dir in explorer
System.Diagnostics.Debug.Assert(dir.Exists); // true
dir.Refresh();
System.Diagnostics.Debug.Assert(!dir.Exists); // false
Unfortunately FileSystemWatcher has taken a handle to the directory this means that when the directory is deleted that there is still a handle to the directory marked as PENDING DELETE. I've tried some experiments and it seems that you can use the Error event handler from FileSystemWatcher to identify when this happens.
public myClass(String dir)
{
mDir = dir;
Directory.CreateDirectory(mDir);
InitFileSystemWatcher();
Console.WriteLine("Deleting " + mDir);
Directory.Delete(mDir, true);
}
private FileSystemWatcher watcher;
private string mDir;
private void MyErrorHandler(object sender, FileSystemEventArgs args)
{
// You can try to recreate the FileSystemWatcher here
try
{
mWatcher.Error -= MyErrorHandler;
mWatcher.Dispose();
InitFileSystemWatcher();
}
catch (Exception)
{
// a bit nasty catching Exception, but you can't do much
// but the handle should be released now
}
// you might not be able to check immediately as your old FileSystemWatcher
// is in your current callstack, but it's a start.
}
private void InitFileSystemWatcher()
{
mWatcher = new FileSystemWatcher(mDir);
mWatcher.IncludeSubdirectories = false;
mWatcher.EnableRaisingEvents = true;
mWatcher.Error += MyErrorHandler;
}
I am using the FileSystemWatcher to notify when the new files gets created in the network directory. We process the text files(about 5KB size) and delete them immediately when the new file gets created in the directory. If the FileSystemWatcher windows service stops for some reason we have to look for the unprocessed files after it gets back up and running. How can I handle if the new file comes while processing the old files from the directory? Any examples please?
Thank you,
Here is the code example I have with simple form.
public partial class Form1 : Form
{
private System.IO.FileSystemWatcher watcher;
string tempDirectory = #"C:\test\";
public Form1()
{
InitializeComponent();
CreateWatcher();
GetUnprocessedFiles();
}
private void CreateWatcher()
{
//Create a new FileSystemWatcher.
watcher = new FileSystemWatcher();
watcher.Filter = "*.txt";
watcher.NotifyFilter = NotifyFilters.FileName;
//Subscribe to the Created event.
watcher.Created += new FileSystemEventHandler(watcher_FileCreated);
watcher.Path = #"C:\test\";
watcher.EnableRaisingEvents = true;
}
void watcher_FileCreated(object sender, FileSystemEventArgs e)
{
//Parse text file.
FileInfo objFileInfo = new FileInfo(e.FullPath);
if (!objFileInfo.Exists) return;
ParseMessage(e.FullPath);
}
void ParseMessage(string filePath)
{
// Parse text file here
}
void GetUnprocessedFiles()
{
// Put all txt files into array.
string[] array1 = Directory.GetFiles(#"C:\test\");
foreach (string name in array1)
{
string path = string.Format("{0}{1}", tempDirectory, name)
ParseMessage(path);
}
}
}
When the process starts do the following:
first get the contents of the folder
process every file (end delete them as you already do now)
repeat until no files are in the folder (check again here, since a new file could have been placed in the folder).
start the watcher
For any of our services that use the FileSystemWatcher we always process all the files that exist in the directory first, prior to starting the watcher. After the watcher has been started we then start a timer (with a fairly long interval) to handle any files that appear in the directory without triggering the watcher (it does happen from time to time). That usually covers all the possibilities.