Module e2eeftp.client.cli

Command-line interface for E2EEFTP client.

This module provides an interactive command-line interface for the E2EEFTP client, allowing users to perform secure file transfers through a rich text-based UI. It includes commands for connecting to servers, uploading/downloading files, listing remote files, and managing connections.

The CLI uses Rich for enhanced terminal output and provides a user-friendly interface for interacting with E2EEFTP servers.

Functions

def check_host_status(host: str = '127.0.0.1', port: int | None = None) ‑> bool
Expand source code
def check_host_status(host: str = '127.0.0.1', port: None | int = None) -> bool:
    """
    Checks if a host is up. If a port is provided, it attempts a 
    TCP connection. Otherwise, it defaults to a standard ping.

    Args:
        host (str): the host ip.
        port (None | int): the port of the host.

    Returns:
        bool if the server is up or not.
    """
    if port:
        try:
            # Create a TCP socket and attempt to connect
            with socket.create_connection((host, int(port)), timeout=2):
                return True
        except (socket.timeout, ConnectionRefusedError, OSError):
            return False
    else:
        # Fallback to the original ping logic if no port is provided
        param = '-n' if platform.system().lower() == 'windows' else '-c'
        command = ['ping', param, '1', host]
        try:
            subprocess.run(command, capture_output=True, text=True, check=True, timeout=2)
            return True
        except Exception:
            return False

Checks if a host is up. If a port is provided, it attempts a TCP connection. Otherwise, it defaults to a standard ping.

Args

host : str
the host ip.
port : None | int
the port of the host.

Returns

bool if the server is up or not.

Classes

class e2eeftpClientCli (host: str = '127.0.0.1', port: int = 5001)
Expand source code
class e2eeftpClientCli:
    """
    The CLI for the e2eeftp client for a user frendly TUI experience.
    """
    def __init__(self, host: str='127.0.0.1', port: int=5001):
        """
        Initialize the E2EEFTP client CLI with server connection details.

        Args:
            host (str): The hostname or IP address of the E2EEFTP server.
            port (int): The port number on which the server is listening.

        This method sets up the client connection, console for rich output,
        status code mappings for user-friendly messages, and initializes
        the help system and command interface.
        """
        # Initialize client - ensure host is accessible via self.client.host
        self.client = e2eeftpClient(host=host, port=port, logging=False)
        self.console = Console()

        self.status_map = {
            "200": "[bold green]200: Success[/bold green]",
            "226": "[bold cyan]226: Transfer Complete[/bold cyan]",
            "400": "[bold yellow]400: Bad Request[/bold yellow]",
            "404": "[bold red]404: File Not Found[/bold red]",
            "500": "[bold red]500: Internal Server Error[/bold red]"
        }
        self._create_help_table()
        self.commands = self.client

    def _create_help_table(self):
        """
        Creates the persistent help table.
        """
        self.help_table = Table(title="Available Commands", header_style="bold magenta")
        self.help_table.add_column("Command", style="cyan", width=15)
        self.help_table.add_column("Description")
        self.help_table.add_column("Syntax", style="italic")
        
        self.help_table.add_row("SEND", "Upload a file to server", "SEND <local_path>")
        self.help_table.add_row("GET", "Download from server", "GET <remote_path>")
        self.help_table.add_row("LIST", "List server files", "LIST")
        self.help_table.add_row("DELETE", "Delete server file", "DELETE <remote_path>")
        self.help_table.add_row("HLIST", "List server commands", "HLIST")
        self.help_table.add_row("PING", "Check server status", "PING")
        self.help_table.add_row("HELP", "Show this message", "HELP")
        self.help_table.add_row("EXIT", "Close the client", "EXIT")

    def _get_status_style(self, code):
        """
        Get the styled status message for a given status code.

        Args:
            code: The HTTP-like status code returned by server operations.

        Returns:
            str: A Rich-formatted string with appropriate colors and styling
                 for the status code, or a generic unknown status message.
        """
        return self.status_map.get(str(code), f"[white]{code}: Unknown Status[/white]")

    def run(self):
        """
        Start the interactive CLI session for the E2EEFTP client.

        This method clears the console, displays a welcome panel, and enters
        an interactive loop where it accepts user commands. It handles special
        commands like EXIT and delegates other commands to _evaluate_command
        for processing. The loop continues until the user exits or an interrupt
        occurs.

        The interface provides a clean, Rich-enhanced terminal experience with
        colored output and formatted panels.
        """
        self.console.clear()
        self.console.print(Panel.fit(
            "[bold cyan]E2EE FTP Client[/bold cyan]",
            border_style="magenta"
        ))
        
        try:
            while True:
                # Use Prompt.ask for a clean input experience
                command_input = _Prompt.ask("[bold green]>>> [/bold green]").strip()
                
                if not command_input:
                    continue
                
                if command_input.upper() == "EXIT":
                    rprint("[yellow]Exiting...[/yellow]")
                    break
                    
                self._evaluate_command(command_input)
        except (KeyboardInterrupt, EOFError):
            rprint("\n[red]Session terminated.[/red]")

    def _evaluate_command(self, command: str):
        """
        Parse and execute a user-entered command.

        Args:
            command (str): The raw command string entered by the user.

        This method parses the command, determines the operation type, and
        executes the appropriate action. Supported commands include:
        - SEND: Upload a file to the server
        - GET: Download a file from the server
        - LIST: Display server's file directory
        - DELETE: Remove a file from the server
        - HLIST: Show available server commands
        - HELP: Display the help table
        - PING: Check server connectivity
        - EXIT: Terminate the session (handled in run())

        For file operations, it validates arguments and provides user feedback
        through Rich-formatted console output. Invalid commands are reported
        with appropriate error messages.
        """
        parts = command.split(maxsplit=1)
        method = parts[0].upper()
        args = parts[1] if len(parts) > 1 else None

        match method:
            case "SEND":
                if args:
                    if os.path.exists(args):
                        rprint(f"[blue]Action:[/blue] Uploading {args}...")
                        status = self.client.send(args) 
                        rprint(f"Status: {self._get_status_style(status)}")
                    else:
                        rprint(f"[bold red]Error:[/bold red] Local file '{args}' not found.")
                else:
                    rprint("[red]Error: Provide a file path.[/red]")
            
            case "GET":
                if args:
                    rprint(f"[blue]Action:[/blue] Downloading {args}...")
                    status = self.client.get(args)
                    rprint(f"Status: {self._get_status_style(status)}")
                else:
                    rprint("[red]Error: Provide a file path.[/red]")
            
            case "LIST":
                rprint("[blue]Action:[/blue] Requesting file list...")
                file_list = self.client.list_files()

                if file_list is not None:
                    # Handle empty list or list with a single empty string from split
                    if not file_list or (len(file_list) == 1 and not file_list[0]):
                        rprint("[yellow]Server directory is empty.[/yellow]")
                        return

                    table = Table(title="Server Directory", header_style="bold magenta")
                    table.add_column("Filename", style="white")
                    
                    i = 0
                    for filename in file_list:
                        if filename: # Don't add empty rows
                            table.add_row(filename)
                            i += 1
                    
                    self.console.print(table)
                    rprint("[i][blue]check:[/blue] list.txt[/i]")
                    rprint(f"[i][blue]Total files:[/blue] {i}[/i]")
                else:
                    rprint("[bold red]Error:[/bold red] Could not retrieve directory list. Check logs for details.")
            
            case "DELETE":
                if args:
                    rprint(f"[blue]Action:[/blue] Deleteing {args}...")
                    status = self.client.delete(args)
                    rprint(f"Status: {self._get_status_style(status or 200)}")
                else:
                    rprint("[red]Error: Provide a file path.[/red]")

            case "HLIST":
                rprint("[blue]Action:[/blue] Requesting server commands...")
                cmd_list = self.client.hlist()

                if cmd_list is not None:
                    # Handle empty list or list with a single empty string from split
                    if not cmd_list or (len(cmd_list) == 1 and not cmd_list[0]):
                        rprint("[yellow]Server has no commands available.[/yellow]")
                        return

                    table = Table(title="Available Server Commands", header_style="bold magenta")
                    table.add_column("Command", style="cyan")
                    
                    i = 0
                    for cmd in cmd_list:
                        if cmd:  # Don't add empty rows
                            table.add_row(cmd)
                            i += 1
                    
                    self.console.print(table)
                    rprint(f"[i][blue]Total commands:[/blue] {i}[/i]")
                else:
                    rprint("[bold red]Error:[/bold red] Could not retrieve command list. Check logs for details.")

            case "HELP":
                self.console.print(self.help_table)

            case "PING":
                # Use the host and port from your client instance
                host = getattr(self.client, 'host', 'localhost')
                port = getattr(self.client, 'port', 21) # Default FTP port is 21
                
                rprint(f"[blue]Checking connection to {host}:{port}...[/blue]")
                
                if check_host_status(host, port):
                    rprint(f"Status: [bold green]UP (Port {port} is open)[/bold green]")
                else:
                    rprint(f"Status: [bold red]DOWN (Could not connect to port {port})[/bold red]")
            
            case _:
                rprint(f"Status: {self._get_status_style(400)} (Unknown Command '{method}')")

