host.transfer.base

class otto.host.transfer.base.TransferContext(transfer: str, host_name: str, max_filename_len: int = 255, exec_cmd: collections.abc.Callable[..., Coroutine[Any, Any, CommandStatus]] | None = None, connections: ConnectionManager | None = None, nc_options: NcOptions | None = None, scp_options: ScpOptions | None = None, get_local_ip: collections.abc.Callable[[], str] | None = None, filesystem: EmbeddedFileSystem | None = None)

Bases: object

Construction inputs a host provides to build its transfer backend via BaseFileTransfer.create(). The frozen public seam for custom transfer backends. Carries the union of what any family’s built-ins receive at their call sites; a unix backend reads the unix fields, an embedded backend the embedded ones. Selector validation (host-family applicability) runs before construction, so a backend never sees a ctx missing the fields it needs.

transfer : str
host_name : str
max_filename_len : int = 255
exec_cmd : Callable[..., Coroutine[Any, Any, CommandStatus]] | None = None
connections : ConnectionManager | None = None
nc_options : NcOptions | None = None
scp_options : ScpOptions | None = None
get_local_ip : Callable[[], str] | None = None
filesystem : EmbeddedFileSystem | None = None
otto.host.transfer.base.validate_filename_lengths(files: list[Path], limit: int, host_name: str) tuple[Status, str]

Reject files whose basename exceeds the host’s filesystem cap.

Shared by UnixFileTransfer (Unix) and EmbeddedFileTransfer (embedded) so every backend surfaces the same self-explaining error. Without this guard the failure modes are:

  • Unix SCP/SFTP/FTP: server returns File name too long (errno 36), mid-transfer, after the local file is already read.

  • Embedded FAT (8.3, no LFN) or LittleFS over NAME_MAX: device fails fs_open with -ENOENT, giving no hint that the name was the problem.

Returns (Status.Success, '') when every basename fits.

class otto.host.transfer.base.BaseFileTransfer(name: str, max_filename_len: int = 255)

Bases: ABC

Shared API + progress plumbing for any file-transfer backend.

The public put_files / get_files surface (filename-length validation, shared Rich progress acquisition) is owned by this base. Concrete backends (Unix’s UnixFileTransfer subclasses (ScpFileTransfer, SftpFileTransfer, FtpFileTransfer, NcFileTransfer), embedded’s EmbeddedFileTransfer subclasses (ConsoleFileTransfer, TftpFileTransfer), and any future ones) implement two abstract methods — _run_put and _run_get — both of which receive a TransferProgressFactory and are responsible for invoking it at least once per source file, terminating with bytes_done == bytes_total to mark completion.

The progress-bar capability is enforced at the type system level: abc.abstractmethod refuses to instantiate a subclass that omits either method, so a new backend cannot be defined without supplying a way to report progress. The runtime contract test (TestTransferProgressContract) verifies the factory is actually invoked, not just that the methods exist.

host_families : frozenset[str] = frozenset({})

Host-family selectors this backend serves — a subset of {'unix', 'embedded'}. Subclasses declare it; the spec field_validator rejects a backend on a host of the wrong family. A backend with an empty set can never validate and is rejected at registration.

classmethod create(ctx: TransferContext) BaseFileTransfer

Build a transfer backend from a TransferContext.

The uniform construction seam (WS#4). Concrete backends override this to run their exact construction against the ctx fields they need. Not an abstractmethod deliberately: only registered built-ins are ever constructed through create, and test doubles that subclass BaseFileTransfer only to exercise the progress contract must not be forced to implement it.

async put_files(src_files: list[Path], dest_dir: Path, show_progress: bool = True) tuple[Status, str]
async get_files(src_files: list[Path], dest_dir: Path, show_progress: bool = True) tuple[Status, str]
otto.host.transfer.base.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.base.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’]