How to Transfer Files in different Threads with FileWatcher - c#

I have a form with a File Watcher to which he transfers to multiple addresses all video files placed in a folder. What is the best option so that when multiple files are added to even be able to perform each transfer in a thread. Here's an example of my code:
DockingBarTransferEntities context = new DockingBarTransferEntities();
private void fileSystemWatcher1_Changed(object sender, System.IO.FileSystemEventArgs e)
{
IEnumerable<Diretorios> directories = context.Diretorios.ToList();
foreach (var destino in directories)
{
try
{
Transfere(e.FullPath,Path.GetFileName(e.FullPath),destino);
}
catch (Exception ex)
{
textBox1.Text += "Error: " + ex.Message;
}
}
}
public void Transfere(string fullPath, string name, Diretorios diretorio)
{
try
{
if (Directory.Exists(diretorio.Caminho))
{
string fileName = Path.GetFileName(fullPath);
fileName = String.Format("{0}\\{1}", diretorio.Caminho, fileName);
FileInfo arquivo = new FileInfo(fullPath);
arquivo.CopyTo(fileName, true);
}
}
catch (Exception ex)
{
}
}

It should be as simple as this:
Task.Factory.StartNew(() => Transfere(e.FullPath, Path.GetFileName(e.FullPath), destino));
instead of calling Transfere directly.

Related

How to copy several files at the same time?

I have a number of USB drives that I want to copy a folder to.
I can't transfer files at the same time, but only one file at a time.
Although I manage to be directed to a different drive each time, but it is not at the same time.
Where am I wrong?
static void Main(string[] args)
{
string path = #"....";
Parallel.ForEach(
DriveInfo.GetDrives(), drive =>
{
if (drive.DriveType == DriveType.Removable)
{
CloneDirectory(path, drive.Name);
}
}
);
}
private static void CloneDirectory(string root, string dest)
{
foreach (var directory in Directory.GetDirectories(root))
{
//Get the path of the new directory
var newDirectory = Path.Combine(dest, Path.GetFileName(directory));
//Create the directory if it doesn't already exist
Directory.CreateDirectory(newDirectory);
//Recursively clone the directory
CloneDirectory(directory, newDirectory);
}
try
{
foreach (var file in Directory.GetFiles(root))
{
try
{
File.Copy(file, Path.Combine(dest, Path.GetFileName(file)), false);
Console.WriteLine(Path.Combine(dest, Path.GetFileName(file)));
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}

FileSystemWatcher C# - cannot access file because it is being used by another process

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}");
}
}
}

c# - How to multithreading Iterate Through a Directory Tree, in folder 1000000 files

