0

I am trying to download multiple files from a ftp site using a for-loop. The following code seems to work only for the first 2 files in the loop before the python.exe shutdown window pops up. Two downloaded files are perfect, but the 3rd downloaded file is empty upon the shutdown. I don't get the rest of files. Any idea what could be the problem?

from PyQt4 import QtCore, QtGui, QtNetwork


class FtpWindow(QtGui.QDialog):

    def __init__(self, parent=None):
        self.fileList = QtGui.QTreeWidget()
        self.ftp = QtNetwork.QFtp(self)
        self.progressDialog = QtGui.QProgressDialog(self)
        self.downloadAllButton.clicked.connect(self.downloadAllFile)
        self.ftp.commandFinished.connect(self.ftpCommandFinished)

    def downloadAllFile(self):
        for jj in range(self.fileList.topLevelItemCount()): # how many files in a particular folder
            fileName = self.fileList.topLevelItem(jj).text(0) 
            self.outFile = QtCore.QFile(fileName)

            self.ftp.get(fileName, self.outFile) #download one file at a time
            self.progressDialog.setLabelText("Downloading %s..." % fileName)    
            self.progressDialog.exec_()

    def ftpCommandFinished(self, _, error):
        self.setCursor(QtCore.Qt.ArrowCursor)
        if self.ftp.currentCommand() == QtNetwork.QFtp.Get:
            if error:
                self.statusLabel.setText("Canceled download of %s." % self.outFile.fileName())
                self.outFile.close()
                self.outFile.remove()
            else:
                self.statusLabel.setText("Downloaded %s to current directory." % self.outFile.fileName())
                self.outFile.close()

            self.outFile = None
            self.enableDownloadButton()
            self.progressDialog.hide()
