How to implement file processing in Windows Service? - c#

How do I create a monitoring Windows Service (C#) for several directories to scan for new files and (if there are any) edit, rename, move them somewhere else? I have created a WorkerTask() but it works only for files that are in directory when I start the service, not for the ones I put there later. It has to run 24/7.
private void WorkerTask() {
while (running) {
// only 1 input dir in this case
string[] filePaths = Directory.GetFiles(input_dir, "*.jpg");
if (filePaths.Lenght > 0)
{
foreach (String file_path in filePaths)
{
// some other operations before moving
File.Move(file_path, output_file_path);
}
}
}
}
How can I constantly scan for new (only complete!) files that are being uploaded to this folder? It has to run with like a maximum of 2-3 seconds delay between scans, so that as soon as the file lands in folder it's processed and moved. I've seen FileSystemWatcher() but I think trying to implement it for multiple input folders might not be a good idea.

public void Start()
{
FileSystemWatcher fsw = new FileSystemWatcher();
fsw.Path = "\\server\share"; //or use your inputdir
fsw.NotifyFilter = NotifyFilters.Size; //(several others available)
fsw.Filter = "*.jpg";
fsw.Changed += new FileSystemEventHandler(OnChanged);
fsw.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
//do stuff.
}

Use the FileSystemWatcher class, that's what it's for.
It has several events that you can subscribe for - when files are created, updated and deleted.

Related

C# and the FileSystemWatcher

i have written a service in C# which should move backup files (*.bak and *.trn) from a database server to a special backup server. This works quite well so far. The problem is that it tries to move single files twice. This fails of course. I have configured FileSystemWatcher as follows:
try
{
m_objWatcher = new FileSystemWatcher();
m_objWatcher.Filter = m_strFilter;
m_objWatcher.Path = m_strSourcepath.Substring(0, m_strSourcepath.Length - 1);
m_objWatcher.IncludeSubdirectories = m_bolIncludeSubdirectories;
m_objWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess; // | NotifyFilters.CreationTime;
m_objWatcher.Changed += new FileSystemEventHandler(objWatcher_OnCreated);
}
catch (Exception ex)
{
m_objLogger.d(TAG, m_strWatchername + "InitFileWatcher(): " + ex.ToString());
}
Is it possible that the Watcher produces an event twice for the same file? If I set the filter to CreationTime only, it does not react at all.
How do I have to set the Watcher to fire an event only once per file?
Thanks in advance for your help
The documentation states that common file system operations might raise more than one event. Check under the Events and Buffer Sizes heading.
Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.
It also offers a few guidelines, including:
Keep your event handling code as short as possible.
To that end, you could use your FileSystemWatcher.Changed event to queue files for processing, then process them later. This is a quick example of what that might look like using an instance of System.Threading.Timer to process the queue.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
public class ServiceClass
{
public ServiceClass()
{
_processing = false;
_fileQueue = new ConcurrentQueue<string>();
_timer = new System.Threading.Timer(ProcessQueue);
// Schedule the time to run in 5 seconds, then again every 5 seconds.
_timer.Change(5000, 5000);
}
private void objWatcher_OnChanged(object sender, FileSystemEventArgs e)
{
// Just queue the file to be processed later. If the same file is added multiple
// times, we'll skip the duplicates when processing the files.
_fileQueue.Enqueue(e.FilePath);
}
private void ProcessQueue(object state)
{
if (_processing)
{
return;
}
_processing = true;
var failures = new HashSet<string>();
try
{
while (_fileQueue.TryDequeue(out string fileToProcess))
{
if (!File.Exists(fileToProcess))
{
// Probably a file that was added multiple times and it was
// already processed.
continue;
}
var file = new FileInfo(fileToProcess);
if (FileIsLocked(file))
{
// File is locked. Maybe you got the Changed event, but the file
// wasn't done being written.
failures.Add(fileToProcess);
continue;
}
try
{
fileInfo.MoveTo(/*Your destination*/);
}
catch (Exception)
{
// File failed to move. Add it to the failures so it can be tried
// again.
failutes.Add(fileToProcess);
}
}
}
finally
{
// Add any failures back to the queue to try again.
foreach (var failedFile in failures)
{
_fileQueue.Enqueue(failedFile);
}
_processing = false;
}
}
private bool IsFileLocked(FileInfo file)
{
try
{
using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read,
FileShare.None))
{
stream.Close();
}
}
catch (IOException)
{
return true;
}
return false;
}
private System.Threading.Timer _timer;
private bool _processing;
private ConcurrentQueue<string> _fileQueue;
}
Credit where it's due, I took FileIsLocked from this answer.
Some other things you might need to consider:
What happens if your FileSystemWatcher misses an event? [The documentation] does state that it is possible.
Note that a FileSystemWatcher may miss an event when the buffer size is exceeded. To avoid missing events, follow these guidelines:
Increase the buffer size by setting the InternalBufferSize property.
Avoid watching files with long file names, because a long file name contributes to filling up the buffer. Consider renaming these files using shorter names.
Keep your event handling code as short as possible.
What happens if your service crashes, but the process writing backup files continues to write them? When you restart your service, will it pick those files up and move them?
I tried all sorts of ideas to stop this. The events are too close together... it can't be stopped in the FileChanged Event. Here is my working solution:
private System.Timers.Timer timer;
private FileSystemWatcher fwatcher;
static void Main(string[] args)
{
new Program();
}
private Program()
{
timer = new System.Timers.Timer(100);
timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
timer.AutoReset = false; // only once
fwatcher = new FileSystemWatcher();
fwatcher.Path = filePath;
fwatcher.Filter = fileName;
fwatcher.NotifyFilter = NotifyFilters.LastWrite;
fwatcher.Changed += new FileSystemEventHandler(FileChanged);
fwatcher.EnableRaisingEvents = true;
while (IsRunning)
{
Thread.Sleep(100);
}
Thread.Sleep(100);
}
private void FileChanged(object sender, FileSystemEventArgs e)
{
timer.Start();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
Console.WriteLine("file has changed!");
}
The timer will only fire once each time the file is changed.