The CLI for the e2eeftp client for a user frendly TUI experience.

Initialize the E2EEFTP client CLI with server connection details.

Args

host : str
The hostname or IP address of the E2EEFTP server.
port : int
The port number on which the server is listening.

This method sets up the client connection, console for rich output, status code mappings for user-friendly messages, and initializes the help system and command interface.

Methods

def run(self)
Expand source code
def run(self):
    """
    Start the interactive CLI session for the E2EEFTP client.

    This method clears the console, displays a welcome panel, and enters
    an interactive loop where it accepts user commands. It handles special
    commands like EXIT and delegates other commands to _evaluate_command
    for processing. The loop continues until the user exits or an interrupt
    occurs.

    The interface provides a clean, Rich-enhanced terminal experience with
    colored output and formatted panels.
    """
    self.console.clear()
    self.console.print(Panel.fit(
        "[bold cyan]E2EE FTP Client[/bold cyan]",
        border_style="magenta"
    ))
    
    try:
        while True:
            # Use Prompt.ask for a clean input experience
            command_input = _Prompt.ask("[bold green]>>> [/bold green]").strip()
            
            if not command_input:
                continue
            
            if command_input.upper() == "EXIT":
                rprint("[yellow]Exiting...[/yellow]")
                break
                
            self._evaluate_command(command_input)
    except (KeyboardInterrupt, EOFError):
        rprint("\n[red]Session terminated.[/red]")

Start the interactive CLI session for the E2EEFTP client.

This method clears the console, displays a welcome panel, and enters an interactive loop where it accepts user commands. It handles special commands like EXIT and delegates other commands to _evaluate_command for processing. The loop continues until the user exits or an interrupt occurs.

The interface provides a clean, Rich-enhanced terminal experience with colored output and formatted panels.