Skip to content

fix(sftp): wire asyncssh progress_handler for transfer progress - #47

Closed
Orinks wants to merge 6 commits into
devfrom
fix/sftp-progress-callback
Closed

fix(sftp): wire asyncssh progress_handler for transfer progress#47
Orinks wants to merge 6 commits into
devfrom
fix/sftp-progress-callback

Conversation

@Orinks

@Orinks Orinks commented Mar 2, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #45 — file downloads/uploads stuck at 0% after the asyncssh migration (PR #44).

  • Root cause: SFTPClient.download() and upload() used a manual chunk-read loop via sftp.open() + rf.read(8192), bypassing asyncssh's pipelined I/O and native progress_handler callback. Progress callbacks fired on the asyncio event loop thread but without asyncssh's built-in reporting, the UI never received timely updates.
  • Fix: Switch to sftp.get()/sftp.put() with their progress_handler parameter for pipelined reads/writes and correct progress reporting. The existing wx.PostEvent in TransferManager._notify() is already thread-safe, so progress callbacks from the asyncio thread reach the wx main thread correctly.
  • Fallback: A chunked-read path is retained for in-memory BinaryIO streams (BytesIO) used in unit tests.
  • Tests: 6 new tests covering native get/put with progress, no-callback path, BytesIO fallback, and upload size verification. All 352 tests pass.

Test plan

  • ruff check and ruff format pass with no issues
  • All 352 existing + new tests pass (pytest tests/ -v)
  • New TestSFTPClientNativeTransfer class validates:
    • sftp.get() called with progress_handler for downloads with file paths
    • sftp.put() called with progress_handler for uploads with file paths
    • Progress callback receives (bytes_copied, total_bytes) tuples
    • BytesIO objects fall back to chunked read/write (no sftp.get/put)
    • Upload size verification still catches mismatches

🤖 Generated with Claude Code

Orinks and others added 6 commits March 2, 2026 06:49
Adds native PPK v2 support for SFTP site authentication, matching WinSCP
behaviour. Users can now select a .ppk file directly when configuring a
site — no manual conversion required.

Implementation:
- New ppk_utils.py: is_ppk_file() detects .ppk extension; load_ppk_key()
  converts PPK → OpenSSH in-memory via puttykeys + paramiko, nothing
  written to disk
- protocols.py: when key_path ends in .ppk, route through load_ppk_key()
  and pass pkey= to paramiko instead of key_filename=
- Passphrase-protected PPK files use the existing password field
- puttykeys added as a dependency (uses cryptography, already installed)

Supports RSA and Ed25519 key types (PPK v2). PPK v3 users can convert
with PuTTYgen.

Closes #43
Replace paramiko with asyncssh for all SFTP operations. asyncssh handles
PPK v2/v3 keys natively (no puttykeys conversion needed) and provides
non-blocking IO via a dedicated event loop on a background thread,
eliminating the UI freeze workarounds (daemon threads, _sftp_lock,
_listdir_attr_safe) that were needed with paramiko.

Changes:
- Rewrite SFTPClient in protocols.py to use asyncssh with a dedicated
  asyncio event loop on a daemon thread
- Remove ppk_utils.py (asyncssh handles PPK natively)
- Remove puttykeys dependency, add asyncssh>=2.14
- Simplify host_key_policy.py and ssh_utils.py (remove paramiko code)
- Update all tests to use asyncssh mocks
- Update PyInstaller spec for asyncssh

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After the asyncssh migration, SFTP downloads/uploads showed 0% progress
because the manual chunk-read loop bypassed asyncssh's pipelined I/O and
native progress reporting.

Switch SFTPClient.download() and upload() to use sftp.get()/sftp.put()
with their progress_handler callback. This gives pipelined reads/writes
for better throughput and correct progress updates via the existing
wx.PostEvent thread-safe notification path.

A chunked-read fallback is retained for in-memory BinaryIO streams
(BytesIO) used in tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Orinks Orinks closed this Mar 2, 2026
@Orinks
Orinks deleted the fix/sftp-progress-callback branch March 2, 2026 16:38
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