C# File System Watchers is only keeping count of Copied files

I am currently trying to keep a counter on c# on a local file folder for new files that are created.
I have two sub directories to CD and LP that I have to keep checking.
File System Watcher is only keeping track of my copied folders.
Basically I need to keep track of folders created starting with EM* but my code shows the counter increasing when I copy and paste folders and not when I create the EM* folders.
e.g EM1 EM2 only EM2-copy increases the counter and even then sometimes it increases +2
static int LPcounter { get; set; }
static int CDcounter { get; set; }
static int LPCreated;
static int CDCreated;
FileSystemWatcher CDdirWatcher = new FileSystemWatcher();
FileSystemWatcher LPdirWatcher = new FileSystemWatcher();
public Form1()
{
InitializeComponent();
while (true)
watch();
}
public void watch()
{
CDdirWatcher.Path = #"C:\Data\LotData\CD";
CDdirWatcher.Filter = "EM*";
CDdirWatcher.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.LastWrite;
CDdirWatcher.EnableRaisingEvents = true;
CDdirWatcher.Created += CDdirWatcher_Created;
LPdirWatcher.Path = #"C:\Data\LotData\LP";
LPdirWatcher.Filter = "EM*";
LPdirWatcher.NotifyFilter = NotifyFilters.DirectoryName;
LPdirWatcher.EnableRaisingEvents = true;
LPdirWatcher.Created += LPdirWatcher_Created;
}
private static void CDdirWatcher_Created(object sender, FileSystemEventArgs e)
{
CDCreated += 1;
}
private static void LPdirWatcher_Created(object sender, FileSystemEventArgs e)
{
LPCreated += 1;
}
Your code is right, try to create directory with console and MKDIR, it will work. If you create a directory from Explorer its first created as "New folder" and then renamed.
From Microsoft web: copy and paste is interpreteds as rename
https://learn.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher.notifyfilter?view=netframework-4.8
The operating system and FileSystemWatcher object interpret a
cut-and-paste action or a move action as a rename action for a folder
and its contents
From the same docu, events can raise multipletimes:
Common file system operations might raise more than one event. For
example, when a file is moved from one directory to another, several
OnChanged and some OnCreated and OnDeleted events might be raised.

FileSystemWatcher not firing events when file written using fprintf or printf

