Navigator / src / navigator / download / ftpserver / mount_server.py
mount_server.py
Raw
"""Mounts the FTP server to the local file system."""

import os
import shutil
from abc import ABC, abstractmethod
from pathlib import Path

__all__ = ["AbstractMountServer", "CDDISMountServer"]


class AbstractMountServer(ABC):
    """An abstract class for mounting an FTP server to the local file system.

    This class provides a blueprint for mounting and unmounting FTP servers to the local directory.

    Attributes:
        _mountDir (Path): The path to the mount directory.
        _mounted (bool): A flag indicating if the FTP server is mounted.
    """

    def __init__(self, mountDir: Path) -> None:
        """Initializes the AbstractMountServer instance.

        Args:
            mountDir (Path): Path to the mount directory.
        """
        # Check if the mount directory is valid
        self._validate_mount_dir(mountDir)

        self._mountDir = mountDir
        self._is_mounted = False

    def _validate_mount_dir(self, mountPoint: Path) -> None:
        """Validates the mount directory.

        Has to be empty if it exists.
        """
        if mountPoint.exists() and not mountPoint.is_dir():
            raise ValueError("Mount point is not a directory")

        if mountPoint.exists() and len(os.listdir(mountPoint)) > 0:
            raise ValueError("Mount point is not empty")

        return

    def _make_mount_point(self) -> None:
        """Creates the mount point directory."""
        if not self._mountDir.exists():
            os.makedirs(self._mountDir)

    def _tear_down_mount_point(self) -> None:
        """Tears down the mount point directory."""
        if self._mountDir.exists() and not self.isMounted:
            os.rmdir(self._mountDir)

    def mount(self) -> None:
        """Mounts the FTP server to the local file system."""
        # If already mounted, return
        if self.isMounted:
            return

        # Make the mount point directory
        self._make_mount_point()

        # Mount the ftp server
        self._mountLogic()

        # Set the mounted flag
        self._is_mounted = True

        return

    def unmount(self) -> None:
        """Unmounts the FTP server from the local file system."""
        # If not mounted, return
        if not self.isMounted:
            return

        # Unmount the ftp server
        self._unmountLogic()
        # Set the mounted flag
        self._is_mounted = False

        # Tear down the mount point directory
        self._tear_down_mount_point()

        return

    @abstractmethod
    def _mountLogic(self) -> None:
        """Mount logic."""
        pass

    @abstractmethod
    def _unmountLogic(self) -> None:
        """Unmount logic."""
        pass

    @property
    def mountDir(self) -> Path:
        """Returns the mount directory."""
        return self._mountDir

    @mountDir.setter
    def mountDir(self, mountDir: Path) -> None:  # noqa: ARG002
        """Sets the mount directory."""
        raise AttributeError(
            "Cannot set mount directory. Use the constructor to set the mount directory."
        )

    @property
    def isMounted(self) -> bool:
        """Returns True if the FTP server is mounted."""
        return self._is_mounted

    @isMounted.setter
    def isMounted(self, mounted: bool) -> None:  # noqa: ARG002
        """Sets the mounted flag."""
        raise AttributeError(
            "Cannot set mounted flag. Use the mount and unmount methods to set the mounted flag."
        )


class CDDISMountServer(AbstractMountServer):
    """A class for mounting the CDDIS FTP server to the local file system.

    This class extends AbstractMountServer to specifically handle CDDIS FTP server mounting
    and unmounting operations.

    Attributes:
        _email (str): Email address used for anonymous login.
    """

    def __init__(self, mountDir: Path, email: str) -> None:
        """Initializes the CDDISMountServer instance.

        Args:
            mountDir (Path): Path to the mount directory.
            email (str): Email address to use for anonymous login.


        Raises:
            Exception: If curlftpfs is not installed.
            ValueError: If the provided email is not valid.
        """
        if not self._check_curftpfs():
            raise Exception("curlftpfs is not installed")

        if "@" not in email:
            raise ValueError("Email is not valid")

        self._email = email
        super().__init__(mountDir)

    def _mountLogic(self) -> None:
        """Mounts the CDDIS FTP server."""
        mntCmd = f"curlftpfs -o ssl -o user='anonymous:{self._email}' gdc.cddis.eosdis.nasa.gov {self.mountDir}"

        if os.system(mntCmd):
            raise Exception("Error mounting the ftp server")

        return

    def _unmountLogic(self) -> None:
        """Unmounts the CDDIS FTP server."""
        umntCmd = f"umount -l {self.mountDir}"

        if os.system(umntCmd):
            print("Error unmounting the ftp server")
            print(
                f"Manually unmount the ftp server using the following command: {umntCmd}"
            )
            raise Exception("Error unmounting the ftp server")

    def _check_curftpfs(self) -> bool:
        """Check if curlftpfs is installed in the system."""
        return shutil.which("curlftpfs") is not None