All classes on GitHub repo
This is the code for downloading files from a FTP-server running Mozilla Fileserver.
FtpItem
is the interface for the two classes FtpFile
and FtpFolder
; FtpFile
represents a file on the server and FtpFolder
represents a Folder directory on the server.
FtpUser
might be better named FtpHandler
or something, it was made to avoid having global variables in the WinForms UI class together with non-UI specific logic.
TransferProgress
is a class which is passed from FtpItem
and FtpFile
through events to update the progress of a download or tell the UI when a directory is downloaded and ready to be shown in the listbox.
FtpFileList
is the name of the listbox in which FtpItems are shown to the user. Debuglist
currently prints information about what's happening behind the scenes to avoid stepping through to check non-critical logic.
Right now it seems like only one task can be run in the background at any time, i.e it's not possible to change to a directory that's not currently downloaded while a file is transferring; I'm currently working on fixing this.
FtpItem Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FtpLibrary
{
public interface FtpItem
{
string Name { get; }
string Path { get; }
int FileSize { get; }
SortedList<string, FtpItem> List { get; }
void Download(string s, string t, string p);
FtpItem PreviousItem { get; }
int Count { get; }
event Action<TransferProgress> DownloadProgress;
}
}
FtpFolder Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Diagnostics;
namespace FtpLibrary
{
public class FtpFolder : FtpItem
{
private static int _folderAmount = 0;
private string _folderName;
private string _folderPath;
private FtpItem _parent;
private SortedList<string, FtpItem> _folderList;
public event Action<TransferProgress> DownloadProgress;
public FtpFolder(string foldername, string folderpath, FtpItem parent)
{
_folderName = foldername;
_folderPath = folderpath;
_parent = parent;
_folderList = new SortedList<string, FtpItem>();
_folderAmount += 1;
}
public string Name { get { return _folderName; } }
public string Path { get { return _folderPath; } }
public SortedList<string, FtpItem> List { get { return _folderList; } }
public int FileSize { get { return 0; } }
public FtpItem PreviousItem { get { return _parent; } }
public void Download(string _username, string _password, string _savePath=null)
{
var ellapsedTime = Stopwatch.StartNew();
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri(this.Path));
request.Credentials = new NetworkCredential(_username, _password);
request.Proxy = null;
request.UseBinary = true;
request.EnableSsl = true;
ServicePoint sp;
sp = request.ServicePoint;
sp.ConnectionLimit = 1;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(responseStream))
{
SortedList<string, FtpItem> updateList = new SortedList<string, FtpItem>();
FtpFolder tempFolder = new FtpFolder(" /cd ..", Path, this);
updateList.Add(tempFolder.Name, tempFolder);
while (!reader.EndOfStream)
{
string ListDetails = reader.ReadLine();
var listarray = ListDetails.Split(new char[] { ' ' }, 9, StringSplitOptions.RemoveEmptyEntries);
if (listarray.ElementAt(4) == "0")
{
tempFolder = new FtpFolder(listarray.ElementAt(8), Path + listarray.ElementAt(8) + '/', this);
updateList.Add(tempFolder.Name, tempFolder);
}
else
{
FtpFile tempFile = new FtpFile(listarray.ElementAt(8), listarray.ElementAt(4), Path + listarray.ElementAt(8));
updateList.Add(tempFile.Name, tempFile);
}
}
responseStream.Close();
response.Close();
reader.Close();
ellapsedTime.Stop();
RefreshList(updateList);
TransferProgress progress = new TransferProgress(0, 0);
progress.FileSize = FileSize;
progress.Complete = true;
progress.EllapsedTime = (int)ellapsedTime.ElapsedMilliseconds;
progress.Message = $"The FileSize is: {FileSize} and The name is: {Name}";
OnDownloadProgress(progress);
}
}
private void RefreshList(SortedList<string, FtpItem> updateList)
{
if (_folderList.Count == 0)
{
_folderList = updateList;
}
else
{
SortedList<string, FtpItem> removeList = new SortedList<string, FtpItem>();
foreach (var i in _folderList)
{
if (!updateList.ContainsKey(i.Key))
{
removeList.Add(i.Key, i.Value);
}
}
foreach (var i in removeList) // removes deleted items
{
_folderList.Remove(i.Key);
}
foreach (var i in updateList)
{
if (!_folderList.ContainsKey(i.Key)) // check if any items have been added to the server
{
_folderList.Add(i.Key, i.Value);
}
}
}
}
private void OnDownloadProgress(TransferProgress progress)
{
var update = DownloadProgress;
if (update != null)
{
update(progress);
}
}
public int Count { get { return _folderAmount; } }
~FtpFolder() { _folderAmount--; }
}
}
FtpFile Class
using System;
using System.Collections.Generic;
using System.Net;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using System.Diagnostics;
namespace FtpLibrary
{
public class FtpFile : FtpItem
{
private string _fileName;
private int _fileSize;
private string _filePath;
private static int _fileAmount = 0;
public event Action<TransferProgress> DownloadProgress;
public FtpFile(string filename, string filesize, string filepath)
{
_fileName = filename;
_fileSize = Int32.Parse(filesize);
_filePath = filepath;
_fileAmount += 1;
}
public string Name { get { return _fileName; } }
public string Path { get { return _filePath; } }
public int FileSize { get { return _fileSize; } }
public SortedList<string, FtpItem> List { get { return null; } }
public void Download(string _username, string _password, string _savePath = null)
{
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri(this.Path));
request.Credentials = new NetworkCredential(_username, _password);
request.Proxy = null;
request.UseBinary = true;
request.EnableSsl = true;
ServicePoint sp;
sp = request.ServicePoint;
sp.ConnectionLimit = 1;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
request.Method = WebRequestMethods.Ftp.DownloadFile;
FileStream newFile;
FileInfo file = new FileInfo($"{_savePath}{this.Name}");
if (File.Exists($"{_savePath}{this.Name}"))
{
request.ContentOffset = file.Length;
newFile = new FileStream($"{_savePath}{this.Name}", FileMode.Append, FileAccess.Write);
}
else
{
newFile = new FileStream($"{_savePath}{this.Name}", FileMode.Create, FileAccess.Write);
}
using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
using (Stream downloadStream = response.GetResponseStream())
using (newFile)
{
byte[] buffer = new byte[128 * 1024];
int read;
var ellapsedTime = Stopwatch.StartNew();
TransferProgress progress = new TransferProgress(0, (int)newFile.Length);
progress.FileSize = FileSize;
while ((read = downloadStream.Read(buffer, 0, buffer.Length)) > 0)
{
newFile.Write(buffer, 0, read);
progress.EllapsedTime = (int)ellapsedTime.ElapsedMilliseconds;
progress.TotalTransfered = (int)newFile.Length;
progress.Message = $"The FileSize is: {FileSize} and The name is: {Name}";
OnDownloadProgress(progress);
}
ellapsedTime.Stop();
newFile.Close();
downloadStream.Close();
response.Close();
}
}
public FtpItem PreviousItem { get { return this; } }
public int Count { get { return _fileAmount; } }
private void OnDownloadProgress(TransferProgress progress)
{
var update = DownloadProgress;
if(update != null)
{
update(progress);
}
}
~FtpFile() { _fileAmount--; }
}
}
FtpUser Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net;
namespace FtpLibrary
{
public class FtpUser
{
private string _userName;
private string _password;
private string _serverAdress;
private string _savePath;
private FtpItem _serverObject;
private FtpWebRequest _ftpWebRequest;
private FtpWebResponse _ftpWebResponse;
private ServicePoint _servicePoint;
private string _errorMessage;
private bool _verified = false;
public event Action<TransferProgress> TaskEvent;
private bool Verified { get { return _verified; } set { _verified = value; } }
public string UserName { get { return _userName; } set { _userName = value; } }
public string Password { get { return _password; } set { _password = value; } }
public string SavePath { get { return _savePath; } set { _savePath = value; } }
public bool IsVerified { get { return _verified; } }
public string ServerAdress { get { return _serverAdress; } set { _serverAdress = value; } }
public string ErrorMessage { get { return _errorMessage; } set { _errorMessage = value; } }
public FtpItem ServerObject { get { return _serverObject; } set { _serverObject = value; } }
public FtpWebRequest Request { get { return _ftpWebRequest; } set { _ftpWebRequest = value; } }
public FtpWebResponse Response { get { return _ftpWebResponse; } set { _ftpWebResponse = value; } }
public bool VerifyUser()
{
if (CreateRequest() && CreateResponse())
{
_verified = true;
_serverObject = new FtpFolder("root", _serverAdress, null);
DownloadSelected(ServerObject);
return _verified;
}
else
{
return _verified;
}
}
private bool CreateRequest(int Connections = 1)
{
if (!CreateUri())
{
return false;
}
else
{
Request.Credentials = new NetworkCredential(UserName, Password);
Request.Proxy = null;
Request.UseBinary = true;
Request.EnableSsl = true;
_servicePoint = Request.ServicePoint;
_servicePoint.ConnectionLimit = Connections;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
Request.Method = WebRequestMethods.Ftp.PrintWorkingDirectory;
return true;
}
}
private bool CreateResponse()
{
try
{
Response = (FtpWebResponse)Request.GetResponse();
return true;
}
catch (WebException e)
{
ErrorMessage = $"CreateResponse: { e.Message}";
return false;
}
}
private bool CreateUri()
{
try
{
_ftpWebRequest = (FtpWebRequest)WebRequest.Create(new Uri(ServerAdress));
}
catch (UriFormatException e)
{
ErrorMessage = e.Message;
return false;
}
return true;
}
public void PreviousFolder()
{
if (ServerObject != null)
{
ServerObject = ServerObject.PreviousItem;
}
}
public bool ChangeFolder(string folder)
{
if (ServerObject != null && IsFolder(ServerObject.List[folder]))
{
ServerObject = ServerObject.List[folder];
if (ServerObject.List.Count == 0)
{
DownloadSelected(ServerObject);
}
else
{
TransferProgress progress = new TransferProgress(0, 0);
progress.Complete = true;
FtpItem_TaskEvent(progress);
}
return true;
}
return false;
}
public void DownloadFile(string file)
{
DownloadSelected(ServerObject.List[file]);
}
private void DownloadSelected(FtpItem item)
{
if (IsVerified)
{
item.DownloadProgress += FtpItem_TaskEvent;
Task t = new Task(() => item.Download(UserName, Password, SavePath));
t.Start();
// Task t = Task.Factory.StartNew( () => item.Download(UserName, Password));
}
}
private bool IsFolder(FtpItem item)
{
return item.FileSize == 0;
}
private void FtpItem_TaskEvent(TransferProgress progress)
{
var update = TaskEvent;
if (update != null)
{
update(progress);
}
}
}
}
TransferProgress Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FtpLibrary
{
public class TransferProgress
{
private int _ellapsedTime;
private int _totalTransfered;
private int _fileSize;
private static int _count = 0;
private bool _completed = false;
private string _message;
public TransferProgress(int ellapsedTime, int totalTransferred)
{
_ellapsedTime = ellapsedTime;
_totalTransfered = totalTransferred;
_count++;
}
public int EllapsedTime { get { return _ellapsedTime; } set { _ellapsedTime = value; } }
public int TotalTransfered { get { return _totalTransfered; } set { _totalTransfered = value; } }
public int FileSize { get { return _fileSize; } set { _fileSize = value; } }
public bool Complete { get { return _completed; } set { _completed = value; } }
public int Count { get { return _count; } set { _count++; } }
public int PercentDone { get { return (int)((((float)TotalTransfered) / ((float)FileSize)) * 100); } }
public string Message { get { return _message; } set { _message = value; } }
public int Seconds()
{
return (((FileSize - TotalTransfered) / (TotalTransfered / EllapsedTime)) / 1000) % 60;
}
public int Minutes()
{
return (((FileSize - TotalTransfered) / (TotalTransfered / EllapsedTime)) / 1000) / 60;
}
public string TransferSpeed()
{
if ((TotalTransfered / EllapsedTime) <= 1000)
{
return $"{TotalTransfered / EllapsedTime} kb/s ";
}
else
{
return $"{ (TotalTransfered / EllapsedTime) / 1000} + Mb/s ";
}
}
}
}
WinForms UI
This is mostly to show how i use Events to update the Listbox and progressbar while keeping it thread safe.
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using FtpLibrary;
using System.Diagnostics;
using System.Collections.Generic;
using System.IO;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
private FtpUser currentUser;
public Form1()
{
InitializeComponent();
ftpfileList.MouseDoubleClick += FtpfileList_MouseDoubleClick;
SavePath.Text = $"C:/Users/{Environment.UserName}/Ftp";
if (!Directory.Exists($"C:/Users/{Environment.UserName}/Ftp"))
{
Directory.CreateDirectory($"C:/Users/{Environment.UserName}/Ftp");
}
}
private void FtpfileList_MouseDoubleClick(object sender, MouseEventArgs e)
{
try
{
UiListChange(ftpfileList.GetItemText(ftpfileList.SelectedItem).Substring(0, ftpfileList.GetItemText(ftpfileList.SelectedItem).IndexOf(" , Size: ")));
}
catch (System.ArgumentOutOfRangeException ex)
{
debugList.Items.Add(ex.Message);
}
}
private void UiListChange(string _currentFtpItem)
{
if (_currentFtpItem == " /cd ..")
{
currentUser.PreviousFolder();
PrintDirectory(currentUser.ServerObject);
}
else if (currentUser.ChangeFolder(_currentFtpItem))
{
}
else // Time to download a file
{
currentUser.DownloadFile(_currentFtpItem);
}
}
private void Connect_Click(object sender, EventArgs e) // init a session
{
if (Connect.Text == "Connect")
{
currentUser = new FtpUser();
currentUser.UserName = UserName.Text;
currentUser.Password = Password.Text;
currentUser.ServerAdress = $"ftp://{Adress.Text}/";
currentUser.SavePath = $"C:/Users/{Environment.UserName}/Ftp/";
currentUser.TaskEvent += FtpItem_ProgressChanged;
if (currentUser.VerifyUser())
{
Connect.Text = "Refresh";
}
else
{
Connect.Text = "Connect";
MessageBox.Show(currentUser.ErrorMessage);
}
}
}
private void PrintDirectory(FtpItem item)
{
ftpfileList.Items.Clear();
debugList.Items.Clear();
foreach (var t in item.List)
{
ftpfileList.Items.Add(t.Value.Name + " , Size: " + t.Value.FileSize + " Byte");
}
}
private void FtpItem_ProgressChanged(TransferProgress progress)
{
if (progress.FileSize == 0)
{
if (progress.Complete == true)
{
if (ftpfileList.InvokeRequired)
{
ftpfileList.Invoke(new Action<TransferProgress>(FtpItem_ProgressChanged), new object[] { progress });
}
else
{
PrintDirectory(currentUser.ServerObject);
debugList.Items.Add($"Operation took: {progress.EllapsedTime} MilliSeconds");
if (progress.Message != null)
{
debugList.Items.Add(progress.Message);
}
}
}
}
else
{
if (progressBar1.InvokeRequired)
{
progressBar1.Invoke(new Action<TransferProgress>(FtpItem_ProgressChanged), new Object[] { progress });
}
else
{
if (progress.TotalTransfered != 0)
{
SpeedLabel.Text = $"{progress.PercentDone} % Complete, Speed: {progress.TransferSpeed()}\n Time Left: {progress.Minutes()} Minutes & {progress.Seconds()} seconds";
progressBar1.Value = progress.PercentDone;
debugList.Items.Add(progress.Message);
}
if (progressBar1.Value == 100)
{
progressBar1.Value = 0;
debugList.Items.Clear();
SpeedLabel.Text = "Download Complete";
}
}
}
}
private void OpenFtpFolder_Click(object sender, EventArgs e)
{
if (!Directory.Exists($"C:/Users/{Environment.UserName}/Ftp"))
{
Directory.CreateDirectory($"C:/Users/{Environment.UserName}/Ftp");
}
System.Diagnostics.Process.Start($"C:/Users/{Environment.UserName}/Ftp");
}
private void CancelDownload_Click(object sender, EventArgs e)
{
}
private void ProgressLabel_Click(object sender, EventArgs e)
{
}
}
}