Yes there are many similar questions but none of them address my unique situation.
There is a separate c++ process writing the file using c++ printf and fprintf.
filename i am trying to watch is info_20160525.log
My fileSystemWatcher in winform C# application gets notification when the writer process writes to the file AND I physically access the file i-e F5 the folder or have it open in textpad and click the opened file or right click the file but I never get any event notification when I dont physically interact with the file.
Also, when I shutdown the writer application I do get the notification.
Here is my code.
public bool StartUp(string fullfilepath, int linenumber)
{
if (!File.Exists(fullfilepath))
return false;
if (!LogClass.CheckPathExists(m_applicationPath))
return false;
try
{
FileInfo info = new FileInfo(fullfilepath);
m_filename = fullfilepath;
m_fileWatcher = new FileSystemWatcher(info.DirectoryName, info.Name);
m_fileWatcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.LastAccess
| NotifyFilters.LastWrite | NotifyFilters.Size ;
m_fileWatcher.Changed += m_fileWatcher_Changed;
m_fileWatcher.Error += m_fileWatcher_Error;
m_fileWatcher.Created += m_fileWatcher_Created;
m_fileWatcher.Deleted += m_fileWatcher_Deleted;
m_fileWatcher.Disposed += m_fileWatcher_Disposed;
m_fileWatcher.Renamed += m_fileWatcher_Renamed;
m_linesRead = linenumber;
m_fileWatcher.EnableRaisingEvents = true;
}
catch(Exception e)
{
LogClass.LogError(e, "Trouble accessing the file" + fullfilepath, m_applicationPath);
}
return true;
}
These are the handlers. I have breakpoints in each one of them but I never get a trigger unless ofcourse I physically interact with the file.
void m_fileWatcher_Renamed(object sender, RenamedEventArgs e)
{
string S = "";
}
void m_fileWatcher_Disposed(object sender, EventArgs e)
{
string S = "";
}
void m_fileWatcher_Deleted(object sender, FileSystemEventArgs e)
{
string S = "";
}
void m_fileWatcher_Created(object sender, FileSystemEventArgs e)
{
string S = "";
}
void m_fileWatcher_Error(object sender, ErrorEventArgs e)
{
string S = "";
}
void m_fileWatcher_Changed(object sender, FileSystemEventArgs args)
{
if (args.ChangeType == WatcherChangeTypes.Changed)
{
while (ParseFile(args.FullPath))
{
}
}
}
I bet this thread has your answer --> FileSystemWatcher changed event (for “LastWrite”) is unreliable
The FileSystemWatcher uses an update to the LastWrite attribute of a file to fire events, however, the LastWrite is not updated in real time and should not be relied upon as trigger for an event.
If you have enough time and resources on your hands then you probably want to look into File System Filters and the simpler approach of a Filter Manager and Minifilter Driver. It is driver type development, however, it is a sure file way to accomplish your objective.
It is dug down a little deeper by system policy but gives you a wide array of events to latch onto. If I was doing this for anything like pci compliance or similar tasks then I would not use the FileSystemWatcher.
Make sure you set IncludeSubdirectories to true.
https://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher(v=vs.110).aspx

How to recycle file/folder from network share delete

I'm trying to code a program which will watch a directory/disk and move deleted files/folders to recycle bin when they are deleted from network shared folder.
I have this code which is working when a file/folder is deleted but I need to cancel delete and move file to Recycle Bin. I found how to move file/folder to Recycle Bin here: How to Move files to the recycle bin
FileSystemWatcher fsw = new FileSystemWatcher();
private void frmMain_Load(object sender, EventArgs e)
{
StartWatcher("X:\\Virtuals\\");
}
private void StopWatcher()
{
if (fsw != null)
try
{
fsw.EnableRaisingEvents = false;
fsw.Dispose();
fsw = null;
}
catch { }
}
private void StartWatcher(string path)
{
StopWatcher();
FileSystemWatcher fsw = new FileSystemWatcher();
fsw.Path = Path.GetDirectoryName(path);
fsw.Filter = Path.GetFileName(path);
fsw.NotifyFilter = NotifyFilters.LastAccess |
NotifyFilters.LastWrite | NotifyFilters.FileName
| NotifyFilters.DirectoryName;
fsw.Deleted += new FileSystemEventHandler(OnDeleted);
fsw.EnableRaisingEvents = true;
}
private void OnDeleted(object source, FileSystemEventArgs e)
{ MessageBox.Show("File deleted: " + e.FullPath); }
FileSystemWatcher won't help you in your task. Proper way to do this is employ a filesystem filter driver which will intercept file deletion requests and move files to the recycle bin instead. You can create your own filter driver (requires knowledge of low-level kernel-mode development), or we offer CallbackFilter product which lets you do the job in user mode (even in .NET).
The FileSystemWatcher raises events after the file is actually deleted, modified or created, so it´s to late to do a copy after the delete event is raised. And there is no possibility to cancel an operation with the FileSystemWatcher. Possibly it can be solved by using some sort of low level APIs (but I don´t know which and how).

Monitor multiple folders using FileSystemWatcher