I need to save in database file Name and Size in byte from folder and all subfolder.
In this folder lay 1 000 000 files.
And when I use example from msdn it works 4 days, that very slowly.
static void Main(string[] args)
{
string pdxPathDocFiles = System.Configuration.ConfigurationManager.AppSettings["PDX_PathDocFiles"] as string;
if (string.IsNullOrEmpty(pdxPathDocFiles))
{
Console.WriteLine("In the configuration file is missing the path to the root directory - PDX_PathDocFiles.");
}
else
{
if (!Directory.Exists(pdxPathDocFiles))
{
Console.WriteLine("Directory not found");
}
else
{
try
{
Console.WriteLine("rootPath: " + pdxPathDocFiles);
PayDox_EPD19_T20_RGMEntities db = new PayDox_EPD19_T20_RGMEntities();
System.IO.DirectoryInfo rootDir = new DirectoryInfo(pdxPathDocFiles);
db.FileDBRecord.RemoveRange(db.FileDBRecord);
WalkDirectoryTree(rootDir, rootDir.ToString(), db);
db.SaveChanges();
}
catch (Exception)
{
Console.WriteLine("Failed to connect to the database");
}
Console.WriteLine("All ok");
}
}
Console.WriteLine("Bye, Good Day.");
}
static void WalkDirectoryTree(System.IO.DirectoryInfo root, string rootDir, PayDox_EPD19_T20_RGMEntities db)
{
System.IO.FileInfo[] files = null;
System.IO.DirectoryInfo[] subDirs = null;
try
{
files = root.GetFiles("*.*");
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
}
if (files != null)
{
foreach (System.IO.FileInfo fi in files)
{
db.FileDBRecord.Add(new FileDBRecord { FileName = fi.FullName.Replace(rootDir, ""), FileSize = fi.Length });
}
subDirs = root.GetDirectories();
foreach (System.IO.DirectoryInfo dirInfo in subDirs)
{
WalkDirectoryTree(dirInfo, rootDir, db);
}
}
db.SaveChanges();
}
When I try another way, it throw-out with exception stack overflow exception.
static void Main(string[] args)
{
string pdxPathDocFiles = System.Configuration.ConfigurationManager.AppSettings["PDX_PathDocFiles"] as string;
if (string.IsNullOrEmpty(pdxPathDocFiles))
{
Console.WriteLine("In the configuration file is missing the path to the root directory - PDX_PathDocFiles.");
}
else
{
if (!Directory.Exists(pdxPathDocFiles))
{
Console.WriteLine("Directory not found");
}
else
{
try
{
Console.WriteLine("rootPath: " + pdxPathDocFiles);
PayDox_EPD19_T20_RGMEntities db = new PayDox_EPD19_T20_RGMEntities();
db.FileDBRecord.RemoveRange(db.FileDBRecord);
db.SaveChanges();
Console.WriteLine("Remove data from table");
System.IO.FileInfo[] files = null;
System.IO.DirectoryInfo rootDir2 = new DirectoryInfo(pdxPathDocFiles);
try
{
files = rootDir2.GetFiles("*.*", SearchOption.AllDirectories);
Console.WriteLine("Reed {0} fileName", files.Length);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("You do not have permission to access one or more folders in this directory tree.");
Console.WriteLine(ex.Message);
return;
}
db.FileDBRecord.AddRange(files.Select(x => new FileDBRecord { FileName = x.FullName.Replace(pdxPathDocFiles, ""), FileSize = x.Length }));
db.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("All ok");
}
}
Console.WriteLine("Bye, Good Day.");
}
How make program faster, maybe add multithreading?
For starters, your code isn't async. Break this out into a separate class and make the methods async. This allows the thread to be used while waiting for an IO operation. Anytime your calling the Database or File system use async equivalent methods.
The second thing I would do is try to make is so each transaction is atomic. If you doing something x amount of times, write the program in such a way that each x time can be done is isolation. Once that is done you can run these is parallel by creating a new Task (Task.Run).
Once those 2 are done and the task is still taking a while, look into TPL Dataflow. That can buffer requests for you to optimize your process.
I improved first example from msdn, by adding there TPL library.
Now it working 4 hour, not 4 days.
static void Main(string[] args)
{
string pdxPathDocFiles = System.Configuration.ConfigurationManager.AppSettings["PDX_PathDocFiles"] as string;
if (string.IsNullOrEmpty(pdxPathDocFiles))
{
Console.WriteLine("In the configuration file is missing the path to the root directory - PDX_PathDocFiles.");
}
else
{
if (!Directory.Exists(pdxPathDocFiles))
{
Console.WriteLine("Directory not found");
}
else
{
try
{
Console.WriteLine("rootPath: " + pdxPathDocFiles);
PayDox_EPD19_T20_RGMEntities db = new PayDox_EPD19_T20_RGMEntities();
System.IO.DirectoryInfo rootDir = new DirectoryInfo(pdxPathDocFiles);
db.Database.ExecuteSqlCommand("TRUNCATE TABLE [FileDBRecord]");
db.SaveChanges();
db.Dispose();
Console.WriteLine("Remove data from table");
WalkDirectoryTree(rootDir, rootDir.ToString());
Console.WriteLine("All ok");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Console.WriteLine("Bye, Good Day.");
Console.WriteLine("Processing complete. Press any key to exit.");
Console.ReadKey();
}
static void WalkDirectoryTree(System.IO.DirectoryInfo root, string rootDir)
{
//Console.WriteLine("Go to folder: "+ root.FullName.Replace(rootDir, ""));
System.IO.FileInfo[] files = null;
System.IO.DirectoryInfo[] subDirs = null;
try
{
files = root.GetFiles("*.*");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
if (files != null)
{
PayDox_EPD19_T20_RGMEntities db = new PayDox_EPD19_T20_RGMEntities();
foreach (var currentElement in files)
{
db.FileDBRecord.Add(new FileDBRecord { FileName = currentElement.FullName.Replace(rootDir, ""), FileSize = currentElement.Length });
}
db.SaveChanges();
db.Dispose();
subDirs = root.GetDirectories();
Parallel.ForEach(subDirs,
currentElement =>
{
try
{
WalkDirectoryTree(currentElement, rootDir);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
});
}
}
}
maybe we can fix your second code.. (untested, but may not throw the exception)
if you test it, let me know if it is faster..
static void Main(string[] args)
{
string pdxPathDocFiles = System.Configuration.ConfigurationManager.AppSettings["PDX_PathDocFiles"] as string;
if (string.IsNullOrEmpty(pdxPathDocFiles))
{
Console.WriteLine("In the configuration file is missing the path to the root directory - PDX_PathDocFiles.");
}
else
{
if (!Directory.Exists(pdxPathDocFiles))
{
Console.WriteLine("Directory not found");
}
else
{
try
{
Console.WriteLine("rootPath: " + pdxPathDocFiles);
PayDox_EPD19_T20_RGMEntities db = new PayDox_EPD19_T20_RGMEntities();
db.FileDBRecord.RemoveRange(db.FileDBRecord);
db.SaveChanges();
Console.WriteLine("Remove data from table");
IList<FileDBRecord> files = null;
System.IO.DirectoryInfo rootDir2 = new DirectoryInfo(pdxPathDocFiles);
try
{
files = rootDir2.GetFiles("*.*", SearchOption.AllDirectories).Select(x => new FileDBRecord { FileName = x.FullName.Replace(pdxPathDocFiles, ""), FileSize = x.Length });
Console.WriteLine("Reed {0} fileName", files.Length);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("You do not have permission to access one or more folders in this directory tree.");
Console.WriteLine(ex.Message);
return;
}
files.Foreach(db.FileDBRecord);
db.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("All ok");
}
}
Console.WriteLine("Bye, Good Day.");
}

BackgroundTransfer cannot move files

I am having problem downloading files using Background transfer. After completion of download when moving file, it gives you an exception Operation not permitted
void addTransferRequest(string fileName)
{
if (string.IsNullOrEmpty(fileName))
return;
string filePathToDownload = string.Empty;
filePathToDownload = activeReciter.DownloadURL;
filePathToDownload += fileName;
Uri transferUri = new Uri(Uri.EscapeUriString(filePathToDownload),
UriKind.RelativeOrAbsolute);
BackgroundTransferRequest transferRequest = new
BackgroundTransferRequest(transferUri);
transferRequest.Method = "GET";
transferRequest.TransferPreferences = TransferPreferences.AllowBattery;
Uri downloadUri = new Uri(DataSource.TEMPDOWNLOADLOCATION + fileName,
UriKind.RelativeOrAbsolute);
transferRequest.DownloadLocation = downloadUri;
transferRequest.Tag = fileName;
transferRequest.TransferStatusChanged +=
new EventHandler<BackgroundTransferEventArgs>
(transfer_TransferStatusChanged);
transferRequest.TransferProgressChanged += new
EventHandler<BackgroundTransferEventArgs>(transfer_TransferProgressChanged);
try
{
BackgroundTransferService.Add(transferRequest);
chapterFileNames.Dequeue();
}
catch (InvalidOperationException)
{
}
catch (Exception)
{
}
}
void transfer_TransferStatusChanged(object sender, BackgroundTransferEventArgs e)
{
ProcessTransfer(e.Request);
}
void transfer_TransferProgressChanged(object sender, BackgroundTransferEventArgs e)
{
}
private void ProcessTransfer(BackgroundTransferRequest transfer)
{
switch (transfer.TransferStatus)
{
case TransferStatus.Completed:
if (transfer.StatusCode == 200 || transfer.StatusCode == 206)
{
using (IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication())
{
try
{
string filename = transfer.Tag;
string folderPath = string.Format(#"{0}{1}\{2}\",
DataSource.DOWNLOADLOCATION, activeReciter.ReciterID, chapter.ChapterID);
string fileFullPath = folderPath + filename;
if (!isoStore.DirectoryExists(Path.GetDirectoryName(folderPath)))
isoStore.CreateDirectory(Path.GetDirectoryName(folderPath));
if (isoStore.FileExists(fileFullPath))
isoStore.DeleteFile(fileFullPath);
isoStore.MoveFile(transfer.DownloadLocation.OriginalString, fileFullPath);
//Excpetion is thrown here
RemoveTransferRequest(transfer.RequestId);
}
catch (Exception ex)
{
MessageBox.Show("Error Occured: " + ex.Message + transfer.Tag, "Error",
MessageBoxButton.OK);
return;
}
}
}
break;
}
}
When moving file, it throws exception, I don't know what is wrong with moving (this happens on some of the files not all files).
From the MSDN Page, under File System Restrictions section:
You can create any additional directory structure you choose
underneath the root “/shared/transfers” directory, and you can copy or
move files after the transfer is complete to ensure that the
background transfer service does not modify the files, but attempting
to initiate a transfer using a path outside of the “/shared/transfers”
directory will throw an exception.
Make sure you are not trying to move your file outside from the /Shared/Transfers folder.

How to upload directory to ftp using ftplib?

I have problem with upload all files to ftp: I use ftplib.
I have a function to upload:
static void DirSearch(string sDir, FtpConnection ftp)
{
try
{
foreach (string d in Directory.GetDirectories(sDir))
{
string dirname = new DirectoryInfo(d).Name;
if (!ftp.DirectoryExists(dirname))
{
ftp.CreateDirectory(dirname);
}
ftp.SetCurrentDirectory(dirname);
foreach (string f in Directory.GetFiles(d))
{
Uri uri = new Uri(f);
ftp.PutFile(f, System.IO.Path.GetFileName(uri.LocalPath));
}
DirSearch(d, ftp);
}
}
catch (System.Exception e)
{
MessageBox.Show(String.Format("Błąd FTP: {0} {1}", e.Message), "Błąd wysyłania plików na FTP", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
ok this function uload files but I have in local disc files:
UPLOAD
--DIR1
----DIR3
------FILE4
----FILE3
--DIR2
----DIR4
------FILE7
----FILE5
----FILE6
--FILE1
--FILE2
In serwer I have:
UPLOAD
--DIR1
----DIR3
------DIR2
--------DIR4
----------FILE7
--------FILE5
--------FILE6
------FILE4
----FILE3
I dont have files in first folder and dir tree is wrong
i think foult is in line ftp.SetCurrentDirectory(dirname);
Well, your function is the problem - when you enter the folder, and copy the files into it, you are not going back to the previous folder, instead you are going more deeply into tree.
Simple solution for this is to rewrite this function to go back from the directory once it has iterated through it:
static void DirSearch(string sDir, FtpConnection ftp)
{
try
{
// First, copy all files in the current directory
foreach (string f in Directory.GetFiles(d))
{
Uri uri = new Uri(f);
ftp.PutFile(f, System.IO.Path.GetFileName(uri.LocalPath));
}
// For all directories in the current directory, create directory if there is
// no such, and call this function recursively to copy files.
foreach (string d in Directory.GetDirectories(sDir))
{
string dirname = new DirectoryInfo(d).Name;
if (!ftp.DirectoryExists(dirname))
{
ftp.CreateDirectory(dirname);
}
ftp.SetCurrentDirectory(dirname);
DirSearch(d, ftp);
}
}
catch (System.Exception e)
{
MessageBox.Show(String.Format("Błąd FTP: {0} {1}", e.Message), "Błąd wysyłania plików na FTP", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally{
// Go back!
ftp.SetCurrentDirectory(".."); //untested, but it should be fine, as I don't see cdup command in ftplib
}
}
Yes, you're right. You can save and assign the current directory on each call. Try this:
static void DirSearch(string sDir, FtpConnection ftp, string currentDirectory)
{
try
{
ftp.SetCurrentDirectory(currentDirectory);
foreach (string d in Directory.GetDirectories(sDir))
{
string dirname = new DirectoryInfo(d).Name;
if (!ftp.DirectoryExists(dirname))
{
ftp.CreateDirectory(dirname);
}
foreach (string f in Directory.GetFiles(d))
{
Uri uri = new Uri(f);
ftp.PutFile(f, System.IO.Path.GetFileName(uri.LocalPath));
}
string newCurrentDir = currentDirectory + dirname + "/";
DirSearch(d, ftp, newCurrentDir);
}
}
catch (System.Exception e)
{
MessageBox.Show(String.Format("Błąd FTP: {0} {1}", e.Message), "Błąd wysyłania plików na FTP", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
and method calling
DirSearch("your initial dir", your ftp connection, "/");
This code is good
static void DirSearch(string sDir, FtpConnection ftp, string currentDirectory)
{
try
{
ftp.SetCurrentDirectory(currentDirectory);
foreach (string f in Directory.GetFiles(sDir))
{
Uri uri = new Uri(f);
ftp.PutFile(f, System.IO.Path.GetFileName(uri.LocalPath));
}
foreach (string d in Directory.GetDirectories(sDir))
{
ftp.SetCurrentDirectory(currentDirectory);
string dirname = new DirectoryInfo(d).Name;
if (!ftp.DirectoryExists(dirname))
{
ftp.CreateDirectory(dirname);
}
string newCurrentDir = currentDirectory + "/" + dirname ;
DirSearch(d, ftp, newCurrentDir);
}
}
catch (System.Exception e)
{
MessageBox.Show(String.Format("Błąd FTP: {0} {1}", e.Message), "Błąd wysyłania plików na FTP", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

Categories