Ftp with Qt

As a better example of usage for QCoreApplication, we can see how to use QFtp, the Qt class that provides an implementation for an FTP client.

I wrote this post after reading the part relative to the same argument in chapter 15 of C++ GUI Programming with Qt4, 2nd Edition by Jasmin Blanchette and Mark Summerfield - Prentice Hall. And I suggest you to read it too for more details on the matter.

Since our project is going to use network features, we should ensure that our .pro file contains a line like this:
QT       += core network
Then, we create a class for managing the FTP access:
#ifndef FTPGETTER_H
#define FTPGETTER_H

#include <QObject>
#include <QFtp>
#include <QFile>
#include <QUrl>

class FtpGetter : public QObject
{
Q_OBJECT
public:
explicit FtpGetter(QObject *parent = 0);

bool getFile(const QUrl& url); // 1.
signals:
void done(); // 2.
private slots:
void ftpDone(bool); // 3.
private:
QFtp ftp;
QFile file;
};
#endif // FTPGETTER_H

1. We'll call getFile() to connect and retrieve a file from a FTP server.
2. This signal would be connected to the job to be done when the FtpGetter job is completed.
3. We'll make this slot be called when the QFtp object signals that its job is done.

In the relative .cpp file we are going to implements the FtpGetter methods in this way:
FtpGetter::FtpGetter(QObject *parent) : QObject(parent)
{
connect(&ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));
}

The constructor connects the signal done() for the ftp object owned by the class to the ftpDone() slot of this instance.

Large part of the job is done in getFile():
bool FtpGetter::getFile(const QUrl &url)
{
if (!url.isValid())
{
std::cerr << "Error: Invalid URL." << std::endl;
return false;
}

if(url.scheme() != "ftp")
{
std::cerr << "Error: not an ftp address." << std::endl;
return false;
}

if(url.path().isEmpty())
{
std::cerr << "Error: URL has no path." << std::endl;
return false;
}

QString localFileName = QFileInfo(url.path()).fileName();
if(localFileName.isEmpty())
localFileName = "file.out";

file.setFileName(localFileName);
if(!file.open(QIODevice::WriteOnly))
{
std::cerr << "Error: Cannot write file "
<< qPrintable(file.fileName()) << ": "
<< qPrintable(file.errorString()) << std::endl;
return false;
}

ftp.connectToHost(url.host(), url.port(21));
ftp.login();
ftp.get(url.path(), &file);
ftp.close();

return true;
}

After checking that the user passed a good URL, we create a local file name (if possible, we keep the same name of the original file), we open it in write only mode, then we ask our FTP object to perform a connection, login, getting the file, and then close the connection. Notice that all these operation are done asynchronously, so we don't actually know if they succeeded or not at the end of this function call. We return sort of "tentatively" success, waiting for more feedback from FTP.

As we have seen in the ctor, ftpDone() is called in connection to ftp::done(), so it is here that we check if actually the FTP operation has succeeded or not:
void FtpGetter::ftpDone(bool error)
{
if(error)
std::cerr << "Error: " << qPrintable(ftp.errorString()) << std::endl;
else
std::cerr << "File downloaded to " << qPrintable(file.fileName()) << std::endl;
file.close();
emit done();
}

After writing some feedback, this method emits the done() signal.

The main function of our application would take care of instantiating a FptGetter object, calling it with a suitable URL (here I ask for a file on the trolltech server containing the list of mirrors), and connecting the FtpGetter::done() signal to the QCoreApplication::quit() slot for our current application object. That would mean that we quit the application when the FTP transfer has been completed:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

FtpGetter getter;
QUrl url("ftp://ftp.trolltech.com/mirrors");
std::cout << "Getting file " << qPrintable(url.path())
<< " from " << qPrintable(url.authority());
if(!getter.getFile(url))
{
std::cout << " failed." << std::endl;
return 1;
}

QObject::connect(&getter, SIGNAL(done()), &a, SLOT(quit()));
return a.exec();
}

Notice that in case FtpGetter::getFile() returns failure we don't even care of entering the main loop in the core application object, and just terminating the application instead.

No comments:

Post a Comment