host.transfer

File transfer utilities for RemoteHost.

Defines the canonical TransferProgressHandler callback type used across all transfer protocols (SCP, SFTP, FTP, netcat). Rich (or any other progress reporting library) lives only in make_rich_progress_handler — changing Rich’s API requires touching nothing else in the transfer stack.

Callback signature mirrors asyncssh’s progress_handler so that SCP and SFTP can forward it directly without any adaptation layer.

otto.host.transfer.make_rich_progress_handler(progress, host_name)

Return a TransferProgressHandler that drives the given Rich Progress bar.

One task is created per source file, detected by a change in src_path. The caller is responsible for the Progress context (entering and exiting it).

Example:

with make_transfer_progress() as progress:
    handler = make_rich_progress_handler(progress, host_name=host.hostname)
    status, err = await host.get(files, dest, progress_handler=handler)
Return type:

Callable[[str, str, int, int], None]

otto.host.transfer.make_rich_progress_factory(progress, host_name)

Return a factory that creates a fresh TransferProgressHandler per file.

Each call to the returned factory produces an independent handler with its own closure state, so concurrent transfers don’t share progress tracking.

Example:

with make_transfer_progress() as progress:
    factory = make_rich_progress_factory(progress, host_name=host.name)
    status, err = await host.put(files, dest)
Return type:

Callable[[], Callable[[str, str, int, int], None]]

otto.host.transfer.make_transfer_progress()

Return a pre-configured Rich Progress suited for file transfers.

Return type:

Progress

otto.host.transfer.NcPortStrategy

Strategy for finding free ports on the remote host for netcat transfers.

Available strategies:

  • 'auto' (default) — try each built-in strategy in order (ss → netstat → python → proc) and cache the first one that succeeds.

  • 'ss' — parse ss -tln output to find unused ports.

  • 'netstat' — parse netstat -tln output (fallback for hosts without ss).

  • 'python' — bind a socket to port 0 via a python/python3 one-liner and let the OS assign a free port.

  • 'proc' — read /proc/net/tcp directly (Linux-only, always available as a last resort).

  • 'custom' — run the shell command specified in nc_port_cmd; the command must print a free port number to stdout.

alias of Literal[‘auto’, ‘ss’, ‘netstat’, ‘python’, ‘proc’, ‘custom’]

otto.host.transfer.NcListenerCheck

Strategy for checking if a remote nc listener is ready.

Available strategies:

  • 'auto' (default) — probe for ss, then netstat, falling back to proc. The first tool found is cached and reused for subsequent checks.

  • 'ss' — check for a LISTEN socket via ss -tln sport = :<port>.

  • 'netstat' — grep netstat -tln output for the port.

  • 'proc' — scan /proc/net/tcp for LISTEN state (0A) on the port (Linux-only, always available as a last resort).

  • 'custom' — run the shell command specified in nc_listener_cmd with a {port} placeholder. Must exit 0 when the port is listening.

alias of Literal[‘auto’, ‘ss’, ‘netstat’, ‘proc’, ‘custom’]

class otto.host.transfer.FileTransfer(connections, name, transfer, nc_options, scp_options, get_local_ip, exec_cmd)

Bases: object

Handles all file-transfer protocols (SCP, SFTP, FTP, netcat) for a RemoteHost.

Receives injectable callables for open_session and oneshot so it can be tested without real connections.

async get_files(srcFiles, destDir, show_progress=True)
Return type:

tuple[Status, str]

async put_files(srcFiles, destDir, show_progress=True)
Return type:

tuple[Status, str]

async prepare()

Resolve port + listener strategies in a single round-trip.

Runs the shared _STRATEGY_PROBE script through _control_run so the port and listener strategies are resolved up front rather than lazily at first-transfer time. Idempotent — a second call with both strategies already cached is a no-op.

Callers use _warmup_for_transfer to run this concurrently with exec-pool warming; direct callers can invoke prepare() alone.

If the probe itself fails (non-zero exit, malformed output), the caches stay unset and the lazy cascades in _find_free_port_auto / _resolve_listener_strategy still kick in as fallbacks.

Return type:

None