1

I was having issues downloading a file with ftplib in python. Using the simple way with the following code was not working. The code did not stop.

with open('README', 'wb') as fp:
     ftp.retrbinary('RETR README', fp.write)

So i found this solution:

class ImplicitFTP_TLS(ftplib.FTP_TLS):
    """FTP_TLS subclass that automatically wraps sockets in SSL to support implicit FTPS."""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._sock = None
    @property
    def sock(self):
        """Return the socket."""
        return self._sock
    @sock.setter
    def sock(self, value):
        """When modifying the socket, ensure that it is ssl wrapped."""
        if value is not None and not isinstance(value, ssl.SSLSocket):
            value = self.context.wrap_socket(value)
        self._sock = value


def setInterval(interval, times = -1):
    # This will be the actual decorator,
    # with fixed interval and times parameter
    def outer_wrap(function):
        # This will be the function to be
        # called
        def wrap(*args, **kwargs):
            stop = threading.Event()

            # This is another function to be executed
            # in a different thread to simulate setInterval
            def inner_wrap():
                i = 0
                while i != times and not stop.isSet():
                    stop.wait(interval)
                    function(*args, **kwargs)
                    i += 1

            t = threading.Timer(0, inner_wrap)
            t.daemon = True
            t.start()
            return stop
        return wrap
    return outer_wrap

class PyFTPclient:
    # https://github.com/keepitsimple/pyFTPclient
    def __init__(self, host, port = 21, login = 'anonymous', passwd = 'anonymous', monitor_interval = 30):
        self.host = host
        self.port = port
        self.login = login
        self.passwd = passwd
        self.monitor_interval = monitor_interval
        self.ptr = None
        self.max_attempts = 15
        self.waiting = True

    def get_files_names(self, dir_score='/'):
        """
        This function returns a list of the files from a directory.

        Parameters
        ----------
        dir_score : str, default root directory
            The directory path.

        Returns
        -------
        list
            List with the files from the directory.
        """
        ftp_client = ImplicitFTP_TLS()
        ftp_client.connect(host=self.host, port=self.port)
        ftp_client.login(user=self.login, passwd=self.passwd)
        ftp_client.prot_p()
        # ftp_client.dir()
        ftp_client.cwd(dir_score) #Muda diretorio
        files = []
        ftp_client.dir(files.append)
        ftp_client.quit()
        return files

    def DownloadFile(self, dst_filename, local_filename = None):
        res = ''
        if local_filename is None:
            local_filename = dst_filename.split('/')[-1]

        with open(local_filename, 'w+b') as f:
            self.ptr = f.tell()

            @setInterval(self.monitor_interval)
            def monitor():
                if not self.waiting:
                    i = f.tell()
                    if self.ptr < i:
                        logging.debug("%d  -  %0.1f Kb/s" % (i, (i-self.ptr)/(1024*self.monitor_interval)))
                        self.ptr = i
                    else:
                        ftp.close()

            def connect():
                ftp.connect(self.host, self.port)
                ftp.login(self.login, self.passwd)
                ftp.prot_p()
                # optimize socket params for download task
                ftp.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
                ftp.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 75)
                ftp.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)
                
            ftp = ImplicitFTP_TLS()
            ftp.set_debuglevel(2)
            ftp.set_pasv(True)

            connect()
            ftp.voidcmd('TYPE I')
            dst_filesize = ftp.size(dst_filename)

            mon = monitor()
            while dst_filesize > f.tell():
                try:
                    connect()
                    self.waiting = False
                    # retrieve file from position where we were disconnected
                    res = ftp.retrbinary('RETR %s' % dst_filename, f.write) if f.tell() == 0 else \
                              ftp.retrbinary('RETR %s' % dst_filename, f.write, rest=f.tell())

                except:
                    self.max_attempts -= 1
                    if self.max_attempts == 0:
                        mon.set()
                        logging.exception('')
                        raise
                    self.waiting = True
                    logging.info('waiting 30 sec...')
                    time.sleep(30)
                    logging.info('reconnect')


            mon.set() #stop monitor
            ftp.close()

            if not res.startswith('226 Transfer complete'):
                logging.error('Downloaded file {0} is not full.'.format(dst_filename))
                # os.remove(local_filename)
                return None
            return 1

obj = PyFTPclient(FTP_HOST, port=FTP_PORT, login=FTP_USER, passwd=FTP_PWD)
obj.DownloadFile(dst_filename=path_file_name_to_download, local_filename=path_local_filename)

Now, i am having the same problem trying upload files. The way to do so i found in internet is:

f = open(fullname, "rb")
ftp.storbinary('STOR ' + name, f)

But is not working for me with the same problem as before. The code does not stop. I tested with small files and the last output it gives are:

*cmd* 'STOR file.csv'
*put* 'STOR file.csv\r\n'
*get* '150 Opening BINARY mode data connection.\n'
*resp* '150 Opening BINARY mode data connection.'

and does not finish the code.

vitormgou
  • 11
  • 1
  • Thanks a lot. This link really helped me. I needed to redefine the storbinary function. With the retrbinary function i did this: res = ftp.retrbinary('RETR %s' % dst_filename, f.write) if f.tell() == 0 else \ ftp.retrbinary('RETR %s' % dst_filename, f.write, rest=f.tell()) Somehow this rest parameter helps to retrieve file from position where we were if we get disconnected. – vitormgou Jun 27 '22 at 20:54

0 Answers0