Skip to content

[VFS] Optimize FTP directory existence to use CWD instead of parent LIST#759

Open
ilang wants to merge 2 commits intoapache:masterfrom
new-proimage:VFS-cwd-optimization
Open

[VFS] Optimize FTP directory existence to use CWD instead of parent LIST#759
ilang wants to merge 2 commits intoapache:masterfrom
new-proimage:VFS-cwd-optimization

Conversation

@ilang
Copy link
Copy Markdown

@ilang ilang commented Mar 31, 2026

Summary

  • setFTPFile() now tries CWD before falling back to parent LIST for directory existence checks
  • CWD is a lightweight control-channel command (no data transfer) vs LIST which opens a data channel and transfers the entire directory listing
  • When the path is a directory, the expensive parent LIST is skipped entirely
  • When the path is a file or non-existent, falls back to the original parent LIST behavior
  • Timestamps on CWD-resolved directories are lazily fetched via parent LIST when first requested
  • Adds FtpClient.isDirectory() with PWD save/restore to avoid changing the working directory

Symlink note

CWD follows symlinks transparently, so a symlink-to-directory is classified as a plain directory via the CWD fast path. This is acceptable because FTP symlink behavior in VFS is provider-internal — doGetType() resolves symlinks to their target type, and isSymbolicLink() always returns false for FTP (the FTP provider does not override doIsSymbolicLink()). The embedded MINA FTP server also cannot report symlinks in LIST output (it uses the old java.io.File API which follows symlinks transparently).

Depends on

This PR depends on #757 (root exists on disconnect fix). Please merge #757 first.

Test plan

  • New FtpCwdOptimizationTest with 5 tests:
    • Nested directory detected via CWD optimization
    • File detected via LIST fallback
    • Non-existent path returns IMAGINARY
    • Working directory preserved after mixed CWD/LIST operations
    • Directory timestamp lazily fetched via parent LIST (no NPE)
  • All existing FTP tests pass

@ilang ilang marked this pull request as draft March 31, 2026 22:35
ilang added 2 commits April 1, 2026 09:33
…s after connection drop

setFTPFile() blindly assumed that root-level directories (where
getParent() returns null) always exist by creating a synthetic FTPFile
with DIRECTORY_TYPE. This caused exists() to return true even after the
FTP connection was lost.

Fix: add verifyRootDirectory() which uses CWD "." to verify the
directory exists on the server. CWD "." checks the current directory
(the logical VFS root) which is correct for both configurations:
- userDirIsRoot=true: current dir is the user's login directory
- userDirIsRoot=false: current dir is "/" (set by the factory CWD)

Using "." rather than "/" avoids a semantic mismatch on non-chroot
servers with userDirIsRoot=true, where "/" would go to the actual
server root instead of the user's login directory.

Add FtpClient.changeDirectory() default method and FTPClientWrapper
implementation to support CWD from FtpFileObject.
For directory existence checks, CWD is a lightweight control-channel
command with no data transfer, while LIST opens a data channel and
transfers the entire directory contents. When CWD succeeds (path is a
directory), the expensive parent LIST is skipped entirely. When CWD
fails (path is a file, symlink, or non-existent), falls back to the
original parent LIST behavior.

Add FtpClient.isDirectory() with PWD save/restore in FTPClientWrapper
to avoid changing the working directory when probing non-root paths.

Rename verifyRootDirectory() to verifyDirectory() as it now serves
both root and non-root paths.

Fix NPE in getTimestampMillis() for CWD-resolved directories: the
synthetic FTPFile has no timestamp, so lazily fetch the real FTPFile
via parent LIST when timestamp is first requested.
@ilang ilang force-pushed the VFS-cwd-optimization branch from 0ec1a6e to c0cec45 Compare April 1, 2026 08:26
@ilang ilang marked this pull request as ready for review April 1, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant