I am trying to create a C# service as a console app.
The main code:
static void Main(string[] args)
{
var exitCode = HostFactory.Run(
x =>
{
x.Service<HeartBeat>(s =>
{
s.ConstructUsing(heartbeat => new HeartBeat());
s.WhenStarted(heartbeat => heartbeat.Start());
s.WhenStopped(heartbeat => heartbeat.Stop());
});
x.RunAsLocalSystem();
x.SetServiceName("UpgradeServices");
x.SetDisplayName("Service Upgrade");
x.SetDescription("Service is monitoring new version.");
});
int exitCodeValue = (int)Convert.ChangeType(exitCode, exitCode.GetTypeCode());
Environment.ExitCode = exitCodeValue;
}
Then I have code for deleting and copying files as per below:
public class MovingFiles
{
public string fileName;
public string destPath;
private DirectoryInfo directory;
private DirectoryInfo myFile;
public string sourcePath;
public string targetPath;
public MovingFiles(string sourceFolder, string targetFolder)
{
sourcePath = sourceFolder;
targetPath = targetFolder;
}
public void deleteFilesMethod()
{
System.Threading.Thread.Sleep(10000);
string deleteString;
//First we want to delete all files except for the JSON file as this has all of the important settings
if (System.IO.Directory.Exists(targetPath))
{
string[] files = System.IO.Directory.GetFiles(targetPath);
// Loop through each files and then delete these if they are not the JSON file
foreach (string s in files)
{
deleteString = targetPath;
// The file name which is returned will be deleted
fileName = System.IO.Path.GetFileName(s);
if (fileName != "appsettings.json")
{
deleteString = System.IO.Path.Combine(targetPath, fileName);
try
{
System.IO.File.Delete(deleteString);
}
catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
return;
}
}
}
}
else
{
Console.WriteLine("The loop didn't run, source path doesn't exist");
}
}
public void copyFilesMethod()
{
System.Threading.Thread.Sleep(10000);
if (System.IO.Directory.Exists(sourcePath))
{
// Searching for the latest directory created in the sourcePath folder
directory = new DirectoryInfo(sourcePath);
myFile = (from f in directory.GetDirectories()
orderby f.LastWriteTime descending
select f).First();
sourcePath = System.IO.Path.Combine(sourcePath, myFile.Name);
string[] files = System.IO.Directory.GetFiles(sourcePath);
// Copy the files and overwrite destination files if they already exist.
foreach (string s in files)
{
// Use static Path methods to extract only the file name from the path.
fileName = System.IO.Path.GetFileName(s);
if (fileName != "appsettings.json")
{
destPath = System.IO.Path.Combine(targetPath, fileName);
try
{
System.IO.File.Copy(s, destPath, true);
}
catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
return;
}
}
}
}
else
{
Console.WriteLine("The loop didn't run, source path doesn't exist");
}
// Keep console window open in debug mode.
Console.WriteLine("Procedure has been completed.");
}
This should be triggered once there is a new file, which I have written as this:
class FileMonitor
{
public FileSystemWatcher watcher = new FileSystemWatcher();
public string sourcePath;
public string targetPath;
public FileMonitor(string sourceFolder, string targetFolder)
{
sourcePath = sourceFolder;
targetPath = targetFolder;
}
public void watch()
{
watcher.Path = sourcePath;
watcher.NotifyFilter = NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName
| NotifyFilters.CreationTime;
//var one = NotifyFilters.FileName;
watcher.Filter = "*.*";
watcher.Created += new FileSystemEventHandler (OnChanged);
watcher.EnableRaisingEvents = true;
//System.Threading.Thread.Sleep(25000);
}
public void OnChanged(object source, FileSystemEventArgs e)
{
//Copies file to another directory.
MovingFiles FileMoveOne = new MovingFiles(sourcePath, targetPath);
FileMoveOne.deleteFilesMethod();
FileMoveOne.copyFilesMethod();
}
}
What I understand once I run the below it would look every 10 seconds if there is a new file and then trigger the OnChange method, am I right?
public class HeartBeat
{
private readonly Timer _timer;
public HeartBeat()
{
_timer = new Timer(10000)
{
AutoReset = true
};
_timer.Elapsed += TimerElapsed;
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
//StringBuilder loggingLine = new StringBuilder();
/* Every 30 seconds it will write to the file */
string[] lines = new string[] {DateTime.Now.ToString() + ": Heartbeat is active. Service is monitoring SS and DS"};
//lines[1] = DateTime.Now.ToString() + " About to check if new files are placed on server";
//loggingLine.Append(lines[i]);
File.AppendAllLines(#"C:\Users\RLEBEDEVS\Desktop\Monitor\Monitor1\HeartBeat.log", lines);
//File.AppendAllLines(#"C:\Users\RLEBEDEVS\Desktop\Monitor\Monitor1\HeartBeat.log", lines);
FileMonitor versioOne = new FileMonitor(#"C:\Users\RLEBEDEVS\Desktop\Monitor\Monitor1", #"C:\Users\RLEBEDEVS\Desktop\Monitor\Monitor2");
versioOne.watch();
}
public void Start ()
{
_timer.Start();
}
public void Stop ()
{
_timer.Stop();
}
}
The issue I am having is inconsistency.
It should copy the files to the folder Monitor2 once a new folder is created, but it is not doing that on the first creation. It does delete and copy the files on the second time once create a folder in monitor1 folder.
On every second time it is trying to copy the files it crashes with the below error which I am not familiar with:
Topshelf.Hosts.ConsoleRunHost Critical: 0 : The service threw an unhandled exception, System.UnauthorizedAccessException: Access to the path 'C:\Users\RLEBEDEVS\Desktop\Monitor\Monitor2\System.Net.Sockets.dll' is denied.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.InternalDelete(String path, Boolean checkHost)
at System.IO.File.Delete(String path)
at UpgradeServices.MovingFiles.deleteFilesMethod() in C:\Users\RLEBEDEVS\Desktop\C#\Service\UpgradeServices\MovingFIles.cs:line 48
at UpgradeServices.FileMonitor.OnChanged(Object source, FileSystemEventArgs e) in C:\Users\RLEBEDEVS\Desktop\C#\Service\UpgradeServices\FileMonitor.cs:line 43
at System.IO.FileSystemWatcher.OnCreated(FileSystemEventArgs e)
at System.IO.FileSystemWatcher.NotifyFileSystemEventArgs(Int32 action, String name)
at System.IO.FileSystemWatcher.CompletionStatusChanged(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* overlappedPointer)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
Topshelf.Hosts.ConsoleRunHost Information: 0 : Stopping the UpgradeServices service
Topshelf.Hosts.ConsoleRunHost Information: 0 : The UpgradeServices service has stopped.
The program '[497452] UpgradeServices.exe' has exited with code 1067 (0x42b).
Line 48 is this one, though it performed the tasks previously fine (on the first go).
System.IO.File.Delete(deleteString);
I see that the issue is with the way I am raising the event. Does anybody know what should I change in order to achieve the desired result which is when the service is started on every new folder created in the destiny it would perform the two methods moving and deleting files? The folder will always have only new folders created.
Regards,
It seems that in your heartbeat you starting new FileMonitor every 10 seconds, so after 20 seconds you will have 2 FileMonitor's watching and moving(deleting) the same files at the time. Just start FileMonitor once using hosted service for example. Or remove the timer handler part in your HeartBeat class and just create FileMonitor in constructor:
public HeartBeat()
{
FileMonitor versioOne = new
FileMonitor(#"C:\Users\RLEBEDEVS\Desktop\Monitor\Monitor1", #"C:\Users\RLEBEDEVS\Desktop\Monitor\Monitor2");
versioOne.watch();
// may be save it to instance field so it does not get garbage collected.
// Not sure how FileSystemWatcher behaves with subscription,
// it should prevent the "versionOne" from being collected via subscription.
}
Related
I have been using fileSystemWatcher for c# to track when a zip file has been downloaded, the problem is that the name of the file never gets output to result.Name. Why is this??
public ResultsDetails ExportCallsToCsv()
{
var downloadsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + #"\Downloads";
ExportToCsvButton.Click();
using (var watcher = new FileSystemWatcher(downloadsDirectory))
{
watcher.Filter = "*.zip";
var result = watcher.WaitForChanged(WatcherChangeTypes.Created, 10000);
CsvFilePath = $#"{downloadsDirectory}\{result.Name}".Replace("_", "-");
}
return new ResultsDetails(ActionResult.Passed, "Csv file created succesfully.");
}
Why does result.Name never get filled? I've attempted to put an endless while loop inside the using block which breaks when result.Name gets filled, but to still no avail. It just halts forever when I do that.
FileSystemWatcher.WaitForChanged is a synchronous/blocking method and is unable to capture the file change that was triggered on the same thread.
Here's an example of your code in a small test program. This will not work (as you pointed out).
static void Main(string[] args)
{
var directory = "C:\\test";
File.Copy("C:\\test\\source.txt", "C:\\test\\new_file.txt");
using (var watcher = new FileSystemWatcher(directory))
{
watcher.Filter = "*.txt";
var result = watcher.WaitForChanged(WatcherChangeTypes.Created, 10000);
if (result.Name is null)
{
// This is always printed.
Console.WriteLine("No name");
}
}
Console.ReadLine();
}
Here's a small sample program that does work. This version handles the FileSystemWatcher events asynchronously.
static void Main(string[] args)
{
var directory = "C:\\test";
using (var watcher = new FileSystemWatcher(directory, "*.txt"))
{
watcher.EnableRaisingEvents = true; // *** Be sure to include this!
watcher.Created += Watcher_Created; // *** This is different, too.
File.Delete("C:\\test\\new_file.txt");
File.Copy("C:\\test\\source.txt", "C:\\test\\new_file.txt");
}
Console.WriteLine("All done.");
Console.ReadLine();
}
private static void Watcher_Created(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"Detected {e.Name} is {e.ChangeType}.");
}
I'm using FileSystemWatcher to detect directory changes, and after that I read file content and insert it to database.
Here's my code:
private FileSystemWatcher _watcher;
public MainWindow()
{
try
{
InitializeComponent();
GetFiles();
//Task.Factory.StartNew(() => GetFiles())
// .ContinueWith(task =>
// {
// }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
catch(Exception ex)
{
//..
}
}
public bool GetFiles()
{
_watcher = new FileSystemWatcher(Globals.iniFilesPath, "*.ini");
_watcher.Created += FileCreated;
_watcher.IncludeSubdirectories = false;
_watcher.EnableRaisingEvents = true;
return true;
}
private void FileCreated(object sender, FileSystemEventArgs e)
{
try
{
string fileName = Path.GetFileNameWithoutExtension(e.FullPath);
if (!String.IsNullOrEmpty(fileName))
{
string[] content = File.ReadAllLines(e.FullPath);
string[] newStringArray = content.Select(s => s.Substring(s.LastIndexOf('=') + 1)).ToArray();
ChargingStationFile csf = new Product
{
Quantity = Convert.ToDecimal(newStringArray[1]),
Amount = Convert.ToDecimal(newStringArray[2]),
Price = Convert.ToDecimal(newStringArray[3]),
FileName = fileName
};
ProductController.Instance.Save(csf);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
If I run this code with CTRL+F5 I received this message:
But If I go with F5 (Debugging mode) than I receive this and not this error about cannot access process and item is sucessfully saved. This is confusing me really..
Should I dispose watcher? or something like that? Maybe I'm missing something here?
This is first time I'm using FileSystemWatcher, obliviously something is really wrong here..
P.S I've found out that this line is causing an exception:
string[] content = File.ReadAllLines(e.FullPath);
how come?
Thanks guys
Cheers
File.ReadAllLines() cannot access the file when it is open for writing in another application but you can use a FileStream and StreamReader instead.
Replace string[] content = File.ReadAllLines(e.FullPath); with the following code and you should be able to read the contents of the file regardless of whether it is open in another application:
List<string> content = new List<string>();
using (FileStream stream = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (StreamReader sr = new StreamReader(stream))
{
while (!sr.EndOfStream)
content.Add(sr.ReadLine());
}
As mention in this answer:
Most likely what is happening here is that the FileCreated event is
being raised and tries to process the file before is has been
completely written to disk.
So, you need to wait until the file has finished to copy. According to this other answer:
From the documentation for FileSystemWatcher:
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.
So, a workaround for your case will be to create a list of strings containing the paths of the files that could not be read in the Created method handler, and re-process those paths in the Changed event of the FileSystemWatcher (read the comments in the code) :
public partial class MainWindow : Window {
private FileSystemWatcher _watcher;
public MainWindow() {
try {
InitializeComponent();
GetFiles();
} catch (Exception ex) {
MessageBox.Show($"Exception: {ex.Message}");
}
}
private bool GetFiles() {
_watcher = new FileSystemWatcher(#"C:\TestFolder", "*.ini");
_watcher.Created += FileCreated;
_watcher.Changed += FileChanged; // add this.
_watcher.IncludeSubdirectories = false;
_watcher.EnableRaisingEvents = true;
return true;
}
// this field is new, and contains the paths of the files that could not be read in the Created method handler.
private readonly IList<string> _waitingForClose = new List<string>();
private void FileChanged(object sender, FileSystemEventArgs e) {
if (_waitingForClose.Contains(e.FullPath)) {
try {
string[] content = File.ReadAllLines(e.FullPath);
string[] newStringArray = content.Select(s => s.Substring(s.LastIndexOf('=') + 1)).ToArray();
MessageBox.Show($"On FileChanged: {string.Join(" --- ", newStringArray)}");
// Again, process the data from the file to saving in the database.
// removing the path, so as not to reprocess the file..
_waitingForClose.Remove(e.FullPath);
} catch (Exception ex) {
MessageBox.Show($"Exception on FileChanged: {ex.Message} - {e.FullPath}");
}
}
}
private void FileCreated(object sender, FileSystemEventArgs e) {
try {
string fileName = Path.GetFileNameWithoutExtension(e.FullPath);
if (!String.IsNullOrEmpty(fileName)) {
string[] content = File.ReadAllLines(e.FullPath);
string[] newStringArray = content.Select(s => s.Substring(s.LastIndexOf('=') + 1)).ToArray();
MessageBox.Show($"On FileCreated: {string.Join(" --- ", newStringArray)}");
// process the data from the file to saving in the database.
}
} catch (Exception ex) {
// if the method fails, add the path to the _waitingForClose variable
_waitingForClose.Add(e.FullPath);
//MessageBox.Show($"Exception on FIleCreated: {ex.Message} - {e.FullPath}");
}
}
}
I'm making a WPF application.
I'm using WebClient to download files. I have a list of the name of the files that should be downloaded from a current path. I use an foreach to iterate through each name and then download each file sequency. The name of the file i get from a torrent file which i decode.
public class DownloadGameFile
{
private DownloadTorrentFile DLTorrent;
//List of file that already exist
private List<string> ExistFile = new List<string>();
DirectoryInfo fileInfo;
private volatile bool _completed;
private string savePath = #"C:\Program Files (x86)\program\Client\package\downloads\";
public DownloadGameFile()
{
DLTorrent = new DownloadTorrentFile();
fileInfo = new DirectoryInfo(savePath);
}
public bool StartDownload(int torrentId)
{
try
{
DLTorrent.DecodeTorrent(torrentId);
//File info from a Directory
FileInfo[] files = fileInfo.GetFiles();
foreach (FileInfo i in files)
{
Console.WriteLine("Files exit ");
if (DLTorrent.GameInfomation[i.Name] != i.Length)
{
i.Delete();
}
else
{
Console.WriteLine("add files ");
ExistFile.Add(i.Name);
}
}
//Make a list which file not downloaded yet
var res = DLTorrent.GameInfomation.Keys.Except(ExistFile);
foreach (var x in res)
{
Console.WriteLine(x);
}
foreach (var x in res)
{
DownloadProtocol("http://cdn.path.com/rental/" + torrentId + "/" + x, savePath + x);
}
return true;
}
catch
{
return false;
}
}
public void DownloadProtocol(string address, string location)
{
WebClient client = new WebClient();
Uri Uri = new Uri(address);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
client.DownloadFileAsync(Uri, location);
}
private void DownloadProgress(object sender, DownloadProgressChangedEventArgs e)
{
// Displays the operation identifier, and the transfer progress.
Console.WriteLine("{0} downloaded {1} of {2} bytes. {3} % complete...",
(string)e.UserState,
e.BytesReceived,
e.TotalBytesToReceive,
e.ProgressPercentage);
}
private void Completed(object sender, AsyncCompletedEventArgs e)
{
if (e.Cancelled == true)
{
Console.WriteLine("Download has been canceled.");
}
else
{
Console.WriteLine("Download completed!");
}
}
This code work fine in an Console app with a current thread blocker. But when I use the same code in an WPF app It doesn't. I'm using a button to execute the StartDownload() function, but when I do that it start downloading all the files at the same time. Example the first file get 3% done and then it switch to another file and so on. I really don't know why this isn't working.
Have you considered not using .DownloadFileAsync? You can try .DownloadFile and start DownloadProtocol() with a single background thread. Although I think you'll have to rethink your DownloadProgress output.
I do something very similar within a winform.
I'm trying to make this program that will move (cut and paste) all files from one directory (a folder) to another directory. In this example, I'm trying to move all the files that are inside the D:\Source folder (has a few files in it) to C:\Source folder (which has no files in it). When I run the program, I get this error.
http://s13.postimg.org/kuufg0gmu/error.jpg
Here is the full source code:
using System.IO;
namespace FileManager
{
public partial class Form1 : Form
{
string sourceDirectory = "";
//string destinationDirectory = #"C:\Destination";
string date = "";
string[] filePaths;
string destinationPath;
public Form1()
{
InitializeComponent();
}
private void buttonClean_Click(object sender, EventArgs e)
{
// Get source directory
sourceDirectory = textBoxDirectory.Text;
// Get date of files
date = textBoxDate.Text;
// Get file paths
if (Directory.Exists(sourceDirectory))
{
filePaths = Directory.GetFiles(#sourceDirectory, "*", SearchOption.AllDirectories);
foreach (string sourcePath in filePaths)
{
destinationPath = sourcePath.Remove(0, 1).Insert(0, "C");
File.Copy(sourcePath, destinationPath);
//MessageBox.Show(destinationPath);
}
}
else
{
MessageBox.Show("Directory does not exist.");
}
}
}
}
You need to check if destination directory exists than copy files otherwise first create destination directory.
foreach (string sourcePath in filePaths)
{
destinationPath = sourcePath.Remove(0, 1).Insert(0, "C");
if(!Directory.Exists(destinationPath))
Directory.CreateDirectory(destinationpath);
File.Copy(sourcePath, destinationPath);
//MessageBox.Show(destinationPath);
}
Exception clearly states that destinationPath is not valid. Make sure destinationPath exist as shown by #Mairaj then use File.Move to cut-paste. Complete code to move one file. You can your logic of directories to move all the files.
using System;
using System.IO;
class Test
{
public static void Main()
{
string path = #"c:\temp\MyTest.txt";
string path2 = #"c:\temp2\MyTest.txt";
try
{
if (!File.Exists(path))
{
// This statement ensures that the file is created,
// but the handle is not kept.
using (FileStream fs = File.Create(path)) {}
}
// Ensure that the target does not exist.
if (File.Exists(path2))
File.Delete(path2);
// Move the file.
File.Move(path, path2);
Console.WriteLine("{0} was moved to {1}.", path, path2);
// See if the original exists now.
if (File.Exists(path))
{
Console.WriteLine("The original file still exists, which is unexpected.");
}
else
{
Console.WriteLine("The original file no longer exists, which is expected.");
}
}
catch (Exception e)
{
Console.WriteLine("The process failed: {0}", e.ToString());
}
}
}
Find more info here
I am running into an issue with a FileSystemWatcher.
I listen to a certain text file and each new line (FileSystemWatcher.Changed) is handle.
Text file that I listen to it is on the local network and some computers can write to the file at the same time.
When a particular machine writing my text file I immediately take the last line in the file and handles it.
How can I verify if for example some machines write to the file at the same time that i took this string from each machine without miss ?
This is my class:
public class Watcher
{
private string _file;
public Watcher(string file)
{
_file = file;
}
public void startWatch()
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = Path.GetDirectoryName(_file);
watcher.Filter = Path.GetFileName(_file);
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Changed += watcher_Changed;
watcher.EnableRaisingEvents = true;
}
public void watcher_Changed(object sender, FileSystemEventArgs e)
{
readLastLine();
}
private void readLastLine()
{
string lastLine = string.Empty;
using (StreamReader sr = new StreamReader(File.Open(_file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
string str = sr.ReadToEnd();
int x = str.LastIndexOf('\n');
lastLine = str.Substring(x + 1);
}
validateString(lastLine);
}
private void validateString(string str)
{
string[] arr = str.Split(' ');
if (arr.Length != 2 && arr[0] != "start" && arr[0] != "stop" && arr[0] != "finish")
return;
// Handle the string...
}
}