Skip to content

DownloadDirectoryAsync not downloading relativly but always with full file name #852

Closed as not planned
@JanKotschenreuther

Description

@JanKotschenreuther

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.

  1. RemovePrefix(remoteFolder) only works if the user passed remoteFolder with a preceding "/".
  2. RemovePrefix(remoteFolder) never applies if the remoteFile.FullName contains the user-root-directory "ftpuser".
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions