"""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