Search code examples
c#.netftprenamefile-move

Move a file to another Ftp directory and rename it


I've been struggling with that for the past few hours and I literally can't find a way to do it without downloading and uploading the file again. Is it even possible?

This question is kind of a copy of this one: How can I use FTP to move files between directories? but I have the feeling that its not solved, although it's answered.

Renaming the file itself is quite easy and works without any problems, but how do I move it to another directory?

I have this sample code:

string uri2 = "ftp://ftpUser@testFtp.com/mainFolder/moveFrom/file.txt";

f = (FtpWebRequest)FtpWebRequest.Create(new Uri(uri2));

f.Credentials = new NetworkCredential(ftpUser, ftpPass);
f.KeepAlive = false;
f.UsePassive = true;
f.Method = WebRequestMethods.Ftp.Rename;
f.Timeout = 5000;
f.UseBinary = true;

f.RenameTo = "ftp://ftpUser@testFtp.com/mainFolder/moveTo/file.txt";

FtpWebResponse response = (FtpWebResponse)f.GetResponse();

response.Close();
f.Abort();

I get the same error as in the other topic:

The remote server returned an error: (550) File unavailable (e.g., file not found, no access).

Using a relative path, doesn't do anything different.

Do I do something wrong or is the only way to download from the source folder, upload to dest and then delete the file from the source? That's 3 calls to the FTP server..


Solution

  • .NET puts an abstraction layer over the FTP protocol which can abstract too much away and thus makes some things impossible or tricky. I did not have a source code of .NET, but based on the source of FTPWebRequest from Mono (an implementation which tries to be compatible with .NET) it will do for any operations on URIs:

    • Split first the URI into a directory part and a file_name part (see CWDAndSetFileName in source).
    • CWD to the directory.
    • And do the operation there.

    With renaming URL=ftp:/host/foo/bar/file1 to RenameTo=file2 this means in FTP commands:

    ... log into host ...
    CWD foo/bar
    RNFR file1
    RNTO file2
    

    But, if you have source and target in different directories, e.g. RenameTo=/foo/bar2/file2, you would rather have it like this:

    ... log into host ...
    RNFR foo/bar/file1
    RNTO foo/bar2/file2
    

    But unfortunately that is not the way it is implemented :(

    If you are lucky you could try to use a relative path, e.g. RenameTo=../bar2/file2, which should result in

    ... log into host ...
    CWD foo/bar
    RNFR file1
    RNTO ../bar2/file2
    

    This should probably work with the Mono-Implementation, but I don't know if it also works with real .NET.

    Edit: I just had a look at the source for .NET 4.5. It differs from Mono in that it does not do a CWD to the basedir before the file operation, but instead prepends the current basedir to the name if the name is relative. Thus with renameTo=../bar2/file2 this results in

     RNFR foo/bar/file1
     RNTO foo/bar/../bar2/file2
    

    while with an absolute name renameTo=/bar2/file2 this would result in

     RNFR foo/bar/file1
     RNTO /bar2/file2
    

    Depending on type and setup of the FTP server the absolute name might get interpreted as relative to the root of the users home directory, or it might be a real absolute path. So it would be best to work only with relative path and the solution with ../dir/ should work with Mono and "real" .NET.