ftp_download.ensure

 1from .prefs import Conf
 2
 3import re
 4import sys
 5import ftplib
 6from io import StringIO
 7from typing import Dict, List
 8from pathlib import PureWindowsPath
 9
10# IMPORTANT: https://kb.globalscape.com/KnowledgebaseArticle10142.aspx
11
12
13def posix_path(path: str) -> str:
14
15    """
16    Normalizes a path and make compatible usual FTP file systems (unix-like).
17
18    ### Args:
19
20    - **path** (`str`): The path to be normalized;
21
22    ### Returns:
23
24    A `str` with path normalized and in posix format.
25    """ # noqa
26
27    posix_path = PureWindowsPath(path).as_posix()
28    swap_drive_letter_for_root = re.compile("[A-Z]:/", re.IGNORECASE)
29    return swap_drive_letter_for_root.sub("/", posix_path)
30
31
32def describe_dir(
33        ftp: ftplib.FTP,
34        path: str = ''
35        ) -> Dict[str, List[str]]:
36
37    """
38    Describes the remote path showing names of files and folders in the specified directory.
39
40    ### Args:
41
42    - **ftp** (`ftplib.FTP`): A `ftplib.FTP` connected and logged in to the server
43    - **path** (`str`): Path to be described
44
45    ### Returns:
46
47    A `dict` with two keys `"dirs"` and `"files"`. The first stores a `list` of dirnames in the specified directory, and the latter stores a `list` of filenames.
48    """ # noqa
49
50    # Capturing stdout adapted from:
51    # https://stackoverflow.com/questions/5136611/capture-stdout-from-a-script
52
53    curr_stdout = sys.stdout
54    capturer = StringIO()
55    sys.stdout = capturer
56
57    ftp.dir(posix_path(path))
58
59    sys.stdout = curr_stdout
60    # Capture stdout as a list
61    # Last item is always an empty line
62    lines_list = capturer.getvalue().split("\n")[:-1]
63
64    # Will get wrong result if filename or dirname has space " "
65    paths = {}
66    paths["dirs"] = [i.rpartition(" ")[-1] for i in lines_list if i[0] == "d"]
67    paths["files"] = [i.rpartition(" ")[-1] for i in lines_list if i[0] != "d"]
68
69    if len(paths["dirs"]) + len(paths["files"]) == 0:
70        error_msg = "Invalid path provided."
71        if Conf.raise_if_invalid:
72            raise ftplib.error_perm(error_msg)
73        else:
74            print(error_msg)
75
76    return paths
77
78
79def login(ftp: ftplib.FTP) -> None:
80
81    """
82    Logs in to the specified `ftplib.FTP` object's server and skips any login error.
83
84    ### Args:
85
86    - **ftp** (`ftplib.FTP`): A connected `ftplib.FTP` object
87    """ # noqa
88
89    try:
90        ftp.login()
91    except ftplib.error_perm as perm_err:
92        resp = perm_err.__str__()
93
94        if resp[:3] != "530":
95            raise ftplib.error_perm(resp)
def posix_path(path: str) -> str:
14def posix_path(path: str) -> str:
15
16    """
17    Normalizes a path and make compatible usual FTP file systems (unix-like).
18
19    ### Args:
20
21    - **path** (`str`): The path to be normalized;
22
23    ### Returns:
24
25    A `str` with path normalized and in posix format.
26    """ # noqa
27
28    posix_path = PureWindowsPath(path).as_posix()
29    swap_drive_letter_for_root = re.compile("[A-Z]:/", re.IGNORECASE)
30    return swap_drive_letter_for_root.sub("/", posix_path)

Normalizes a path and make compatible usual FTP file systems (unix-like).

Args:

  • path (str): The path to be normalized;

Returns:

A str with path normalized and in posix format.

def describe_dir(ftp: ftplib.FTP, path: str = '') -> Dict[str, List[str]]:
33def describe_dir(
34        ftp: ftplib.FTP,
35        path: str = ''
36        ) -> Dict[str, List[str]]:
37
38    """
39    Describes the remote path showing names of files and folders in the specified directory.
40
41    ### Args:
42
43    - **ftp** (`ftplib.FTP`): A `ftplib.FTP` connected and logged in to the server
44    - **path** (`str`): Path to be described
45
46    ### Returns:
47
48    A `dict` with two keys `"dirs"` and `"files"`. The first stores a `list` of dirnames in the specified directory, and the latter stores a `list` of filenames.
49    """ # noqa
50
51    # Capturing stdout adapted from:
52    # https://stackoverflow.com/questions/5136611/capture-stdout-from-a-script
53
54    curr_stdout = sys.stdout
55    capturer = StringIO()
56    sys.stdout = capturer
57
58    ftp.dir(posix_path(path))
59
60    sys.stdout = curr_stdout
61    # Capture stdout as a list
62    # Last item is always an empty line
63    lines_list = capturer.getvalue().split("\n")[:-1]
64
65    # Will get wrong result if filename or dirname has space " "
66    paths = {}
67    paths["dirs"] = [i.rpartition(" ")[-1] for i in lines_list if i[0] == "d"]
68    paths["files"] = [i.rpartition(" ")[-1] for i in lines_list if i[0] != "d"]
69
70    if len(paths["dirs"]) + len(paths["files"]) == 0:
71        error_msg = "Invalid path provided."
72        if Conf.raise_if_invalid:
73            raise ftplib.error_perm(error_msg)
74        else:
75            print(error_msg)
76
77    return paths

Describes the remote path showing names of files and folders in the specified directory.

Args:

  • ftp (ftplib.FTP): A ftplib.FTP connected and logged in to the server
  • path (str): Path to be described

Returns:

A dict with two keys "dirs" and "files". The first stores a list of dirnames in the specified directory, and the latter stores a list of filenames.

def login(ftp: ftplib.FTP) -> None:
80def login(ftp: ftplib.FTP) -> None:
81
82    """
83    Logs in to the specified `ftplib.FTP` object's server and skips any login error.
84
85    ### Args:
86
87    - **ftp** (`ftplib.FTP`): A connected `ftplib.FTP` object
88    """ # noqa
89
90    try:
91        ftp.login()
92    except ftplib.error_perm as perm_err:
93        resp = perm_err.__str__()
94
95        if resp[:3] != "530":
96            raise ftplib.error_perm(resp)

Logs in to the specified ftplib.FTP object's server and skips any login error.

Args:

  • ftp (ftplib.FTP): A connected ftplib.FTP object