|
Introduction
This code is for implementing FTP client capabilities in applications without needing to know the underlying protocol. The general idea is 'easy to use' while giving you access to (mainly) information you would also want to know about the upload/download process. One of the things that pushed me towards writing it was a lack of pre-existing code that lets you have progress information. The DoDownload() and DoUpload() functions return the bytes received, and the total bytes and file size info are available as well for any further calculations you might want to do.
There is no screenshot of the demo application because the purpose of this code is not to provide you with a functioning application. There is a demo application though, just to demonstrate how I intended the code to be used. It's a small command line FTP client with very basic functionality that is meant to provide a good reference on how to use the features the ftplib code provides.
Using the code
See the sample application for the best example of using it. I've made notes in the comments about how to use it, and there is info in the comments of ftplib.cs about adding on to it, functions you should call to keep from re-inventing the wheel, and how to deal with critical and non-critical errors.
Opening a Connection and Changing to a Directory
Opening a connection is pretty straightforward as you see below:
FTP ftplib = new FTP();
try
{
ftplib.Connect("ftp.microsoft.com",
"anonymous",
"anonymous@noplace.net");
ftplib.ChangeDir("directory_foo/");
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
Downloading a File
Downloading and uploading are pretty much the exact same process. The only real difference is using the upload methods in place of the download methods. try
{
int perc = 0;
ftplib.OpenDownload("somefile_on_the_server.txt", true);
while(ftplib.DoDownload() > 0)
{
perc = (int)((ftplib.BytesTotal * 100) / ftplib.FileSize);
Console.Write("\rDownloading: {0}/{1} {2}%",
ftplib.BytesTotal, ftplib.FileSize, perc);
Console.Out.Flush();
}
Console.WriteLine("");
}
catch(Exception ex)
{
Console.WriteLine("");
Console.WriteLine(ex.Message);
}
Listing Files
Listing files is a very simple process as well. Not all servers are guaranteed to return a file listing in the same format either. An example is, MS IIS can be configured to return a long UNIX like directory listing or another one with some tags like <DIR> in them. Be prepared to handle either one when processing the list returned. try
{
foreach(string f in ftplib.List())
Console.WriteLine(f);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
Points of Interest
The DoUpload() and DoDownload() functions have had significant changes made. They no longer return the percentage complete in a transfer. The former approach was OK when I had originally started this code for a specific task, but to make it more compatible with various FTP server implementations, it had to be changed. The problem that existed with this approach is that file sizes are not always available, which makes progress monitoring not possible in all cases. To avoid potential problems, I have changed the code to return the bytes sent/received in a transfer. Provided the file sizes are available, all the information needed to calculate the progress is available within the class. Any code based on the old methods has to be changed to cope with the new process of a file transfer. See the example code in this article or in the FTP client included with the source, for more information on how to implement a file transfer.
History
I wrote this code with FTPFactory.cs as a reference. You can find FTPFactory.cs on this site. I've listed the authors of FTPFactory.cs in the ftplib.cs source as well as their email addresses. Any and all bugs with this code should not be emailed to them.
Updates
- 7/28/2005
I've updated the code a bit for speed improvements. Those of you who may be using the older code will notice a significant increase in speed for file listings, file transfers, or anything else that requires a data channel. See the ChangeLog.txt file in the project for more information on what has been changed.
- 08/23/2005
There is a new developer! Filipe Madureira has submitted quite a few bug fixes that resolve issues mainly with the MS IIS FTP service, but it also corrects one issue that existed with all FTP services. Thanks for the help on improving the code Filipe! See the ChangeLog.txt for detailed information on what has been changed. You will also notice that all of the source includes the BSD license with the advertising clause omitted. We were approached about licensing so I had to do something. I think the BSD license is the right choice to make everyone happy. The intent of this project is to provide a useful library to the public for whatever means they see fit. This license will be good for personal and commercial interests.
- 05/04/06
Quite a few people have sent in additions and improvements to the code. The most notable is support for active mode FTP sessions. Carlo Andreoli submitted a fully functional implementation of the PORT command for data channels. Many thanks go out to Carlo for this code. Sloan Holliday submitted code for retrieving raw FTP dates as well as a DateTime object on a file residing on a FTP server. While not the primary focus of this article, Daniel Tamajón made improvements to the client that's included with this article. He added in command line support that would be very handy for scripting. For a complete list of changes, please see the ChangeLog.txt file included with the source code. Thanks to everyone who has helped in improving the code! Note: If you haven't already read the Points of Interest section of this document, you should do so before implementing this new code in any project using an older version of the ftplib.cs code.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 238 (Total in Forum: 238) (Refresh) | FirstPrevNext |
|
|
 |
|
|
Hello
If you search a comfortable and reusable FTP client,
-- which is running on .NET Framework 1.1 or higher, -- which can automatically put together splitted files on the server, -- which allows to download only a part of a file on the server, -- which allows to resume any broken download, -- which automatically starts a separate thread, -- which can be aborted any time from your main thread, -- which supports UTF8 encoded filenames, -- which has a built-in download scheduler, -- which has a built-in bandwidth control, -- which has a built-in preview function for the download of movies, -- which automatically reconnects the server after an error has occurred, -- which displays download progress in percent and in bytes and the remaining time, -- which writes a detailed logging for all operations it does, -- which is based on Wininet.dll and has one workaround for each of the 4 known Wininet.dll bugs, -- which is very well tested and bug-free, -- which is written by a very experienced programmer and has a very clean and well documented sourcecode,
then have a look at this project:
ElmueSoft Partial FTP Downloader[^]
Elmü
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
FtpWebRequest ftp = (FtpWebRequest)FtpWebRequest.Create("ftp://ftp.server.com"); ftp.Method = WebRequestMethods.Ftp.ListDirectory; ftp.Credentials = new NetworkCredential("user", "pass"); ftp.UseBinary = true; FtpWebResponse wr = (FtpWebResponse)ftp.GetResponse(); System.IO.Stream stream = wr.GetResponseStream(); StreamReader sr = new StreamReader(stream);
string ftpdirorfile = null; while ((ftpdirorfile = sr.ReadLine()) != null) { }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
There was no such thing has FtpWebRequest when I wrote this library. It was developed for .net 1.1 and FtpWebRequest didn't come around until 2.0.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
FtpWebRequest is badly broken (as in "if the server is broken, the client cannot recover because you cannot hard-reset the sockets").
I will try this one tomorrow.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Experts,
This is my requirement!
There are data in my FTP server which is user specific. The admin can download the different users data. My Problem is I am trying to abort the current operation when the admin wants to download the data for the different user. This aborting can performed when the user is downloading a file. It results in different errors each time. I am not sure what I am doing wrong.
public void Abort() { SendCommand("ABOR"); ReadResponse(); if (response != 225) { string str = responseStr; } }
Please help me..
Many thanks, Balamurugan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, when I'm download large files (24MB) allways a timeout message "Timed out on server to respond" are displayd and the file is (certainly) not downloaded. Is there a change to increase the time out value or retrigger it during the DoDownload() loop?
|
| Sign In·View Thread·PermaLink | 1.67/5 (2 votes) |
|
|
|
 |
|
|
I recommend to increase the buffer size in DoUpload from 512 bytes to 4096 bytes to increase maximum upload speed - otherwise you're limited to about 1MB/s, which is OK for most connections, but not if you're on a faster line.
The same would very likely apply to DoDownload's buffer as well.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I've found its handy to use an FTP client library to manage the FTP connection, most of them are extendible to allow you to customise the functionality to your own needs.
One I use is www.ftpclient.co.uk, it works well and despite the annoying status message popups in the demo code, they can be suppressed by removing the alert box.
|
| Sign In·View Thread·PermaLink | 1.50/5 (3 votes) |
|
|
|
 |
|
|
Hi
I am getting the error 550 OPERATION NOT COMPLETE AT FTPLIB.FTP.GETFILESIZE(STRING FILENAME) when trying to upload a file. Do you know what will cause this.
Thanks Frankie
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
No problem, let me know if you need any help adjusting the code to work without the ftp file size.
|
| Sign In·View Thread·PermaLink | 4.00/5 (1 vote) |
|
|
|
 |
|
|
I love this code. It took me 3 or 4 differnt libraries that didn't work before I found this one.
Here is what I am trying to accomplish: FTP server has directory Structure Needed to be emulated on PC PC already has this structure, too.
I need to go through the directories on the server, and download all PNG and JPG files to their respective directories on the PC.
I had a test set up with about 10 files per directory, they go 3 deep, and a total of about 6 or 8 directories / sub directories. My test ran fine. I was able to navigate through the directories, and grab the files. I also DO NOT over write the files.
This worked great in TEST.
Then we set up the Production FTP server. THe directory structure is identical. The problem is there are THOUSANDS of files in there. This was the intent... an easy way for users to download these thousands of files (approaching 750 MB) and have them automatically put in the correct folders on their system.
What happens is that I am in folder RootA/SubB/ It runs, and pauses, then changes Directories to RootB/ It tries to download the files from RootA/SubB FROM /RootB. Of course, the files don't exist. On another note, sometimes it does work. But most of the time it does not.
Has anyone else had anything like this? Is there a fix? I have looked through the ftplib.cs, played around with timeouts, and the sleep thread, but that didn't help.
Thanks, Brad
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hey Brad, I haven't tried anything like that, if you wouldn't mind emailing me your code that's doing the recursive uploading and downloading I'll take a look and see if I can track down what's causing the issue when I get some spare time. If not, here's some recursive code that was written by Carlo Andreoli for this library that you might want to take and modify to do what you want. This code has been tested a little by myself, but I think Carlo used it on a day to day basis so it should be good code. I've got a newer version of the FTP library that I've not uploaded nor tested very well (mostly same code with new additions to functionality like the code pasted below) if you want me to email it to you.
/// /// Declare a delegate type for Upload/DownloadDir progress feedback /// public delegate void FTPCopyDirProgress(string sCurrDir, string sCurrFile, long fileSize, long bytesXFerred);
/// /// Download a remote directory and its contents /// /// Directory name /// Delegate for receiving progress info, or null public void DownloadDir(string dirname, FTPCopyDirProgress ProgressInfo) { DownloadDir(dirname, dirname, false, ProgressInfo); }
/// /// Download a remote directory and its contents, allowing to delete the /// remote directory after the download is done /// /// Directory name /// Delete the remote directory after the download /// Delegate for receiving progress info, or null public void DownloadDir(string dirname, bool del_remote, FTPCopyDirProgress ProgressInfo) { DownloadDir(dirname, dirname, del_remote, ProgressInfo); }
/// /// Download a remote directory and its contents /// /// The name of the source directory on the FTP server /// The name of the local directory to save as /// Delegate for receiving progress info, or null public void DownloadDir(string dirname, string localdirname, FTPCopyDirProgress ProgressInfo) { DownloadDir(dirname, localdirname, false, ProgressInfo); }
/// /// Download a remote directory and its contents, allowing to delete the /// remote directory after the download is done /// /// The name of the source directory on the FTP server /// The name of the local directory to save as /// Delete the remote directory after the download /// Delegate for receiving progress info, or null public void DownloadDir(string remote_dirname, string local_dirname, bool del_remote, FTPCopyDirProgress ProgressInfo) { string[] sResults; int ix; Regex r = new Regex(" +"); // to parse ls/dir entries
// ensure that local_dirname is an absolute path, and create the local // directory if not already there if (!Path.IsPathRooted(local_dirname)) local_dirname = Path.GetFullPath(local_dirname); if (!Directory.Exists(local_dirname)) Directory.CreateDirectory(local_dirname);
// move to the specified directory on the FTP server side string sCurrDir = GetWorkingDirectory(); ChangeDir(remote_dirname);
// for each directory in the current directory, call itself // recursively to download it ArrayList ld = ListDirectories(); foreach (string sDir in ld) { string sWorkDir; ix = -1;
// extract the directory name if (sDir[0] != 'd') // DOS/Windows-style entry ix = 3; else // Unix-style entry ix = 8;
sResults = r.Split(sDir, ix + 1); if (sResults.GetLength(0) != ix + 1) { throw new Exception("Unrecognized entry format: " + sDir); } sWorkDir = sResults[ix];
// download the directory DownloadDir(sWorkDir, Path.Combine(local_dirname, sWorkDir), del_remote, ProgressInfo); }
// now download each file in the current directory ld.Clear(); ld = ListFiles(); foreach (string sFile in ld) { string sWorkFile; ix = -1;
// extract the file name if (sFile[0] != '-') // DOS/Windows-style entry ix = 3; else // Unix-style entry ix = 8;
sResults = r.Split(sFile, ix + 1); if (sResults.GetLength(0) != ix + 1) { throw new Exception("Unrecognized entry format: " + sFile); } sWorkFile = sResults[ix];
// download the file OpenDownload(sWorkFile, Path.Combine(local_dirname, sWorkFile), false); while(DoDownload() > 0) { if (ProgressInfo != null) ProgressInfo(remote_dirname, sWorkFile, file_size, bytes_total); } // remove the downloaded file from the FTP server, if required if (del_remote) RemoveFile(sWorkFile); }
// go back to the original directory ChangeDir(sCurrDir); // remove the downloaded directory from the FTP server, if required if (del_remote) RemoveDir(remote_dirname); }
/// /// Upload a local directory and its contents /// /// Directory name /// Delegate for receiving progress info, or null public void UploadDir(string dirname, FTPCopyDirProgress ProgressInfo) { UploadDir(dirname, Path.GetDirectoryName(dirname), false, ProgressInfo); }
/// /// Upload a local directory and its contents, allowing to delete the /// local directory after the upload is done /// /// Directory name /// Delete the local directory after the upload /// Delegate for receiving progress info, or null public void UploadDir(string dirname, bool del_local, FTPCopyDirProgress ProgressInfo) { UploadDir(dirname, Path.GetDirectoryName(dirname), del_local, ProgressInfo); }
/// /// Upload a local directory and its contents /// /// The name of the local directory to upload /// The name of the target directory on the FTP server /// Delegate for receiving progress info, or null public void UploadDir(string localdirname, string remotedirname, FTPCopyDirProgress ProgressInfo) { UploadDir(localdirname, Path.GetDirectoryName(remotedirname), false, ProgressInfo); }
/// /// Upload a local directory and its contents, allowing to delete the /// local directory after the upload is done /// /// The name of the local directory to upload /// The name of the target directory on the FTP server /// Delete the local directory after the upload /// Delegate for receiving progress info, or null public void UploadDir(string localdirname, string remotedirname, bool del_local, FTPCopyDirProgress ProgressInfo) { // ensure that localdirname is an absolute path, and check that the local // directory is already there if (!Path.IsPathRooted(localdirname)) localdirname = Path.GetFullPath(localdirname); if (!Directory.Exists(localdirname)) { throw new Exception("Local directory: " + localdirname + " not found"); }
// create the target directory on the FTP server and move to it string sCurrDir = GetWorkingDirectory(); try { ChangeDir(remotedirname); } catch { try { MakeDir(remotedirname); ChangeDir(remotedirname); } catch { throw new Exception("Failed to create the remote directory: " + remotedirname); } }
// for each directory in the current local directory, call itself // recursively to upload it string[] sDirs = Directory.GetDirectories(localdirname); for (int ix = 0; ix < sDirs.GetLength(0); ix++) { // upload the directory UploadDir(sDirs[ix], Path.GetFileName(sDirs[ix]), del_local, ProgressInfo); }
// now upload each file in the current directory string[] sFiles = Directory.GetFiles(localdirname); for (int ix = 0; ix < sFiles.GetLength(0); ix++) { // upload the file string sWorkFile = Path.GetFileName(sFiles[ix]); OpenUpload(sFiles[ix], sWorkFile); while(DoUpload() > 0) { if (ProgressInfo != null) ProgressInfo(remotedirname, sWorkFile, file_size, bytes_total); } // remove the uploaded file from the local directory, if required if (del_local) File.Delete(sFiles[ix]); }
// go back to the original directory ChangeDir(sCurrDir); // remove the uploaded local directory, if required if (del_local) Directory.Delete(localdirname); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
JP,
I was reading through your code. It appears you may have the functionality I need. I am super rusty on delegates (been doing too many web sites lately). How would I call the download directory using the delegate? I will want to show the progress bar, so the users know it is doing something.
I have limited mine to download ony PNG and JPG files, and I see where I could change this code to make it work for me. I just copy and pasted the code you have above into my current ftplib class file. Will that work, or should I get the whole new class file?
Have you tested this enough to feel that it can handle my FTP needs? Or, would I be your Guinea Pig?
Thanks, Brad
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, I've tested it very little, Carlo on the other hand wrote it for his own personal use and emailed it to me to include in the project. I haven't had any updates for it, haven't heard from him in a while to be honest so I'm guessing it works fairly well (take that for what it's worth ) Anyhow, here's the example that Carlo wrote for the ftp client:
// this is our callback function, the delegate static void DirCopyProgress(string sCurrDir, string sCurrFile, long fileSize, long byteCount) { string sMsg;
sMsg = string.Format(@"{0}F: {1}\{2} - B: {3}/{4}", "\r", sCurrDir, sCurrFile, byteCount.ToString(), fileSize.ToString()); sMsg = sMsg.PadRight(78, ' '); Console.Write(sMsg); Console.Out.Flush(); }
static void downloaddir(string command) { try { if (!ftplib.IsConnected) { Console.WriteLine("E: Must be connected to a server."); return; }
string sDir = Regex.Replace(command, "getd ", ""); // Here we call the DownloadDir recursive function and pass the callback/delegate, // new FTP.FTPCopyDirProgress(DirCopyProgress) to the function ftplib.DownloadDir(sDir, sDir, false, new FTP.FTPCopyDirProgress(DirCopyProgress)); string sMsg = "\rDone"; sMsg = sMsg.PadRight(79, ' '); Console.WriteLine(sMsg); } catch(Exception ex) { Console.WriteLine(""); Console.WriteLine(ex.Message); } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, I have put the newer ftplib code in, and used the delegate, and I get different errors. Everything from a timeout, to something about a download speed, and an 'Unrecognized entry format:'.
The part that bothers me is that it is not always the same error, even if I don't change anything. I don't even know where the exceptions are getting thrown, except for the 'Unrecognized entry format:'. I found it.
It even worked ONCE. I didn't change anything, and it didn't work the very next time, or since. I have tried little things here and there, and I am not sure what to do. If you can get in touch with Carlos, and find out if he had any issues, and what he did about them, that would be great.
Thanks, Brad
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Recently, I tried to use this library to a ftp testing. I found a strange question, as below
Byte[] bytes = new Byte[512]; long bytes_got; try { bytes_got = file.Read(bytes, 0, bytes.Length); bytes_total += bytes_got; data_sock.Send(bytes, (int)bytes_got, 0); if (bytes_got <= 0) { ... CloseDataSocket(); ReadResponse(); ... } (1) I don't believe it can send over than 512 bytes to remote ftp server. bytes array just only read 512 bytes from the file.
(2) After one file was uploaded to ftp server with no error, CloseDataSocket() never be called, Why?!
Best regards, =============== Eric Hu
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The file object is a member of the FTP class, it's not local to the function itself which means every time file.Read(bytes, 0, bytes.Length) is called it advances it's own internal file pointer to the new location in the file and it retains even after DoUpload() exits. It reads up to 512 bytes each time DoUpload() is called. CloseDataSocket() is called when bytes_got <= 0 which means when 0 bytes were read from the file which is an indication that you have reached the end of the file.
DoUpload() needs to be called in a loop, same as DoDownload() as shown in the download example in this article. If you're only calling DoUpload() one time and the file is bigger than 512 bytes, you are right, it will never upload the entire file and never close the data socket because the upload is incomplete.
while(myFtp.DoUpload() > 0) { // do something here like show progress feedback to the user }
Hope this helps, J.P.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi~ J.P.
Thanks for your reply. I missed the MSDN statement about FileStream.Read().
...The returned value is the actual number of bytes read, or zero if the end of the stream is reached. If the read operation is successful, the current position of the stream is advanced by the number of bytes read....
BTW, I get another issue, that PASV cannot work fine in NAT because DNS will translate to another ip. How can I do? Why not ftp lib uses raw IP in PASV response directly?
Best regards, ============== Eric Hu
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The library works fine behind NAT with PASV. Pasv is configured at the server, the server is told what ports and most if not all have an option to tell it what IP address to use for the data connection or at the very least, what external IP the server is accessed through.
The ftp code does use the IP supplied by the server, that's how the pasv implementation works for clients behind a firewall. The client requests a passive data connection, the server responds with the IP address and port to use for the data connection and the client connects to it. In this case, the ftp code in this article is the client. The PORT command works different and has problems on the client end with NAT/Firewall setups.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm trying to upload a file to a server, and I use openUpload() but I get the message: 553 File name prohibited.
The file I'm trying to upload is c:\valus\bandeja\sf99.zip (No special characters)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
What is the file name you're trying to upload the file as? You can't pass in directory path's, you need to ChangeDir() to the location you wan the file uploaded to.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I see two problems with the code that could be the cause of your problem:
public void OpenUpload(string filename) { OpenUpload(filename, filename, false); }
and
public void OpenUpload(string filename, bool resume) { OpenUpload(filename, filename, resume); }
In both function, replace:
OpenUpload(filename, filename, false); OpenUpload(filename, filename, resume);
With (repsectively):
OpenUpload(filename, new FileInfo(filename).Name, false); OpenUpload(filename, new FileInfo(filename).Name, resume);
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General ![]()
|