Reputation: 23
I have the following method to download files from an FTP server:
public bool DownloadFTP(string LocalDirectory, string RemoteFile,
string Login, string Password)
{
try
{
FtpWebRequest ftp = (FtpWebRequest)FtpWebRequest.Create(RemoteFile);
ftp.Credentials = new NetworkCredential(Login, Password);
ftp.KeepAlive = false;
ftp.Method = WebRequestMethods.Ftp.DownloadFile;
Form1 form = new Form1();
ftp.UseBinary = true;
long pesoarchivo = obtenertamano(RemoteFile, Login, Password);
ftp.Proxy = null;
ftp.EnableSsl = false;
LocalDirectory = Path.Combine(LocalDirectory, Path.GetFileName(RemoteFile));
using (FileStream fs = new FileStream(LocalDirectory, FileMode.Create,
FileAccess.Write, FileShare.None))
using (Stream strm = ftp.GetResponse().GetResponseStream())
{
int buffLength = 2048;
byte[] buff = new byte[buffLength];
// Leer del buffer 2kb cada vez
int contentLen=strm.Read(buff, 0, buffLength);
int bytes = 0;
try
{
while (contentLen > 0)
{
fs.Write(buff, 0, contentLen);
contentLen = strm.Read(buff, 0, buffLength);
bytes += contentLen;
int totalSize = (int)(pesoarchivo) / 1000;
if (bytes != 0)
{
// Console.WriteLine(((bytes / 1000) * 100 / totalSize).ToString()+" "+ totalSize.ToString());
backgroundWorker1.ReportProgress((bytes / 1000) * 100 / totalSize, totalSize);
// label1.Text = ((bytes / 1000) * 100 / totalSize).ToString();
}
else { }
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString()) }
}
return true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString())
return false;
}
Then I have a BackgroundWorker I called using a button. On the BackgroundWorker "DoWork" event I call the DownloadFTP method:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (descargaFTP(folder + "\\folder\\", ftpServer+ file, User, Password))
{
MessageBox.Show("Download Completed");
}
}
I want to add a button to cancel the FTPDownload. How can I do it?
Upvotes: 0
Views: 1398
Reputation: 70671
To do this, first get rid of the BackgroundWorker
and use the asynchronous members of the FtpWebRequest
class. As far as I know, the synchronous methods aren't cancellable, but you can use the FtpWebRequest.Abort()
method to cancel asynchronous operations.
For example:
// This needs to be an instance field instead of local variable, so that
// the object's `Abort()` method can be called elsewhere
private FtpWebRequest ftp;
public async Task DownloadFTPAsync(CancellationToken cancelToken,
string LocalDirectory, string RemoteFile, string Login, string Password)
{
try
{
ftp = (FtpWebRequest)FtpWebRequest.Create(RemoteFile);
ftp.Credentials = new NetworkCredential(Login, Password);
ftp.KeepAlive = false;
ftp.Method = WebRequestMethods.Ftp.DownloadFile;
ftp.UseBinary = true;
// [PD]: I don't know what this method does or even what the name
// means, but on the assumption that because it needs the login
// and password information, it may be doing some longer network
// I/O or something, I'm wrapping it in a task to ensure the calling
// thread remains responsive.
long pesoarchivo = await Task.Run(() => obtenertamano(RemoteFile, Login, Password));
// This is invariant, so might as well calculate it here, rather
// than over and over again in the stream I/O loop below
int totalSize = (int)(pesoarchivo) / 1000;
ftp.Proxy = null;
ftp.EnableSsl = false;
LocalDirectory = Path.Combine(LocalDirectory, Path.GetFileName(RemoteFile));
using (FileStream fs = new FileStream(LocalDirectory, FileMode.Create,
FileAccess.Write, FileShare.None))
using (Stream strm = (await ftp.GetResponseAsync()).GetResponseStream())
{
int buffLength = 2048;
byte[] buff = new byte[buffLength];
int contentLen;
// Check for cancellation. This will throw an exception
// if cancellation was requested; if you'd prefer, you can
// check the cancelToken.IsCancellationRequested property
// instead and just return, or whatever.
cancelToken.ThrowIfCancellationRequested();
while ((contentLen = await strm.ReadAsync(buff, 0, buff.Length)) > 0)
{
await fs.WriteAsync(buff, 0, contentLen);
// Here, the calling thread (i.e. the UI thread) is executing
// the code so just update whatever progress indication
// you want directly.
label1.Text = ((fs.Position / 1000) * 100 / totalSize).ToString();
// Keep checking inside the loop
cancelToken.ThrowIfCancellationRequested();
}
}
}
finally
{
ftp = null;
}
}
Notes:
Stream.CopyToAsync()
. However, of course in that case you would not be able to do the progress updates. I'm assuming the progress updates were an important feature, so I left the code as above, i.e. so that they can be made.async
methods the exception handling can be moved into the caller, I've removed it here and consolidated it there. Of course, you can do it either way if you like.With the download code implemented as above, you can call it like this (for example):
CancellationTokenSource _cancelTokenSource;
async void downloadButton_Click(object sender, EventArgs e)
{
// User clicked the button to start download
_cancelTokenSource = new CancellationTokenSource();
try
{
// For example, update the UI button state so user can't start a
// new download operation until this one is done, but they can cancel
// this one.
downloadButton.Enabled = false;
cancelButton.Enabled = true;
// I assume you know where to get the other method arguments, i.e.
// from "LocalDirectory" on; your incomplete code example didn't
// provide that detail. :(
await DownloadFTPAsync(_cancelTokenSource.Token,
LocalDirectory, RemoteFile, Login, Password);
}
catch (OperationCanceledException)
{
MessageBox.Show("Download was cancelled by user.");
}
catch (Exception ex)
{
// Some other error occurred
MessageBox.Show(ex.ToString())
}
finally
{
downloadButton.Enabled = true;
cancelButton.Enabled = false;
_cancelTokenSource = null;
}
}
And if the user clicks your cancel button, you handle the Click
event like this:
void cancelButton_Click(object sender, EventArgs e)
{
ftp.Abort();
_cancelTokenSource.Cancel();
}
Calling the FtpWebRequest.Abort()
method will interrupt any actual asynchronous FTP I/O operation, and of course calling CancellationTokenSource.Cancel()
will cause the CancellationToken
to indicate cancel is requested.
Upvotes: 1