Whats the best way to monitor multiple folders (not subdirectories) using FileSystemWatcher in C#?
I don't think FSW supports monitoring multiple folders, so just instantiate one per folder you want to monitor. You can point the event handlers at the same methods, though, which should end up working like I think you want.
The easiest way is to create multiple instances of the FileSystemWatcher object.
http://www.c-sharpcorner.com/UploadFile/mokhtarb2005/FSWatcherMB12052005063103AM/FSWatcherMB.aspx
You'll have to make sure the you handle events between the two folders correctly:
Although some common occurances, such
as copying or moving a file, do not
correspond directly to an event, these
occurances do cause events to be
raised. When you copy a file, the
system raises a Created event in the
directory to which the file was copied
but does not raise any events in the
original directory. When you move a
file, the server raises two events: a
Deleted event in the source directory,
followed by a Created event in the
target directory.
For example, you create two instances
of FileSystemWatcher.
FileSystemWatcher1 is set to watch
"C:\My Documents", and
FileSystemWatcher2 is set to watch
"C:\Your Documents". Now, if you copy
a file from "My Documents" into "Your
Documents", a Created event will be
raised by FileSystemWatcher2, but no
event is raised for
FileSystemWatcher1. Unlike copying,
moving a file or directory would raise
two events. From the previous example,
if you moved a file from "My
Documents" to "Your Documents", a
Created event would be raised by
FileSystemWatcher2 and a Deleted event
would be raised by FileSystemWatcher
Link to FileSystemEventArgs
Out of the box, FileSystemWatcher only supports monitoring a single parent directory. To monitor multiple sibling directories, you would need to create multiple instances of FileSystemWatcher.
You can try cheating this behavior, however, by taking advantage of FileSystemWatcher's ability to include subdirectories. You can create an NTFS junction point (aka symbolic link) as a subdirectory from the directory you are watching. Mark Russinovich of Sysinternals fame has a utility called Junction to simplify creation and management of symlinks.
Note that you can only create symlinks to directories on your local machine.
Although this is an old question I decided to answer, because I couldn't find a good answer anywhere.
So, the aim was to monitor multiple folders (not subdirectories) using FileSystemWatcher? Here's my suggestion:
using System;
using System.IO;
using System.Security.Permissions;
using System.Collections.Generic;
namespace MultiWatcher
// ConsoleApplication, which monitors TXT-files in multiple folders.
// Inspired by:
// http://msdn.microsoft.com/en-us/library/system.io.filesystemeventargs(v=vs.100).aspx
{
public class Watchers
{
public static void Main()
{
Run();
}
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public static void Run()
{
string[] args = System.Environment.GetCommandLineArgs();
// If a directory is not specified, exit program.
if (args.Length < 2)
{
// Display the proper way to call the program.
Console.WriteLine("Usage: Watcher.exe PATH [...] [PATH]";
return;
}
List<string> list = new List<string>();
for (int i = 1; i < args.Length; i++)
{
list.Add(args[i]);
}
foreach (string my_path in list)
{
Watch(my_path);
}
// Wait for the user to quit the program.
Console.WriteLine("Press \'q\' to quit the sample.");
while (Console.Read() != 'q') ;
}
private static void Watch(string watch_folder)
{
// Create a new FileSystemWatcher and set its properties.
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = watch_folder;
/* Watch for changes in LastAccess and LastWrite times, and
the renaming of files or directories. */
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
// Only watch text files.
watcher.Filter = "*.txt";
// Add event handlers.
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
// Begin watching.
watcher.EnableRaisingEvents = true;
}
// Define the event handlers.
private static void OnChanged(object source, FileSystemEventArgs e)
{
// Specify what is done when a file is changed, created, or deleted.
Console.WriteLine("File: " + e.FullPath + " " + e.ChangeType);
}
private static void OnRenamed(object source, RenamedEventArgs e)
{
// Specify what is done when a file is renamed.
Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, e.FullPath);
}
}
}
Can you simply use multiple instances of the FileSystemWatcher, one for each directory?
You would have to instantiate multiple instances of the FileSystemWatcher object. Though you can bind the Events to the same method and use the sender object to determine which FileSystemWatcher triggered the event.
var fsw1 = new FileSystemWatcher();
var fsw2 = new FileSystemWatcher();
FileSystemEventHandler fsw_changed = delegate(object sender, FileSystemEventArgs e)
{
Console.WriteLine("{0} - {1}", (sender as FileSystemWatcher).Path, e.ChangeType);
};
fsw1.Changed += fsw_changed;
fsw2.Changed += fsw_changed;
or you can pass the paths in-code, to mark a certain range of the domain watched like in this :
multiple monitor link
hope this helps.

Categories