Curiosity
  • 31
  • 4
  • I would need more code to tell. `self.progressDialog.exec_()` should be a blocking modal dialog. It looks like the ftp get is non blocking so you have to wait until downloading is finished using the commandFinished() signal. Overwritting the outFile variable may be causing the issue. http://pyside.github.io/docs/pyside/PySide/QtNetwork/QFtp.html#PySide.QtNetwork.PySide.QtNetwork.QFtp.get – justengel Jan 02 '17 at 22:00
  • @HashSplat, I use: self.progressDialog = QtGui.QProgressDialog(self). – Curiosity Jan 02 '17 at 22:34
  • @HashSplat Basically, I tried to create a single click to download all files base on [this version](http://stackoverflow.com/questions/1995046/creating-an-ftp-client-with-python) – Curiosity Jan 02 '17 at 22:42

2 Answers2

0

self.progressDialog.exec_() should be a blocking modal dialog. Use self.progressDialog.show() for a non blocking call.

It looks like the ftp get is non blocking so you have to wait until downloading is finished using the commandFinished() signal.

My guess is that every iteration in the loop is overwriting the self.outFile, so there isn't any python references to the object. This makes the object die whenever python does garbage collection. My guess is that your first two files where small and quick and your third file was larger, so the other files were able to download before garbage collection. Either that or garbage collection was just quicker for the last file.

http://pyside.github.io/docs/pyside/PySide/QtNetwork/QFtp.html#PySide.QtNetwork.PySide.QtNetwork.QFtp.get

class FtpWindow(QtGui.QDialog):

    def __init__(self, parent=None):
        self.fileList = QtGui.QTreeWidget()
        self.ftp = QtNetwork.QFtp(self)
        self.progressDialog = QtGui.QProgressDialog(self)
        self.progressDialog.canceled.connect(self.ftp.abort)
        self.downloadAllButton.clicked.connect(self.downloadAllFile)
        self.ref_holder = {}
        self.ftp.commandFinished.connect(self.ftpCommandFinished)

    def download_file(self, filename):
        """Non blocking start downloading a file."""
        outFile = QtCore.QFile(filename)
        cmd_id = self.ftp.get(filename, outFile) # Non blocking just start downloading

        # This keeps the object alive and doesn't overwrite them.
        self.ref_holder[cmd_id] = [filename, outFile]

    def downloadAllFile(self):
        self.progressDialog.reset()
        num_downloads = self.fileList.topLevelItemCount()
        self.progressDialog.setMaximum(num_downloads)
        self.progressDialog.setValue(0)
        self.progressDialog.setLabelText("Downloading %d files ..." % num_downloads)
        self.progressDialog.show()
        for jj in range(num_downloads): # how many files in a particular folder
            fileName = self.fileList.topLevelItem(jj).text(0) 
            self.download_file(fileName) # Non blocking, and doesn't overwrite self.outFile with every iteration

    def ftpCommandFinished(self, cmd_id, error=None):
        """Increased the number of items finished."""
        self.progressDialog.setValue(self.progressDialog.value()+1)
        item = self.ref_holder.pop(cmd_id) # Remove the reference for the finished item
        if error:
            self.progressDialog.setLabelText("Error downloading %s" % item[0])

        # Check if all downloads are done
        if len(self.ref_holder) == 0:
            self.progressDialog.setValue(self.progressDialog.maximium())
            self.progressDialog.close() # This shouldn't be needed

My example above will hold the filename and outFile object reference until the command is finished. When the command is finished the reference is removed allowing python to clean up the object.

justengel
  • 6,132
  • 4
  • 26
  • 42
  • thanks a lot for your code, it works !! However, I found the files don't show up in my local destination even the download is processed. I have to add "self.outFile.open(QtCore.QIODevice.WriteOnly)" to enable the download and saving locally. Why is it? I have included my version below – Curiosity Jan 03 '17 at 22:39
0

Thanks for HashSplat's input. I have a few updates to make it fully functional:

class FtpWindow(QtGui.QDialog):

def __init__(self, parent=None):
    self.fileList = QtGui.QTreeWidget()
    self.ftp = QtNetwork.QFtp(self)
    self.progressDialog = QtGui.QProgressDialog(self)
    self.progressDialog.canceled.connect(self.ftp.abort)
    self.downloadAllButton.clicked.connect(self.downloadAllFile)
    self.ref_holder = {}
    self.ftp.commandFinished.connect(self.ftpCommandFinished)

def download_file(self, fileName):
    """Non blocking start downloading a file."""
    self.outFile = QtCore.QFile(fileName)

    """ Need this to have files saved locally """
    if not self.outFile.open(QtCore.QIODevice.WriteOnly):  
        QtGui.QMessageBox.information(self, "FTP",
                "Unable to save the file %s." % fileName)
        self.outFile = None
        return
    cmd_id = self.ftp.get(filename, self.outFile) # Non blocking just start downloading

    # This keeps the object alive and doesn't overwrite them.
    self.ref_holder[cmd_id] = [filename, self.outFile]

def downloadAllFile(self):
    self.progressDialog.reset()
    self.num_downloads = self.fileList.topLevelItemCount()
    self.counter=1
    self.progressDialog.setLabelText("Downloading %d/%d files ..." % (self.counter, self.num_downloads))
    self.progressDialog.show()
    for jj in range(num_downloads): # how many files in a particular folder
        fileName = self.fileList.topLevelItem(jj).text(0) 
        self.download_file(fileName) # Non blocking, and doesn't overwrite self.outFile with every iteration

def ftpCommandFinished(self, cmd_id, error=None):
    """Increased the number of items finished."""
    self.progressDialog.setValue(self.progressDialog.value()+1)
    item = self.ref_holder.pop(cmd_id) # Remove the reference for the finished item
    if error:
        self.progressDialog.setLabelText("Error downloading %s" % item[0])

    # Check if all downloads are done
    if len(self.ref_holder) == 0:
        self.progressDialog.close() # This closes the extra window
        self.outFile.close()   # You need this to have the last file saved
    else: 
        self.counter+=1
        self.progressDialog.setLabelText("Downloading %d/%d files ..." % (self.counter, self.num_downloads))

def updateDataTransferProgress(self, readBytes, totalBytes):
    self.progressDialog.setMaximum(totalBytes)
    self.progressDialog.setValue(readBytes)
Curiosity
  • 31
  • 4
  • In ftpCommandFinished where `self.outFile.close() # You need this to have the last file saved`. You should probably move this before the if statement and close every outFile. `item[1].close() # Close the outFile` `if len(self.ref_holder) == 0: ...` – justengel Jan 04 '17 at 04:10