Description
FTP OS: Windows Server
FTP Server: Windows NT
Computer OS: Windows 11 Pro
FluentFTP Version: 37.0.2 on .NET 6
Problem
When using DownloadDirectoryAsync
files are always downloaded with their full file names.
var remoteHostUrl = "ftp://ftp.example.com/";
var credentials = new System.Net.NetworkCredential("ftpuser", "mySecurePw");
using (var client = new FtpClient(remoteHostUrl, credentials))
{
var localFolder = @"C:\Local_Folder";
var remoteFolder = "Remote_Folder/Sub_Folder";
await client.ConnectAsync().ConfigureAwait(false);
var result = await client.DownloadDirectoryAsync(
localFolder,
remoteFolder,
mode: FtpFolderSyncMode.Update,
progress: progress
).ConfigureAwait(false);
await client.DisconnectAsync().ConfigureAwait(false);
}
The used FTP-Server got the username as root directory, in this example /ftpuser
, not sure if that is anything usual.
The remote file /ftpuser/Remote_Folder/Sub_Folder/Sub_Folder_2/example.json
will be downloaded to the local file C:\Local_Folder\ftpuser\Remote_Folder\Sub_Folder\Sub_Folder_2\example.json
.
I expected the file to be downloaded to the local file C:\Local_Folder\Sub_Folder_2\example.json
.
Because i want the download to be relative, i tried changing the working directory, which did not change the behavior.
await client.SetWorkingDirectoryAsync("ftpuser/Remote_Folder/Sub_Folder").ConfigureAwait(false);
Because preceding did not work as I expected, I tried passing var remoteFolder = "ftpuser/Remote_Folder/Sub_Folder"
to the DownloadDirectoryAsync
method, which ran into an exception.
Problem Location
Reviewing the FluentFTP code for a solution, I found the method FtpClient_FolderDownload.cs - GetFilesToDownload on line 175 - code which calculates pathes on lines 182 and 183.
- RemovePrefix(remoteFolder) only works if the user passed remoteFolder with a preceding "/".
- RemovePrefix(remoteFolder) never applies if the remoteFile.FullName contains the user-root-directory "ftpuser".
- Because passing the remoteFolder "ftpuser/Remote_Folder/Sub_Folder" to the Download always resolves to an exception, there is no chance that the code will be hit and correctly calculate the relative path.
Possible Solutions
I reproduced the code on my machine and changed the mentioned lines as following:
// calculate the local path
var relativePath = remoteFile.FullName
.EnsurePrefix("/").RemovePrefix(Credentials.UserName.EnsurePrefix("/")) //Ensure that the UserName is prefixed and remove the UserName as prefix from the file full name.
.EnsurePrefix("/").RemovePrefix(remoteFolder.EnsurePrefix("/")) //Ensure that the remoteFolder is prefixed.
.Replace('/', Path.DirectorySeparatorChar);
var localFile = localFolder.CombineLocalPath(relativePath);
Would be nice if the solution for the UserName will also be picked up, but I am not sure if that is an appropriate solution as i do not know if that is a usual or an unusual case.
Suggestions
To approache this problem, I thought it would be a nice idea to inherit FtpClient.
Sadly, a lot of useful code is private and not even protected, so i had to search the FluentFTP codebase a while to find everything i need and copy it to my inherited FtpClient.
Maybe some methods could be made public to make more options available.
For example:
List<FtpResult> GetFilesToDownload(bunch of parameters){...}
bool FilePassesRules(FtpResult result, List<FtpRule>? rule, more parameters){...}
bool IsBlank(IEnumerable? value)
- Some public API that can pick up
List<FtpResult>
as parameter for a download.