Explorar el Código

0.1.5
1、修改了FTP文件
2、添加了curl库,添加了使用curl下载ftp文件夹的类

Apple hace 7 meses
padre
commit
fd77bf61b5

+ 4 - 0
Libraries/Libraries.cmake

@@ -56,6 +56,7 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/sm_dll)
 list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/qmqtt)
 list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/LHQLog)
 list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/LHHTTPAPI)
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/NetworkCurl)
 
 # if(CMAKE_SIZEOF_VOID_P MATCHES 8)
 #     if(USING_SPDLOG_STATIC_LIB)
@@ -75,6 +76,7 @@ find_package(SM REQUIRED)
 
 find_package(LHQLog REQUIRED)
 find_package(LHHTTPAPI REQUIRED)
+find_package(CURL REQUIRED)
 #打印搜索到的信息
 message(STATUS "--------------------------------------------------")
 message(STATUS "fmt Found: ${fmt_FOUND}")
@@ -98,4 +100,6 @@ message(STATUS "--------------------------------------------------")
 message(STATUS "LHHTTPAPI Found : ${LHHTTPAPI_FOUND}")
 message(STATUS "LHHTTPAPI Version : ${LHHTTPAPI_VERSION}")
 message(STATUS "--------------------------------------------------")
+message(STATUS "CURL Found : ${CURL_FOUND}")
+message(STATUS "--------------------------------------------------")
 

+ 66 - 0
Libraries/NetworkCurl/FindCURL.cmake

@@ -0,0 +1,66 @@
+
+
+#获取CURL的路径
+
+message(STATUS "***** Find CURL *****")
+
+unset(CURL_INCLUDE_DIR CACHE)
+unset(CURL_LIBRARY CACHE)
+
+# 1. 查找头文件
+unset(INCLUDE_DIR CACHE)
+find_path(INCLUDE_DIR
+    NAMES curl.h
+    PATHS ${CMAKE_CURRENT_LIST_DIR}/curl/include
+    NO_DEFAULT_PATH
+)
+list(APPEND CURL_INCLUDE_DIR ${INCLUDE_DIR})
+
+unset(INCLUDE_DIR CACHE)
+find_path(INCLUDE_DIR
+    NAMES CurlFtp.h
+    PATHS ${CMAKE_CURRENT_LIST_DIR}/ftp
+    NO_DEFAULT_PATH
+)
+list(APPEND CURL_INCLUDE_DIR ${INCLUDE_DIR})
+
+
+# 2. 查找源文件
+unset(SOURCE CACHE)
+find_path(SOURCE
+    NAMES CurlFtp.cpp
+    PATHS ${CMAKE_CURRENT_LIST_DIR}/ftp
+    NO_DEFAULT_PATH
+)
+list(APPEND CURL_SOURCE_DIR ${SOURCE})
+
+
+# 3. 查找库文件
+unset(LIBRARY CACHE)
+find_library(CURL_LIBRARY
+    NAMES curl
+    PATHS ${CMAKE_CURRENT_LIST_DIR}/curl/lib
+    NO_DEFAULT_PATH
+)
+list(APPEND CURL_LIBRARY ${LIBRARY})
+
+
+# 4. 输出查找结果
+
+message(STATUS "CURL include : CURL_INCLUDE_DIR")
+message(STATUS "CURL source : CURL_SOURCE_DIR")
+message(STATUS "CURL library : CURL_LIBRARY")
+
+# message(STATUS "CURL include : ${CURL_INCLUDE_DIR}")
+# message(STATUS "CURL source : ${CURL_SOURCE_DIR}")
+# message(STATUS "CURL library : ${CURL_LIBRARY}")
+
+# 5. 设置标志位
+if(CURL_INCLUDE_DIR AND CURL_SOURCE_DIR AND CURL_LIBRARY)
+    set(CURL_FOUND TRUE)
+else()
+    set(CURL_FOUND FALSE)
+endif()
+
+
+message(STATUS "---------------------------------")

+ 0 - 3
Libraries/NetworkCurl/FindLHFILE.cmake

@@ -1,3 +0,0 @@
-
-
-#获取LHFILE的路径

+ 236 - 0
Libraries/NetworkCurl/ftp/CurlFtp.cpp

@@ -0,0 +1,236 @@
+
+#include "CurlFtp.h"
+#include <iostream>
+#include <regex>
+#include <iosfwd>
+
+CurlFtp::CurlFtp()
+{
+
+}
+
+
+CurlFtp::~CurlFtp()
+{
+
+
+}
+
+/* 检查文件夹是否存在,不确定好不好用 */
+// bool CurlFtp::isDirExists(const std::string &ftpUrl, const std::string &username, const std::string &password)
+// {
+//     CURL *curl;
+//     CURLcode res;
+//     bool result = false;
+//     std::string retList;
+
+//     curl_global_init(CURL_GLOBAL_DEFAULT);
+//     curl = curl_easy_init();
+//     if(curl) 
+//     {
+//         curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());
+//         curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
+//         curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
+//         // curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L); // We only want the directory listing
+
+        
+//         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
+//         curl_easy_setopt(curl, CURLOPT_WRITEDATA, &retList);
+
+//         res = curl_easy_perform(curl);
+//         // printf("res = %d\n", res);
+//         if(res != CURLE_OK) 
+//         {
+//             fprintf(stderr, "getDirList() failed, error code %d, :%s\n",res, curl_easy_strerror(res));
+//         }
+//         else 
+//         {
+//             // std::cout << "Directory list: \n" << retList << std::endl;
+//         }
+//         curl_easy_cleanup(curl);
+//     }
+    
+//     curl_global_cleanup();
+    
+//     return result;
+// }
+
+/* 使用正则表达式提取出文件夹 */
+// bool CurlFtp::extractDirectories(const std::string& responseString)
+// {
+//     std::regex directoryRegex(R"(^d.*\s(\S+)$)");
+//     std::smatch match;
+
+//     std::istringstream retStream(responseString);
+//     std::string line;
+
+//     while (std::getline(retStream, line)) 
+//     {
+//         if (std::regex_match(line, match, directoryRegex)) 
+//         {
+//             std::cout << "Directory: " << match[1] << std::endl;
+//         }
+//     }
+//     return true;
+// }
+
+/**
+ * @brief 列出FTP文件夹
+ * 
+ * @param ftpUrl 需要列出文件夹的FTP地址
+ * @param username 
+ * @param password 
+ * @return std::string 
+ */
+std::string CurlFtp::listDir(const std::string &ftpUrl, const std::string &username, const std::string &password)
+{
+    CURL *curl;
+    CURLcode res;
+    bool result = false;
+    std::string retList;
+
+    curl_global_init(CURL_GLOBAL_DEFAULT);
+    curl = curl_easy_init();
+    if(curl) 
+    {
+        curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());
+        curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
+        curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
+        // curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L); // We only want the directory listing
+
+        
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &retList);
+
+        res = curl_easy_perform(curl);
+        // printf("res = %d\n", res);
+        if(res != CURLE_OK) 
+        {
+            fprintf(stderr, "getDirList() failed, error code %d, :%s\n",res, curl_easy_strerror(res));
+        }
+        else 
+        {
+            // std::cout << "Directory list: \n" << retList << std::endl;
+        }
+        curl_easy_cleanup(curl);
+    }
+    
+    curl_global_cleanup();
+    return retList;
+}
+
+/* 写入回调函数,listDir需要调用 */
+size_t CurlFtp::writeCallback(void *contents, size_t size, size_t nmemb, std::string *s) 
+{
+    size_t newLength = size*nmemb;
+    size_t oldLength = s->size();
+    try
+    {
+        s->resize(oldLength + newLength);
+    }
+    catch(std::bad_alloc &e)
+    {
+        //handle memory problem
+        return 0;
+    }
+
+    std::copy((char*)contents,(char*)contents+newLength,s->begin()+oldLength);
+    return size*nmemb;
+}
+
+// Callback function to collect the list of files
+static int filelistCallback(void* buffer, size_t size, size_t nmemb, void* userp)
+{
+    std::vector<std::string>* fileList = static_cast<std::vector<std::string>*>(userp);
+    std::string line(static_cast<char*>(buffer), size * nmemb);
+    fileList->push_back(line);
+    return size * nmemb;
+}
+
+/* 列出文件夹中的所有文件 */
+bool CurlFtp::listFiles(const std::string &ftpUrl, const std::string &username, const std::string &password)
+{
+    CURL *curl;
+    CURLcode res;
+    bool result = false;
+
+    curl_global_init(CURL_GLOBAL_DEFAULT);
+    curl = curl_easy_init();
+    if(curl)
+    {
+        std::vector<std::string> fileList;
+        curl_easy_setopt(curl, CURLOPT_URL, (ftpUrl).c_str());
+        curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
+        curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
+        curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L);
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, filelistCallback);
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fileList);
+
+        res = curl_easy_perform(curl);
+        if(res != CURLE_OK) 
+        {
+            fprintf(stderr, "Failed to get file list, error code :%d ,%s\n", res, curl_easy_strerror(res));
+        }
+        else
+        {
+            for(const std::string& filename : fileList)
+            {
+                printf("%s\n", filename.c_str());
+            }
+            result = true;
+        }
+
+        curl_easy_cleanup(curl);
+    }
+
+    curl_global_cleanup();
+    return result;
+}
+
+
+
+/* 创建文件夹 */
+bool CurlFtp::createDir(const std::string &ftpUrl, const std::string &username, const std::string &password, const std::string &dirName)
+{
+    CURL *curl;
+    CURLcode res;
+    bool result = false;
+
+    curl_global_init(CURL_GLOBAL_DEFAULT);
+    curl = curl_easy_init();
+    if(curl)
+    {
+        curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());
+        curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
+        curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
+
+        // Create a list of FTP commands to be executed on the server
+        struct curl_slist *headerlist = NULL;
+        std::string mkdir = "MKD " + dirName;
+        headerlist = curl_slist_append(headerlist, mkdir.c_str());
+
+        // Set the list of FTP commands to be executed on the server before the transfer
+        curl_easy_setopt(curl, CURLOPT_QUOTE, headerlist);
+        // Tell libcurl to not include the body in the output
+        curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
+
+        res = curl_easy_perform(curl);
+
+        if(res != CURLE_OK) 
+        {
+            fprintf(stderr, "createDir() failed, error code :%d ,%s\n", res, curl_easy_strerror(res));
+            result = false;
+        }else
+        {
+            result = true;
+        }
+
+        curl_easy_cleanup(curl);
+        curl_slist_free_all(headerlist);
+    }
+
+    curl_global_cleanup();
+    return result;
+}
+
+

+ 30 - 0
Libraries/NetworkCurl/ftp/CurlFtp.h

@@ -0,0 +1,30 @@
+#ifndef CURLFTP_H
+#define CURLFTP_H
+
+#include "curl.h"
+#include <string>
+
+class CurlFtp
+{
+public:
+    CurlFtp();
+    ~CurlFtp();
+    /* 列出FTP文件夹 */
+    static std::string listDir(const std::string &ftpUrl, const std::string &username, const std::string &password);
+    /* 列出文件夹中的所有文件 */
+    static bool listFiles(const std::string &ftpUrl, const std::string &username, const std::string &password);
+    /* 检查文件夹是否存在 */
+    // static bool isDirExists(const std::string &ftpUrl, const std::string &username, const std::string &password);
+    /* 使用正则表达式提取出文件夹 */
+    // static bool CurlFtp::extractDirectories(const std::string& responseString);
+    /* 回调函数 */
+    static size_t writeCallback(void *contents, size_t size, size_t nmemb, std::string *s);
+
+    /* 创建文件夹 */
+    static bool createDir(const std::string &ftpUrl, const std::string &username, const std::string &password, const std::string &dirName);
+
+};
+
+
+
+#endif /* CURLFTP_H */

+ 27 - 0
common/ImageBlur/blurutility.cpp

@@ -0,0 +1,27 @@
+#include "blurutility.h"
+#include "imageblur.h"
+#include <QPainter>
+
+//------------------------------------------------------------------------
+//函    数: CreateInnerShadow(QImage &image, const QColor &color, int radius)
+//
+//说    明: 创建内阴影image
+//
+//参    数: 
+//[传入传出]image 传入已分配好内存空间(且具备长宽)的image对象, 传出处理后的image对象
+//[传入]color 阴影颜色
+//[传入]radius 阴影扩散程度
+//
+//返 回 值:是否成功
+//------------------------------------------------------------------------
+bool BlurUtility::CreateInnerShadow(QImage &image, const QColor &color, int radius)
+{
+    if(image.isNull()) return false;
+    QPainter p;
+    p.begin(&image);
+    p.setPen(QPen(color, 2));
+    p.drawRect(QRect(0, 0, image.width(), image.height()));
+    p.end();
+    GaussBlur::Blur(image, radius);
+    return true;
+}

+ 13 - 0
common/ImageBlur/blurutility.h

@@ -0,0 +1,13 @@
+#ifndef BLURUTILITY_H
+#define BLURUTILITY_H
+
+#include <QColor>
+#include <QImage>
+
+class BlurUtility
+{
+public:
+    static bool CreateInnerShadow(QImage &image, const QColor &color = Qt::black, int radius = 8);
+};
+
+#endif // BLURUTILITY_H

+ 99 - 0
common/ImageBlur/imageblur.cpp

@@ -0,0 +1,99 @@
+#include "imageblur.h"
+#include <QtMath>
+#include <QDebug>
+
+//边缘算法
+//i: 偏移
+//x: 当前位置
+//w: 范围(位置范围是0 -> w)
+//返回: x周边第i个的位置
+template <typename T>
+T ImageBlurUtility::Edge(T i, T x, T w)
+{
+    //x周边第i个的位置
+    T i_k = x + i;
+    if      (i_k < 0)  i_k = -x;
+    else if (i_k >= w) i_k = w - 1 - x;
+    else               i_k = i;
+    return i_k;
+}
+//归一化
+void ImageBlurUtility::Normalization(std::vector<double> &kernels)
+{
+    double sum = std::accumulate(kernels.begin(), kernels.end(), 0.0);
+    if(qFuzzyCompare(sum, 0)) return;
+    if(qFuzzyCompare(sum, 1.0)) return;
+    std::transform(kernels.begin(), kernels.end(), kernels.begin(),
+                   [sum](double x){ return x/sum; });
+}
+//得到高斯核(把radius带入高斯公式求解)
+std::vector<double> GaussBlur::GetKernels(int radius)
+{
+    static const double SQRT2PI = qSqrt(2.0 * 3.14159265358979323);
+
+    double sigma = (double)radius / 3.0;
+    double sigma2 = 2.0 * sigma * sigma;
+    double sigmap = sigma * SQRT2PI;
+    
+    std::vector<double> kernels;
+    for(int i = -radius; i <= radius; ++i)
+    {
+        kernels.push_back(qExp(-(double)(i * i) / sigma2) / sigmap);
+    }
+    return kernels;
+}
+
+
+void GaussBlur::Blur(QImage &image, int radius)
+{
+    std::vector<double> kernels = GetKernels(radius);
+    ImageBlurUtility::Normalization(kernels);
+    
+    typedef ImageBlurUtility::pixel_t pixel; 
+    typedef ImageBlurUtility::buff_t buff; 
+    
+    pixel *pData = (pixel*)image.bits();
+    
+    //栈的空间小, 所以这里buffer分配到堆
+    const int size = image.width() * image.height();
+    buff *pBuffer = new buff[size];
+    
+    for(int inx = 0, y = 0; y < image.height(); ++y)
+    {
+        for(int x = 0; x < image.width(); ++x, ++inx)
+        {
+            for(int n = 0, i = -radius; i <= radius; ++i, ++n)
+            {
+                long i_k = ImageBlurUtility::Edge(i, x, image.width());
+                long inx_k = inx + i_k;
+                pBuffer[inx].r += pData[inx_k].r() * kernels[n];
+                pBuffer[inx].g += pData[inx_k].g() * kernels[n];
+                pBuffer[inx].b += pData[inx_k].b() * kernels[n];
+                pBuffer[inx].a += pData[inx_k].a() * kernels[n];
+            }
+        }
+    }
+    
+    for(int inx = 0, x = 0; x < image.width(); ++x)
+    {
+        for(int y = 0; y < image.height(); ++y)
+        {
+            inx = y * image.width() + x;
+            buff buffer;
+            for(int n = 0, i = -radius; i <= radius; ++i, ++n)
+            {
+                int i_k = ImageBlurUtility::Edge(i, y, image.height());
+                int inx_k = inx + i_k * image.width();
+                buffer.r += pBuffer[inx_k].r * kernels[n];
+                buffer.g += pBuffer[inx_k].g * kernels[n];
+                buffer.b += pBuffer[inx_k].b * kernels[n];
+                buffer.a += pBuffer[inx_k].a * kernels[n];
+            }
+            pData[inx].Set(buffer.Red(), buffer.Green(), buffer.Blue(), buffer.Alpha());
+            //pData[inx].Set(Clamp<int>(r), Clamp<int>(g), Clamp<int>(b), Clamp<int>(a));
+        }
+    }
+    delete[] pBuffer;
+}
+
+

+ 48 - 0
common/ImageBlur/imageblur.h

@@ -0,0 +1,48 @@
+#ifndef IMAGEBLUR_H
+#define IMAGEBLUR_H
+
+#include <QImage>
+
+class ImageBlurUtility
+{
+public:
+    //QImage存储数据的方式参考qrgb.h, 按AARRGGBB存
+    //unsigned int4字节, 刚好存放rgba
+    struct pixel_t
+    {
+        unsigned int rgba = 0;
+        inline int r() const { return ((rgba >> 16) & 0xff); }
+        inline int g() const { return ((rgba >> 8) & 0xff); }
+        inline int b() const { return (rgba & 0xff); }
+        inline int a() const { return rgba >> 24; }
+        inline void Set(int r, int g, int b, int a)
+        { rgba = ((a & 0xffu) << 24) | ((r & 0xffu) << 16) | ((g & 0xffu) << 8) | (b & 0xffu); }
+    };
+    //计算时用, 将整数的rgba值变为double类型来作计算, 最后再用qRound转回整数
+    struct buff_t
+    {
+        double r, g, b, a;
+        buff_t() : r(0.0), g(0.0), b(0.0), a(0.0) {}
+        buff_t(const pixel_t &pixel):r(pixel.r()),g(pixel.g()),b(pixel.b()),a(pixel.a()) {}
+        inline int Red() const { return qRound(r); }
+        inline int Green() const { return qRound(g); }
+        inline int Blue() const { return qRound(b); }
+        inline int Alpha() const { return qRound(a); }
+    };
+    
+public:
+    template <typename T>
+    static T Edge(T i, T x, T w);
+public:
+    static void Normalization(std::vector<double> &kernels);
+};
+
+class GaussBlur
+{
+public:
+    static void Blur(QImage &image, int radius);
+private:
+    static std::vector<double> GetKernels(int radius);
+};
+
+#endif // IMAGEBLUR_H

+ 2469 - 0
common/QFtp/qftp.cpp

@@ -0,0 +1,2469 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//#define QFTPPI_DEBUG
+//#define QFTPDTP_DEBUG
+
+#include "qftp.h"
+#include "qabstractsocket.h"
+
+#include "qcoreapplication.h"
+#include "qtcpsocket.h"
+#include "qstringlist.h"
+#include "qregexp.h"
+#include "qtimer.h"
+#include "qfileinfo.h"
+#include "qtcpserver.h"
+#include "qlocale.h"
+
+QT_BEGIN_NAMESPACE
+
+class QFtpPI;
+
+/*
+    The QFtpDTP (DTP = Data Transfer Process) controls all client side
+    data transfer between the client and server.
+*/
+class QFtpDTP : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum ConnectState {
+        CsHostFound,
+        CsConnected,
+        CsClosed,
+        CsHostNotFound,
+        CsConnectionRefused
+    };
+
+    QFtpDTP(QFtpPI *p, QObject *parent = nullptr);
+
+    void setData(QByteArray *);
+    void setDevice(QIODevice *);
+    void writeData();
+    void setBytesTotal(qint64 bytes);
+
+    bool hasError() const;
+    QString errorMessage() const;
+    void clearError();
+
+    void connectToHost(const QString & host, quint16 port);
+    int setupListener(const QHostAddress &address);
+    void waitForConnection();
+
+    QTcpSocket::SocketState state() const;
+    qint64 bytesAvailable() const;
+    qint64 read(char *data, qint64 maxlen);
+    QByteArray readAll();
+
+    void abortConnection();
+
+    static bool parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info);
+
+signals:
+    void listInfo(const QUrlInfo&);
+    void readyRead();
+    void dataTransferProgress(qint64, qint64);
+
+    void connectState(int);
+
+private slots:
+    void socketConnected();
+    void socketReadyRead();
+    void socketError(QAbstractSocket::SocketError);
+    void socketConnectionClosed();
+    void socketBytesWritten(qint64);
+    void setupSocket();
+
+    void dataReadyRead();
+
+private:
+    void clearData();
+
+    QTcpSocket *socket;
+    QTcpServer listener;
+
+    QFtpPI *pi;
+    QString err;
+    qint64 bytesDone;
+    qint64 bytesTotal;
+    bool callWriteData;
+
+    // If is_ba is true, ba is used; ba is never 0.
+    // Otherwise dev is used; dev can be 0 or not.
+    union {
+        QByteArray *ba;
+        QIODevice *dev;
+    } data;
+    bool is_ba;
+
+    QByteArray bytesFromSocket;
+};
+
+/**********************************************************************
+ *
+ * QFtpPI - Protocol Interpreter
+ *
+ *********************************************************************/
+
+class QFtpPI : public QObject
+{
+    Q_OBJECT
+
+public:
+    QFtpPI(QObject *parent = nullptr);
+
+    void connectToHost(const QString &host, quint16 port);
+
+    bool sendCommands(const QStringList &cmds);
+    bool sendCommand(const QString &cmd)
+        { return sendCommands(QStringList(cmd)); }
+
+    void clearPendingCommands();
+    void abort();
+
+    QString currentCommand() const
+        { return currentCmd; }
+
+    bool rawCommand;
+    bool transferConnectionExtended;
+
+    QFtpDTP dtp; // the PI has a DTP which is not the design of RFC 959, but it
+                 // makes the design simpler this way
+signals:
+    void connectState(int);
+    void finished(const QString&);
+    void error(int, const QString&);
+    void rawFtpReply(int, const QString&);
+
+private slots:
+    void hostFound();
+    void connected();
+    void connectionClosed();
+    void delayedCloseFinished();
+    void readyRead();
+    void error(QAbstractSocket::SocketError);
+
+    void dtpConnectState(int);
+
+private:
+    // the states are modelled after the generalized state diagram of RFC 959,
+    // page 58
+    enum State {
+        Begin,
+        Idle,
+        Waiting,
+        Success,
+        Failure
+    };
+
+    enum AbortState {
+        None,
+        AbortStarted,
+        WaitForAbortToFinish
+    };
+
+    bool processReply();
+    bool startNextCmd();
+
+    QTcpSocket commandSocket;
+    QString replyText;
+    char replyCode[3];
+    State state;
+    AbortState abortState;
+    QStringList pendingCommands;
+    QString currentCmd;
+
+    bool waitForDtpToConnect;
+    bool waitForDtpToClose;
+
+    QByteArray bytesFromSocket;
+
+    friend class QFtpDTP;
+};
+
+/**********************************************************************
+ *
+ * QFtpCommand implemenatation
+ *
+ *********************************************************************/
+class QFtpCommand
+{
+public:
+    QFtpCommand(QFtp::Command cmd, const QStringList &raw, const QByteArray &ba);
+    QFtpCommand(QFtp::Command cmd, const QStringList &raw, QIODevice *dev = nullptr);
+    ~QFtpCommand();
+
+    int id;
+    QFtp::Command command;
+    QStringList rawCmds;
+
+    // If is_ba is true, ba is used; ba is never 0.
+    // Otherwise dev is used; dev can be 0 or not.
+    union {
+        QByteArray *ba;
+        QIODevice *dev;
+    } data;
+    bool is_ba;
+
+};
+
+static int nextId()
+{
+    static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0);
+    return 1 + counter.fetchAndAddRelaxed(1);
+}
+
+QFtpCommand::QFtpCommand(QFtp::Command cmd, const QStringList &raw, const QByteArray &ba)
+    : command(cmd), rawCmds(raw), is_ba(true)
+{
+    id = nextId();
+    data.ba = new QByteArray(ba);
+}
+
+QFtpCommand::QFtpCommand(QFtp::Command cmd, const QStringList &raw, QIODevice *dev)
+    : command(cmd), rawCmds(raw), is_ba(false)
+{
+    id = nextId();
+    data.dev = dev;
+}
+
+QFtpCommand::~QFtpCommand()
+{
+    if (is_ba)
+        delete data.ba;
+}
+
+/**********************************************************************
+ *
+ * QFtpDTP implemenatation
+ *
+ *********************************************************************/
+QFtpDTP::QFtpDTP(QFtpPI *p, QObject *parent) :
+    QObject(parent),
+    socket(nullptr),
+    listener(this),
+    pi(p),
+    callWriteData(false)
+{
+    clearData();
+    listener.setObjectName(QLatin1String("QFtpDTP active state server"));
+    connect(&listener, SIGNAL(newConnection()), SLOT(setupSocket()));
+}
+
+void QFtpDTP::setData(QByteArray *ba)
+{
+    is_ba = true;
+    data.ba = ba;
+}
+
+void QFtpDTP::setDevice(QIODevice *dev)
+{
+    is_ba = false;
+    data.dev = dev;
+}
+
+void QFtpDTP::setBytesTotal(qint64 bytes)
+{
+    bytesTotal = bytes;
+    bytesDone = 0;
+    emit dataTransferProgress(bytesDone, bytesTotal);
+}
+
+void QFtpDTP::connectToHost(const QString & host, quint16 port)
+{
+    bytesFromSocket.clear();
+
+    if (socket) {
+        delete socket;
+        socket = nullptr;
+    }
+    socket = new QTcpSocket(this);
+#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
+    //copy network session down to the socket
+    socket->setProperty("_q_networksession", property("_q_networksession"));
+#endif
+    socket->setObjectName(QLatin1String("QFtpDTP Passive state socket"));
+    connect(socket, SIGNAL(connected()), SLOT(socketConnected()));
+    connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead()));
+    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)));
+    connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed()));
+    connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64)));
+
+    socket->connectToHost(host, port);
+}
+
+int QFtpDTP::setupListener(const QHostAddress &address)
+{
+#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
+    //copy network session down to the socket
+    listener.setProperty("_q_networksession", property("_q_networksession"));
+#endif
+    if (!listener.isListening() && !listener.listen(address, 0))
+        return -1;
+    return listener.serverPort();
+}
+
+void QFtpDTP::waitForConnection()
+{
+    // This function is only interesting in Active transfer mode; it works
+    // around a limitation in QFtp's design by blocking, waiting for an
+    // incoming connection. For the default Passive mode, it does nothing.
+    if (listener.isListening())
+        listener.waitForNewConnection();
+}
+
+QTcpSocket::SocketState QFtpDTP::state() const
+{
+    return socket ? socket->state() : QTcpSocket::UnconnectedState;
+}
+
+qint64 QFtpDTP::bytesAvailable() const
+{
+    if (!socket || socket->state() != QTcpSocket::ConnectedState)
+        return (qint64) bytesFromSocket.size();
+    return socket->bytesAvailable();
+}
+
+qint64 QFtpDTP::read(char *data, qint64 maxlen)
+{
+    qint64 read;
+    if (socket && socket->state() == QTcpSocket::ConnectedState) {
+        read = socket->read(data, maxlen);
+    } else {
+        read = qMin(maxlen, qint64(bytesFromSocket.size()));
+        memcpy(data, bytesFromSocket.data(), read);
+        bytesFromSocket.remove(0, read);
+    }
+
+    bytesDone += read;
+    return read;
+}
+
+QByteArray QFtpDTP::readAll()
+{
+    QByteArray tmp;
+    if (socket && socket->state() == QTcpSocket::ConnectedState) {
+        tmp = socket->readAll();
+        bytesDone += tmp.size();
+    } else {
+        tmp = bytesFromSocket;
+        bytesFromSocket.clear();
+    }
+    return tmp;
+}
+
+void QFtpDTP::writeData()
+{
+    if (!socket)
+        return;
+
+    if (is_ba) {
+#if defined(QFTPDTP_DEBUG)
+        qDebug("QFtpDTP::writeData: write %d bytes", data.ba->size());
+#endif
+        if (data.ba->size() == 0)
+            emit dataTransferProgress(0, bytesTotal);
+        else
+            socket->write(data.ba->data(), data.ba->size());
+
+        socket->close();
+
+        clearData();
+    } else if (data.dev) {
+        callWriteData = false;
+        const qint64 blockSize = 16*1024;
+        char buf[16*1024];
+        qint64 read = data.dev->read(buf, blockSize);
+#if defined(QFTPDTP_DEBUG)
+        qDebug("QFtpDTP::writeData: write() of size %lli bytes", read);
+#endif
+        if (read > 0) {
+            socket->write(buf, read);
+        } else if (read == -1 || (!data.dev->isSequential() && data.dev->atEnd())) {
+            // error or EOF
+            if (bytesDone == 0 && socket->bytesToWrite() == 0)
+                emit dataTransferProgress(0, bytesTotal);
+            socket->close();
+            clearData();
+        }
+
+        // do we continue uploading?
+        callWriteData = data.dev != nullptr;
+    }
+}
+
+void QFtpDTP::dataReadyRead()
+{
+    writeData();
+}
+
+inline bool QFtpDTP::hasError() const
+{
+    return !err.isNull();
+}
+
+inline QString QFtpDTP::errorMessage() const
+{
+    return err;
+}
+
+inline void QFtpDTP::clearError()
+{
+    err.clear();
+}
+
+void QFtpDTP::abortConnection()
+{
+#if defined(QFTPDTP_DEBUG)
+    qDebug("QFtpDTP::abortConnection, bytesAvailable == %lli",
+           socket ? socket->bytesAvailable() : (qint64) 0);
+#endif
+    callWriteData = false;
+    clearData();
+
+    if (socket)
+        socket->abort();
+}
+
+static void _q_fixupDateTime(QDateTime *dateTime)
+{
+    // Adjust for future tolerance.
+    const int futureTolerance = 86400;
+    if (dateTime->secsTo(QDateTime::currentDateTime()) < -futureTolerance) {
+        QDate d = dateTime->date();
+        d.setDate(d.year() - 1, d.month(), d.day());
+        dateTime->setDate(d);
+    }
+}
+
+static void _q_parseUnixDir(const QStringList &tokens, const QString &userName, QUrlInfo *info)
+{
+    // Unix style, 7 + 1 entries
+    // -rw-r--r--    1 ftp      ftp      17358091 Aug 10  2004 qt-x11-free-3.3.3.tar.gz
+    // drwxr-xr-x    3 ftp      ftp          4096 Apr 14  2000 compiled-examples
+    // lrwxrwxrwx    1 ftp      ftp             9 Oct 29  2005 qtscape -> qtmozilla
+    if (tokens.size() != 8)
+        return;
+
+    char first = tokens.at(1).at(0).toLatin1();
+    if (first == 'd') {
+        info->setDir(true);
+        info->setFile(false);
+        info->setSymLink(false);
+    } else if (first == '-') {
+        info->setDir(false);
+        info->setFile(true);
+        info->setSymLink(false);
+    } else if (first == 'l') {
+        info->setDir(true);
+        info->setFile(false);
+        info->setSymLink(true);
+    }
+
+    // Resolve filename
+    QString name = tokens.at(7);
+    if (info->isSymLink()) {
+        int linkPos = name.indexOf(QLatin1String(" ->"));
+        if (linkPos != -1)
+            name.resize(linkPos);
+    }
+    info->setName(name);
+
+    // Resolve owner & group
+    info->setOwner(tokens.at(3));
+    info->setGroup(tokens.at(4));
+
+    // Resolve size
+    info->setSize(tokens.at(5).toLongLong());
+
+    QStringList formats;
+    formats << QLatin1String("MMM dd  yyyy") << QLatin1String("MMM dd hh:mm") << QLatin1String("MMM  d  yyyy")
+            << QLatin1String("MMM  d hh:mm") << QLatin1String("MMM  d yyyy") << QLatin1String("MMM dd yyyy");
+
+    QString dateString = tokens.at(6);
+    dateString[0] = dateString[0].toUpper();
+
+    // Resolve the modification date by parsing all possible formats
+    QDateTime dateTime;
+    int n = 0;
+#ifndef QT_NO_DATESTRING
+    do {
+        dateTime = QLocale::c().toDateTime(dateString, formats.at(n++));
+    }  while (n < formats.size() && (!dateTime.isValid()));
+#endif
+
+    if (n == 2 || n == 4) {
+        // Guess the year.
+        dateTime.setDate(QDate(QDate::currentDate().year(),
+                               dateTime.date().month(),
+                               dateTime.date().day()));
+        _q_fixupDateTime(&dateTime);
+    }
+    if (dateTime.isValid())
+        info->setLastModified(dateTime);
+
+    // Resolve permissions
+    int permissions = 0;
+    const QString &p = tokens.at(2);
+    permissions |= (p[0] == QLatin1Char('r') ? QUrlInfo::ReadOwner : 0);
+    permissions |= (p[1] == QLatin1Char('w') ? QUrlInfo::WriteOwner : 0);
+    permissions |= (p[2] == QLatin1Char('x') ? QUrlInfo::ExeOwner : 0);
+    permissions |= (p[3] == QLatin1Char('r') ? QUrlInfo::ReadGroup : 0);
+    permissions |= (p[4] == QLatin1Char('w') ? QUrlInfo::WriteGroup : 0);
+    permissions |= (p[5] == QLatin1Char('x') ? QUrlInfo::ExeGroup : 0);
+    permissions |= (p[6] == QLatin1Char('r') ? QUrlInfo::ReadOther : 0);
+    permissions |= (p[7] == QLatin1Char('w') ? QUrlInfo::WriteOther : 0);
+    permissions |= (p[8] == QLatin1Char('x') ? QUrlInfo::ExeOther : 0);
+    info->setPermissions(permissions);
+
+    bool isOwner = info->owner() == userName;
+    info->setReadable((permissions & QUrlInfo::ReadOther) || ((permissions & QUrlInfo::ReadOwner) && isOwner));
+    info->setWritable((permissions & QUrlInfo::WriteOther) || ((permissions & QUrlInfo::WriteOwner) && isOwner));
+}
+
+static void _q_parseDosDir(const QStringList &tokens, const QString &userName, QUrlInfo *info)
+{
+    // DOS style, 3 + 1 entries
+    // 01-16-02  11:14AM       <DIR>          epsgroup
+    // 06-05-03  03:19PM                 1973 readme.txt
+    if (tokens.size() != 4)
+        return;
+
+    Q_UNUSED(userName);
+
+    QString name = tokens.at(3);
+    info->setName(name);
+    info->setSymLink(name.endsWith(QLatin1String(".lnk"), Qt::CaseInsensitive));
+
+    if (tokens.at(2) == QLatin1String("<DIR>")) {
+        info->setFile(false);
+        info->setDir(true);
+    } else {
+        info->setFile(true);
+        info->setDir(false);
+        info->setSize(tokens.at(2).toLongLong());
+    }
+
+    // Note: We cannot use QFileInfo; permissions are for the server-side
+    // machine, and QFileInfo's behavior depends on the local platform.
+    int permissions = QUrlInfo::ReadOwner | QUrlInfo::WriteOwner
+                      | QUrlInfo::ReadGroup | QUrlInfo::WriteGroup
+                      | QUrlInfo::ReadOther | QUrlInfo::WriteOther;
+    QStringRef ext;
+    int extIndex = name.lastIndexOf(QLatin1Char('.'));
+    if (extIndex != -1)
+        ext = name.midRef(extIndex + 1);
+    if (ext == QLatin1String("exe") || ext == QLatin1String("bat") || ext == QLatin1String("com"))
+        permissions |= QUrlInfo::ExeOwner | QUrlInfo::ExeGroup | QUrlInfo::ExeOther;
+    info->setPermissions(permissions);
+
+    info->setReadable(true);
+    info->setWritable(info->isFile());
+
+    QDateTime dateTime;
+#ifndef QT_NO_DATESTRING
+    dateTime = QLocale::c().toDateTime(tokens.at(1), QLatin1String("MM-dd-yy  hh:mmAP"));
+    if (dateTime.date().year() < 1971) {
+        dateTime.setDate(QDate(dateTime.date().year() + 100,
+                               dateTime.date().month(),
+                               dateTime.date().day()));
+    }
+#endif
+
+    info->setLastModified(dateTime);
+
+}
+
+bool QFtpDTP::parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info)
+{
+    if (buffer.isEmpty())
+        return false;
+
+    QString bufferStr = QString::fromUtf8(buffer).trimmed();
+
+    // Unix style FTP servers
+    QRegExp unixPattern(QLatin1String("^([\\-dl])([a-zA-Z\\-]{9,9})\\s+\\d+\\s+(\\S*)\\s+"
+                                      "(\\S*)\\s+(\\d+)\\s+(\\S+\\s+\\S+\\s+\\S+)\\s+(\\S.*)"));
+    if (unixPattern.indexIn(bufferStr) == 0) {
+        _q_parseUnixDir(unixPattern.capturedTexts(), userName, info);
+        return true;
+    }
+
+    // DOS style FTP servers
+    QRegExp dosPattern(QLatin1String("^(\\d\\d-\\d\\d-\\d\\d\\ \\ \\d\\d:\\d\\d[AP]M)\\s+"
+                                     "(<DIR>|\\d+)\\s+(\\S.*)$"));
+    if (dosPattern.indexIn(bufferStr) == 0) {
+        _q_parseDosDir(dosPattern.capturedTexts(), userName, info);
+        return true;
+    }
+
+    // Unsupported
+    return false;
+}
+
+void QFtpDTP::socketConnected()
+{
+    bytesDone = 0;
+#if defined(QFTPDTP_DEBUG)
+    qDebug("QFtpDTP::connectState(CsConnected)");
+#endif
+    emit connectState(QFtpDTP::CsConnected);
+}
+
+void QFtpDTP::socketReadyRead()
+{
+    if (!socket)
+        return;
+
+    if (pi->currentCommand().isEmpty()) {
+        socket->close();
+#if defined(QFTPDTP_DEBUG)
+        qDebug("QFtpDTP::connectState(CsClosed)");
+#endif
+        emit connectState(QFtpDTP::CsClosed);
+        return;
+    }
+
+    if (pi->abortState != QFtpPI::None) {
+        // discard data
+        socket->readAll();
+        return;
+    }
+
+    if (pi->currentCommand().startsWith(QLatin1String("LIST"))) {
+        while (socket->canReadLine()) {
+            QUrlInfo i;
+            QByteArray line = socket->readLine();
+#if defined(QFTPDTP_DEBUG)
+            qDebug("QFtpDTP read (list): '%s'", line.constData());
+#endif
+            if (parseDir(line, QLatin1String(""), &i)) {
+                emit listInfo(i);
+            } else {
+                // some FTP servers don't return a 550 if the file or directory
+                // does not exist, but rather write a text to the data socket
+                // -- try to catch these cases
+                if (line.endsWith("No such file or directory\r\n"))
+                    err = QString::fromUtf8(line);
+            }
+        }
+    } else {
+        if (!is_ba && data.dev) {
+            do {
+                QByteArray ba;
+                ba.resize(socket->bytesAvailable());
+                qint64 bytesRead = socket->read(ba.data(), ba.size());
+                if (bytesRead < 0) {
+                    // a read following a readyRead() signal will
+                    // never fail.
+                    return;
+                }
+                ba.resize(bytesRead);
+                bytesDone += bytesRead;
+#if defined(QFTPDTP_DEBUG)
+                qDebug("QFtpDTP read: %lli bytes (total %lli bytes)", bytesRead, bytesDone);
+#endif
+                if (data.dev)       // make sure it wasn't deleted in the slot
+                    data.dev->write(ba);
+                emit dataTransferProgress(bytesDone, bytesTotal);
+
+                // Need to loop; dataTransferProgress is often connected to
+                // slots that update the GUI (e.g., progress bar values), and
+                // if events are processed, more data may have arrived.
+            } while (socket->bytesAvailable());
+        } else {
+#if defined(QFTPDTP_DEBUG)
+            qDebug("QFtpDTP readyRead: %lli bytes available (total %lli bytes read)",
+                   bytesAvailable(), bytesDone);
+#endif
+            emit dataTransferProgress(bytesDone+socket->bytesAvailable(), bytesTotal);
+            emit readyRead();
+        }
+    }
+}
+
+void QFtpDTP::socketError(QAbstractSocket::SocketError e)
+{
+    if (e == QTcpSocket::HostNotFoundError) {
+#if defined(QFTPDTP_DEBUG)
+        qDebug("QFtpDTP::connectState(CsHostNotFound)");
+#endif
+        emit connectState(QFtpDTP::CsHostNotFound);
+    } else if (e == QTcpSocket::ConnectionRefusedError) {
+#if defined(QFTPDTP_DEBUG)
+        qDebug("QFtpDTP::connectState(CsConnectionRefused)");
+#endif
+        emit connectState(QFtpDTP::CsConnectionRefused);
+    }
+}
+
+void QFtpDTP::socketConnectionClosed()
+{
+    if (!is_ba && data.dev) {
+        clearData();
+    }
+
+    if (socket->isOpen())
+        bytesFromSocket = socket->readAll();
+    else
+        bytesFromSocket.clear();
+#if defined(QFTPDTP_DEBUG)
+    qDebug("QFtpDTP::connectState(CsClosed)");
+#endif
+    emit connectState(QFtpDTP::CsClosed);
+}
+
+void QFtpDTP::socketBytesWritten(qint64 bytes)
+{
+    bytesDone += bytes;
+#if defined(QFTPDTP_DEBUG)
+    qDebug("QFtpDTP::bytesWritten(%lli)", bytesDone);
+#endif
+    emit dataTransferProgress(bytesDone, bytesTotal);
+    if (callWriteData)
+        writeData();
+}
+
+void QFtpDTP::setupSocket()
+{
+    socket = listener.nextPendingConnection();
+    socket->setObjectName(QLatin1String("QFtpDTP Active state socket"));
+    connect(socket, SIGNAL(connected()), SLOT(socketConnected()));
+    connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead()));
+    connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)));
+    connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed()));
+    connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64)));
+
+    listener.close();
+}
+
+void QFtpDTP::clearData()
+{
+    is_ba = false;
+    data.dev = nullptr;
+}
+
+/**********************************************************************
+ *
+ * QFtpPI implemenatation
+ *
+ *********************************************************************/
+QFtpPI::QFtpPI(QObject *parent) :
+    QObject(parent),
+    rawCommand(false),
+    transferConnectionExtended(true),
+    dtp(this),
+    commandSocket(nullptr),
+    state(Begin), abortState(None),
+    currentCmd(QString()),
+    waitForDtpToConnect(false),
+    waitForDtpToClose(false)
+{
+    commandSocket.setObjectName(QLatin1String("QFtpPI_socket"));
+    connect(&commandSocket, SIGNAL(hostFound()),
+            SLOT(hostFound()));
+    connect(&commandSocket, SIGNAL(connected()),
+            SLOT(connected()));
+    connect(&commandSocket, SIGNAL(disconnected()),
+            SLOT(connectionClosed()));
+    connect(&commandSocket, SIGNAL(readyRead()),
+            SLOT(readyRead()));
+//    connect(&commandSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)),
+//            SLOT(error(QAbstractSocket::SocketError)));
+
+    connect(&commandSocket, SIGNAL(error(QAbstractSocket::SocketError)),
+            SLOT(error(QAbstractSocket::SocketError)));
+
+    connect(&dtp, SIGNAL(connectState(int)),
+             SLOT(dtpConnectState(int)));
+}
+
+void QFtpPI::connectToHost(const QString &host, quint16 port)
+{
+    emit connectState(QFtp::HostLookup);
+#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
+    //copy network session down to the socket & DTP
+    commandSocket.setProperty("_q_networksession", property("_q_networksession"));
+    dtp.setProperty("_q_networksession", property("_q_networksession"));
+#endif
+    commandSocket.connectToHost(host, port);
+}
+
+/*
+  \internal
+
+  Sends the sequence of commands \a cmds to the FTP server. When the commands
+  are all done the finished() signal is emitted. When an error occurs, the
+  error() signal is emitted.
+
+  If there are pending commands in the queue this functions returns \c false and
+  the \a cmds are not added to the queue; otherwise it returns \c true.
+*/
+bool QFtpPI::sendCommands(const QStringList &cmds)
+{
+    if (!pendingCommands.isEmpty())
+        return false;
+
+    if (commandSocket.state() != QTcpSocket::ConnectedState || state!=Idle) {
+        emit error(QFtp::NotConnected, QFtp::tr("Not connected"));
+        return true; // there are no pending commands
+    }
+
+    pendingCommands = cmds;
+    startNextCmd();
+    return true;
+}
+
+void QFtpPI::clearPendingCommands()
+{
+    pendingCommands.clear();
+    dtp.abortConnection();
+    currentCmd.clear();
+    state = Idle;
+}
+
+void QFtpPI::abort()
+{
+    pendingCommands.clear();
+
+    if (abortState != None)
+        // ABOR already sent
+        return;
+
+    if (currentCmd.isEmpty())
+        return; //no command in progress
+
+    if (currentCmd.startsWith(QLatin1String("STOR "))) {
+        abortState = AbortStarted;
+#if defined(QFTPPI_DEBUG)
+        qDebug("QFtpPI send: ABOR");
+#endif
+        commandSocket.write("ABOR\r\n", 6);
+
+        dtp.abortConnection();
+    } else {
+        //Deviation from RFC 959:
+        //Most FTP servers do not support ABOR, or require the telnet
+        //IP & synch sequence (TCP urgent data) which is not supported by QTcpSocket.
+        //Following what most FTP clients do, just reset the data connection and wait for 426
+        abortState = WaitForAbortToFinish;
+        dtp.abortConnection();
+    }
+}
+
+void QFtpPI::hostFound()
+{
+    emit connectState(QFtp::Connecting);
+}
+
+void QFtpPI::connected()
+{
+    state = Begin;
+#if defined(QFTPPI_DEBUG)
+//    qDebug("QFtpPI state: %d [connected()]", state);
+#endif
+    // try to improve performance by setting TCP_NODELAY
+    commandSocket.setSocketOption(QAbstractSocket::LowDelayOption, 1);
+
+    emit connectState(QFtp::Connected);
+}
+
+void QFtpPI::connectionClosed()
+{
+    commandSocket.close();
+    emit connectState(QFtp::Unconnected);
+}
+
+void QFtpPI::delayedCloseFinished()
+{
+    emit connectState(QFtp::Unconnected);
+}
+
+void QFtpPI::error(QAbstractSocket::SocketError e)
+{
+    if (e == QTcpSocket::HostNotFoundError) {
+        emit connectState(QFtp::Unconnected);
+        emit error(QFtp::HostNotFound,
+                    QFtp::tr("Host %1 not found").arg(commandSocket.peerName()));
+    } else if (e == QTcpSocket::ConnectionRefusedError) {
+        emit connectState(QFtp::Unconnected);
+        emit error(QFtp::ConnectionRefused,
+                    QFtp::tr("Connection refused to host %1").arg(commandSocket.peerName()));
+    } else if (e == QTcpSocket::SocketTimeoutError) {
+        emit connectState(QFtp::Unconnected);
+        emit error(QFtp::ConnectionRefused,
+                   QFtp::tr("Connection timed out to host %1").arg(commandSocket.peerName()));
+    }
+}
+
+void QFtpPI::readyRead()
+{
+    if (waitForDtpToClose)
+        return;
+
+    while (commandSocket.canReadLine()) {
+        // read line with respect to line continuation
+        QString line = QString::fromUtf8(commandSocket.readLine());
+        if (replyText.isEmpty()) {
+            if (line.length() < 3) {
+                // protocol error
+                return;
+            }
+            const int lowerLimit[3] = {1,0,0};
+            const int upperLimit[3] = {5,5,9};
+            for (int i=0; i<3; i++) {
+                replyCode[i] = line.at(i).digitValue();
+                if (replyCode[i]<lowerLimit[i] || replyCode[i]>upperLimit[i]) {
+                    // protocol error
+                    return;
+                }
+            }
+        }
+        const char count[4] = { char('0' + replyCode[0]), char('0' + replyCode[1]),
+                                char('0' + replyCode[2]), char(' ') };
+        QString endOfMultiLine(QLatin1String(count, 4));
+        QString lineCont(endOfMultiLine);
+        lineCont[3] = QLatin1Char('-');
+        QStringRef lineLeft4 = line.leftRef(4);
+
+        while (lineLeft4 != endOfMultiLine) {
+            if (lineLeft4 == lineCont)
+                replyText += line.midRef(4); // strip 'xyz-'
+            else
+                replyText += line;
+            if (!commandSocket.canReadLine())
+                return;
+            line = QString::fromUtf8(commandSocket.readLine());
+            lineLeft4 = line.leftRef(4);
+        }
+        replyText += line.midRef(4); // strip reply code 'xyz '
+        if (replyText.endsWith(QLatin1String("\r\n")))
+            replyText.chop(2);
+
+        if (processReply())
+            replyText = QLatin1String("");
+    }
+}
+
+/*
+  \internal
+
+  Process a reply from the FTP server.
+
+  Returns \c true if the reply was processed or false if the reply has to be
+  processed at a later point.
+*/
+bool QFtpPI::processReply()
+{
+#if defined(QFTPPI_DEBUG)
+//    qDebug("QFtpPI state: %d [processReply() begin]", state);
+    if (replyText.length() < 400)
+        qDebug("QFtpPI recv: %d %s", 100*replyCode[0]+10*replyCode[1]+replyCode[2], replyText.toLatin1().constData());
+    else
+        qDebug("QFtpPI recv: %d (text skipped)", 100*replyCode[0]+10*replyCode[1]+replyCode[2]);
+#endif
+
+    int replyCodeInt = 100*replyCode[0] + 10*replyCode[1] + replyCode[2];
+
+    // process 226 replies ("Closing Data Connection") only when the data
+    // connection is really closed to avoid short reads of the DTP
+    if (replyCodeInt == 226 || (replyCodeInt == 250 && currentCmd.startsWith(QLatin1String("RETR")))) {
+        if (dtp.state() != QTcpSocket::UnconnectedState) {
+            waitForDtpToClose = true;
+            return false;
+        }
+    }
+
+    switch (abortState) {
+        case AbortStarted:
+            abortState = WaitForAbortToFinish;
+            break;
+        case WaitForAbortToFinish:
+            abortState = None;
+            return true;
+        default:
+            break;
+    }
+
+    // get new state
+    static const State table[5] = {
+        /* 1yz   2yz      3yz   4yz      5yz */
+        Waiting, Success, Idle, Failure, Failure
+    };
+    switch (state) {
+        case Begin:
+            if (replyCode[0] == 1) {
+                return true;
+            } else if (replyCode[0] == 2) {
+                state = Idle;
+                emit finished(QFtp::tr("Connected to host %1").arg(commandSocket.peerName()));
+                break;
+            }
+            // reply codes not starting with 1 or 2 are not handled.
+            return true;
+        case Waiting:
+            if (static_cast<signed char>(replyCode[0]) < 0 || replyCode[0] > 5)
+                state = Failure;
+            else
+            if (replyCodeInt == 202)
+                state = Failure;
+            else
+                state = table[replyCode[0] - 1];
+            break;
+        default:
+            // ignore unrequested message
+            return true;
+    }
+#if defined(QFTPPI_DEBUG)
+//    qDebug("QFtpPI state: %d [processReply() intermediate]", state);
+#endif
+
+    // special actions on certain replies
+    emit rawFtpReply(replyCodeInt, replyText);
+    if (rawCommand) {
+        rawCommand = false;
+    } else if (replyCodeInt == 227) {
+        // 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)
+        // rfc959 does not define this response precisely, and gives
+        // both examples where the parenthesis are used, and where
+        // they are missing. We need to scan for the address and host
+        // info.
+        QRegExp addrPortPattern(QLatin1String("(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)"));
+        if (addrPortPattern.indexIn(replyText) == -1) {
+#if defined(QFTPPI_DEBUG)
+            qDebug("QFtp: bad 227 response -- address and port information missing");
+#endif
+            // this error should be reported
+        } else {
+            const QStringList lst = addrPortPattern.capturedTexts();
+            QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4];
+            quint16 port = (lst[5].toUInt() << 8) + lst[6].toUInt();
+            waitForDtpToConnect = true;
+            dtp.connectToHost(host, port);
+        }
+    } else if (replyCodeInt == 229) {
+        // 229 Extended Passive mode OK (|||10982|)
+        int portPos = replyText.indexOf(QLatin1Char('('));
+        if (portPos == -1) {
+#if defined(QFTPPI_DEBUG)
+            qDebug("QFtp: bad 229 response -- port information missing");
+#endif
+            // this error should be reported
+        } else {
+            ++portPos;
+            QChar delimiter = replyText.at(portPos);
+            const auto epsvParameters = replyText.midRef(portPos).split(delimiter);
+
+            waitForDtpToConnect = true;
+            dtp.connectToHost(commandSocket.peerAddress().toString(),
+                              epsvParameters.at(3).toInt());
+        }
+
+    } else if (replyCodeInt == 230) {
+        if (currentCmd.startsWith(QLatin1String("USER ")) && pendingCommands.count()>0 &&
+            pendingCommands.constFirst().startsWith(QLatin1String("PASS "))) {
+            // no need to send the PASS -- we are already logged in
+            pendingCommands.pop_front();
+        }
+        // 230 User logged in, proceed.
+        emit connectState(QFtp::LoggedIn);
+    } else if (replyCodeInt == 213) {
+        // 213 File status.
+        if (currentCmd.startsWith(QLatin1String("SIZE ")))
+            dtp.setBytesTotal(replyText.simplified().toLongLong());
+    } else if (replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR "))) {
+        dtp.waitForConnection();
+        dtp.writeData();
+    }
+
+    // react on new state
+    switch (state) {
+        case Begin:
+            // should never happen
+            break;
+        case Success:
+            // success handling
+            state = Idle;
+//            Q_FALLTHROUGH();
+        case Idle:
+            if (dtp.hasError()) {
+                emit error(QFtp::UnknownError, dtp.errorMessage());
+                dtp.clearError();
+            }
+            startNextCmd();
+            break;
+        case Waiting:
+            // do nothing
+            break;
+        case Failure:
+            // If the EPSV or EPRT commands fail, replace them with
+            // the old PASV and PORT instead and try again.
+            if (currentCmd.startsWith(QLatin1String("EPSV"))) {
+                transferConnectionExtended = false;
+                pendingCommands.prepend(QLatin1String("PASV\r\n"));
+            } else if (currentCmd.startsWith(QLatin1String("EPRT"))) {
+                transferConnectionExtended = false;
+                pendingCommands.prepend(QLatin1String("PORT\r\n"));
+            } else {
+                emit error(QFtp::UnknownError, replyText);
+            }
+            if (state != Waiting) {
+                state = Idle;
+                startNextCmd();
+            }
+            break;
+    }
+#if defined(QFTPPI_DEBUG)
+//    qDebug("QFtpPI state: %d [processReply() end]", state);
+#endif
+    return true;
+}
+
+/*
+  \internal
+
+  Starts next pending command. Returns \c false if there are no pending commands,
+  otherwise it returns \c true.
+*/
+bool QFtpPI::startNextCmd()
+{
+    if (waitForDtpToConnect)
+        // don't process any new commands until we are connected
+        return true;
+
+#if defined(QFTPPI_DEBUG)
+    if (state != Idle)
+        qDebug("QFtpPI startNextCmd: Internal error! QFtpPI called in non-Idle state %d", state);
+#endif
+    if (pendingCommands.isEmpty()) {
+        currentCmd.clear();
+        emit finished(replyText);
+        return false;
+    }
+    currentCmd = pendingCommands.constFirst();
+
+    // PORT and PASV are edited in-place, depending on whether we
+    // should try the extended transfer connection commands EPRT and
+    // EPSV. The PORT command also triggers setting up a listener, and
+    // the address/port arguments are edited in.
+    QHostAddress address = commandSocket.localAddress();
+    if (currentCmd.startsWith(QLatin1String("PORT"))) {
+        if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) {
+            int port = dtp.setupListener(address);
+            currentCmd = QLatin1String("EPRT |");
+            currentCmd += (address.protocol() == QTcpSocket::IPv4Protocol) ? QLatin1Char('1') : QLatin1Char('2');
+            currentCmd += QLatin1Char('|') + address.toString() + QLatin1Char('|') + QString::number(port);
+            currentCmd += QLatin1Char('|');
+        } else if (address.protocol() == QTcpSocket::IPv4Protocol) {
+            int port = dtp.setupListener(address);
+            QString portArg;
+            quint32 ip = address.toIPv4Address();
+            portArg += QString::number((ip & 0xff000000) >> 24);
+            portArg += QLatin1Char(',') + QString::number((ip & 0xff0000) >> 16);
+            portArg += QLatin1Char(',') + QString::number((ip & 0xff00) >> 8);
+            portArg += QLatin1Char(',') + QString::number(ip & 0xff);
+            portArg += QLatin1Char(',') + QString::number((port & 0xff00) >> 8);
+            portArg += QLatin1Char(',') + QString::number(port & 0xff);
+
+            currentCmd = QLatin1String("PORT ");
+            currentCmd += portArg;
+        } else {
+            // No IPv6 connection can be set up with the PORT
+            // command.
+            return false;
+        }
+
+        currentCmd += QLatin1String("\r\n");
+    } else if (currentCmd.startsWith(QLatin1String("PASV"))) {
+        if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended)
+            currentCmd = QLatin1String("EPSV\r\n");
+    }
+
+    pendingCommands.pop_front();
+#if defined(QFTPPI_DEBUG)
+    qDebug("QFtpPI send: %s", currentCmd.leftRef(currentCmd.length() - 2).toLatin1().constData());
+#endif
+    state = Waiting;
+    commandSocket.write(currentCmd.toUtf8());
+    return true;
+}
+
+void QFtpPI::dtpConnectState(int s)
+{
+    switch (s) {
+        case QFtpDTP::CsClosed:
+            if (waitForDtpToClose) {
+                // there is an unprocessed reply
+                if (processReply())
+                    replyText = QLatin1String("");
+                else
+                    return;
+            }
+            waitForDtpToClose = false;
+            readyRead();
+            return;
+        case QFtpDTP::CsConnected:
+            waitForDtpToConnect = false;
+            startNextCmd();
+            return;
+        case QFtpDTP::CsHostNotFound:
+        case QFtpDTP::CsConnectionRefused:
+            emit error(QFtp::ConnectionRefused,
+                        QFtp::tr("Data Connection refused"));
+            startNextCmd();
+            return;
+        default:
+            return;
+    }
+}
+
+/**********************************************************************
+ *
+ * QFtpPrivate
+ *
+ *********************************************************************/
+
+class QFtpPrivate
+{
+    Q_DECLARE_PUBLIC(QFtp)
+public:
+
+    inline QFtpPrivate(QFtp *owner) : close_waitForStateChange(false), state(QFtp::Unconnected),
+                           transferMode(QFtp::Passive), error(QFtp::NoError), q_ptr(owner)
+    { }
+
+    ~QFtpPrivate() { while (!pending.isEmpty()) delete pending.takeFirst(); }
+
+    // private slots
+    void _q_startNextCommand();
+    void _q_piFinished(const QString&);
+    void _q_piError(int, const QString&);
+    void _q_piConnectState(int);
+    void _q_piFtpReply(int, const QString&);
+
+    int addCommand(QFtpCommand *cmd);
+
+    QFtpPI pi;
+    QList<QFtpCommand *> pending;
+    bool close_waitForStateChange;
+    QFtp::State state;
+    QFtp::TransferMode transferMode;
+    QFtp::Error error;
+    QString errorString;
+
+    QString host;
+    quint16 port;
+    QString proxyHost;
+    quint16 proxyPort;
+    QFtp *q_ptr;
+};
+
+int QFtpPrivate::addCommand(QFtpCommand *cmd)
+{
+    pending.append(cmd);
+
+    if (pending.count() == 1) {
+        // don't emit the commandStarted() signal before the ID is returned
+        QTimer::singleShot(0, q_func(), SLOT(_q_startNextCommand()));
+    }
+    return cmd->id;
+}
+
+/**********************************************************************
+ *
+ * QFtp implementation
+ *
+ *********************************************************************/
+/*!
+    \internal
+    \class QFtp
+    \brief The QFtp class provides an implementation of the client side of FTP protocol.
+
+    \ingroup network
+    \inmodule QtNetwork
+
+
+    This class provides a direct interface to FTP that allows you to
+    have more control over the requests. However, for new
+    applications, it is recommended to use QNetworkAccessManager and
+    QNetworkReply, as those classes possess a simpler, yet more
+    powerful API.
+
+    The class works asynchronously, so there are no blocking
+    functions. If an operation cannot be executed immediately, the
+    function will still return straight away and the operation will be
+    scheduled for later execution. The results of scheduled operations
+    are reported via signals. This approach depends on the event loop
+    being in operation.
+
+    The operations that can be scheduled (they are called "commands"
+    in the rest of the documentation) are the following:
+    connectToHost(), login(), close(), list(), cd(), get(), put(),
+    remove(), mkdir(), rmdir(), rename() and rawCommand().
+
+    All of these commands return a unique identifier that allows you
+    to keep track of the command that is currently being executed.
+    When the execution of a command starts, the commandStarted()
+    signal with the command's identifier is emitted. When the command
+    is finished, the commandFinished() signal is emitted with the
+    command's identifier and a bool that indicates whether the command
+    finished with an error.
+
+    In some cases, you might want to execute a sequence of commands,
+    e.g. if you want to connect and login to a FTP server. This is
+    simply achieved:
+
+    \snippet code/src_network_access_qftp.cpp 0
+
+    In this case two FTP commands have been scheduled. When the last
+    scheduled command has finished, a done() signal is emitted with
+    a bool argument that tells you whether the sequence finished with
+    an error.
+
+    If an error occurs during the execution of one of the commands in
+    a sequence of commands, all the pending commands (i.e. scheduled,
+    but not yet executed commands) are cleared and no signals are
+    emitted for them.
+
+    Some commands, e.g. list(), emit additional signals to report
+    their results.
+
+    Example: If you want to download the INSTALL file from the Qt
+    FTP server, you would write this:
+
+    \snippet code/src_network_access_qftp.cpp 1
+
+    For this example the following sequence of signals is emitted
+    (with small variations, depending on network traffic, etc.):
+
+    \snippet code/src_network_access_qftp.cpp 2
+
+    The dataTransferProgress() signal in the above example is useful
+    if you want to show a \l{QProgressBar}{progress bar} to
+    inform the user about the progress of the download. The
+    readyRead() signal tells you that there is data ready to be read.
+    The amount of data can be queried then with the bytesAvailable()
+    function and it can be read with the read() or readAll()
+    function.
+
+    If the login fails for the above example, the signals would look
+    like this:
+
+    \snippet code/src_network_access_qftp.cpp 3
+
+    You can then get details about the error with the error() and
+    errorString() functions.
+
+    For file transfer, QFtp can use both active or passive mode, and
+    it uses passive file transfer mode by default; see the
+    documentation for setTransferMode() for more details about this.
+
+    Call setProxy() to make QFtp connect via an FTP proxy server.
+
+    The functions currentId() and currentCommand() provide more
+    information about the currently executing command.
+
+    The functions hasPendingCommands() and clearPendingCommands()
+    allow you to query and clear the list of pending commands.
+
+    If you are an experienced network programmer and want to have
+    complete control you can use rawCommand() to execute arbitrary FTP
+    commands.
+
+    \warning The current version of QFtp doesn't fully support
+    non-Unix FTP servers.
+
+    \sa QNetworkAccessManager, QNetworkRequest, QNetworkReply,
+        {FTP Example}
+*/
+
+
+/*!
+    \internal
+    Constructs a QFtp object with the given \a parent.
+*/
+QFtp::QFtp(QObject *parent)
+    : QObject(parent), d(new QFtpPrivate(this))
+{
+//    Q_D(QFtp);
+    d->errorString = tr("Unknown error");
+
+    connect(&d->pi, SIGNAL(connectState(int)),
+            SLOT(_q_piConnectState(int)));
+    connect(&d->pi, SIGNAL(finished(QString)),
+            SLOT(_q_piFinished(QString)));
+    connect(&d->pi, SIGNAL(error(int,QString)),
+            SLOT(_q_piError(int,QString)));
+    connect(&d->pi, SIGNAL(rawFtpReply(int,QString)),
+            SLOT(_q_piFtpReply(int,QString)));
+
+    connect(&d->pi.dtp, SIGNAL(readyRead()),
+            SIGNAL(readyRead()));
+    connect(&d->pi.dtp, SIGNAL(dataTransferProgress(qint64,qint64)),
+            SIGNAL(dataTransferProgress(qint64,qint64)));
+    connect(&d->pi.dtp, SIGNAL(listInfo(QUrlInfo)),
+            SIGNAL(listInfo(QUrlInfo)));
+}
+
+/*!
+    \internal
+    \enum QFtp::State
+
+    This enum defines the connection state:
+
+    \value Unconnected There is no connection to the host.
+    \value HostLookup A host name lookup is in progress.
+    \value Connecting An attempt to connect to the host is in progress.
+    \value Connected Connection to the host has been achieved.
+    \value LoggedIn Connection and user login have been achieved.
+    \value Closing The connection is closing down, but it is not yet
+    closed. (The state will be \c Unconnected when the connection is
+    closed.)
+
+    \sa stateChanged(), state()
+*/
+/*!
+    \internal
+    \enum QFtp::TransferMode
+
+    FTP works with two socket connections; one for commands and
+    another for transmitting data. While the command connection is
+    always initiated by the client, the second connection can be
+    initiated by either the client or the server.
+
+    This enum defines whether the client (Passive mode) or the server
+    (Active mode) should set up the data connection.
+
+    \value Passive The client connects to the server to transmit its
+    data.
+
+    \value Active The server connects to the client to transmit its
+    data.
+*/
+/*!
+    \internal
+    \enum QFtp::TransferType
+
+    This enum identifies the data transfer type used with get and
+    put commands.
+
+    \value Binary The data will be transferred in Binary mode.
+
+    \value Ascii The data will be transferred in Ascii mode and new line
+    characters will be converted to the local format.
+*/
+/*!
+    \internal
+    \enum QFtp::Error
+
+    This enum identifies the error that occurred.
+
+    \value NoError No error occurred.
+    \value HostNotFound The host name lookup failed.
+    \value ConnectionRefused The server refused the connection.
+    \value NotConnected Tried to send a command, but there is no connection to
+    a server.
+    \value UnknownError An error other than those specified above
+    occurred.
+
+    \sa error()
+*/
+
+/*!
+    \internal
+    \enum QFtp::Command
+
+    This enum is used as the return value for the currentCommand() function.
+    This allows you to perform specific actions for particular
+    commands, e.g. in a FTP client, you might want to clear the
+    directory view when a list() command is started; in this case you
+    can simply check in the slot connected to the start() signal if
+    the currentCommand() is \c List.
+
+    \value None No command is being executed.
+    \value SetTransferMode set the \l{TransferMode}{transfer} mode.
+    \value SetProxy switch proxying on or off.
+    \value ConnectToHost connectToHost() is being executed.
+    \value Login login() is being executed.
+    \value Close close() is being executed.
+    \value List list() is being executed.
+    \value Cd cd() is being executed.
+    \value Get get() is being executed.
+    \value Put put() is being executed.
+    \value Remove remove() is being executed.
+    \value Mkdir mkdir() is being executed.
+    \value Rmdir rmdir() is being executed.
+    \value Rename rename() is being executed.
+    \value RawCommand rawCommand() is being executed.
+
+    \sa currentCommand()
+*/
+
+/*!
+    \internal
+    \fn void QFtp::stateChanged(int state)
+
+    This signal is emitted when the state of the connection changes.
+    The argument \a state is the new state of the connection; it is
+    one of the \l State values.
+
+    It is usually emitted in response to a connectToHost() or close()
+    command, but it can also be emitted "spontaneously", e.g. when the
+    server closes the connection unexpectedly.
+
+    \sa connectToHost(), close(), state(), State
+*/
+
+/*!
+    \internal
+    \fn void QFtp::listInfo(const QUrlInfo &i);
+
+    This signal is emitted for each directory entry the list() command
+    finds. The details of the entry are stored in \a i.
+
+    \sa list()
+*/
+
+/*!
+    \internal
+    \fn void QFtp::commandStarted(int id)
+
+    This signal is emitted when processing the command identified by
+    \a id starts.
+
+    \sa commandFinished(), done()
+*/
+
+/*!
+    \internal
+    \fn void QFtp::commandFinished(int id, bool error)
+
+    This signal is emitted when processing the command identified by
+    \a id has finished. \a error is true if an error occurred during
+    the processing; otherwise \a error is false.
+
+    \sa commandStarted(), done(), error(), errorString()
+*/
+
+/*!
+    \internal
+    \fn void QFtp::done(bool error)
+
+    This signal is emitted when the last pending command has finished;
+    (it is emitted after the last command's commandFinished() signal).
+    \a error is true if an error occurred during the processing;
+    otherwise \a error is false.
+
+    \sa commandFinished(), error(), errorString()
+*/
+
+/*!
+    \internal
+    \fn void QFtp::readyRead()
+
+    This signal is emitted in response to a get() command when there
+    is new data to read.
+
+    If you specify a device as the second argument in the get()
+    command, this signal is \e not emitted; instead the data is
+    written directly to the device.
+
+    You can read the data with the readAll() or read() functions.
+
+    This signal is useful if you want to process the data in chunks as
+    soon as it becomes available. If you are only interested in the
+    complete data, just connect to the commandFinished() signal and
+    read the data then instead.
+
+    \sa get(), read(), readAll(), bytesAvailable()
+*/
+
+/*!
+    \internal
+    \fn void QFtp::dataTransferProgress(qint64 done, qint64 total)
+
+    This signal is emitted in response to a get() or put() request to
+    indicate the current progress of the download or upload.
+
+    \a done is the amount of data that has already been transferred
+    and \a total is the total amount of data to be read or written. It
+    is possible that the QFtp class is not able to determine the total
+    amount of data that should be transferred, in which case \a total
+    is 0. (If you connect this signal to a QProgressBar, the progress
+    bar shows a busy indicator if the total is 0).
+
+    \warning \a done and \a total are not necessarily the size in
+    bytes, since for large files these values might need to be
+    "scaled" to avoid overflow.
+
+    \sa get(), put(), QProgressBar
+*/
+
+/*!
+    \internal
+    \fn void QFtp::rawCommandReply(int replyCode, const QString &detail);
+
+    This signal is emitted in response to the rawCommand() function.
+    \a replyCode is the 3 digit reply code and \a detail is the text
+    that follows the reply code.
+
+    \sa rawCommand()
+*/
+
+/*!
+    \internal
+    Connects to the FTP server \a host using port \a port.
+
+    The stateChanged() signal is emitted when the state of the
+    connecting process changes, e.g. to \c HostLookup, then \c
+    Connecting, then \c Connected.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa stateChanged(), commandStarted(), commandFinished()
+*/
+int QFtp::connectToHost(const QString &host, quint16 port)
+{
+    QStringList cmds;
+    cmds << host;
+    cmds << QString::number((uint)port);
+    int id = d->addCommand(new QFtpCommand(ConnectToHost, cmds));
+    d->pi.transferConnectionExtended = true;
+    return id;
+}
+
+/*!
+    \internal
+    Logs in to the FTP server with the username \a user and the
+    password \a password.
+
+    The stateChanged() signal is emitted when the state of the
+    connecting process changes, e.g. to \c LoggedIn.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa commandStarted(), commandFinished()
+*/
+int QFtp::login(const QString &user, const QString &password)
+{
+    QStringList cmds;
+
+    if (user.isNull() || user.compare(QLatin1String("anonymous"), Qt::CaseInsensitive) == 0) {
+        cmds << (QLatin1String("USER ") + (user.isNull() ? QLatin1String("anonymous") : user) + QLatin1String("\r\n"));
+        cmds << (QLatin1String("PASS ") + (password.isNull() ? QLatin1String("anonymous@") : password) + QLatin1String("\r\n"));
+    } else {
+        cmds << (QLatin1String("USER ") + user + QLatin1String("\r\n"));
+        if (!password.isNull())
+            cmds << (QLatin1String("PASS ") + password + QLatin1String("\r\n"));
+    }
+
+    return d->addCommand(new QFtpCommand(Login, cmds));
+}
+
+/*!
+    \internal
+    Closes the connection to the FTP server.
+
+    The stateChanged() signal is emitted when the state of the
+    connecting process changes, e.g. to \c Closing, then \c
+    Unconnected.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa stateChanged(), commandStarted(), commandFinished()
+*/
+int QFtp::close()
+{
+    return d->addCommand(new QFtpCommand(Close, QStringList(QLatin1String("QUIT\r\n"))));
+}
+
+/*!
+    \internal
+    Sets the current FTP transfer mode to \a mode. The default is QFtp::Passive.
+
+    \sa QFtp::TransferMode
+*/
+int QFtp::setTransferMode(TransferMode mode)
+{
+    int id = d->addCommand(new QFtpCommand(SetTransferMode, QStringList()));
+    d->pi.transferConnectionExtended = true;
+    d->transferMode = mode;
+    return id;
+}
+
+/*!
+    \internal
+    Enables use of the FTP proxy on host \a host and port \a
+    port. Calling this function with \a host empty disables proxying.
+
+    QFtp does not support FTP-over-HTTP proxy servers. Use
+    QNetworkAccessManager for this.
+*/
+int QFtp::setProxy(const QString &host, quint16 port)
+{
+    QStringList args;
+    args << host << QString::number(port);
+    return d->addCommand(new QFtpCommand(SetProxy, args));
+}
+
+/*!
+    \internal
+    Lists the contents of directory \a dir on the FTP server. If \a
+    dir is empty, it lists the contents of the current directory.
+
+    The listInfo() signal is emitted for each directory entry found.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa listInfo(), commandStarted(), commandFinished()
+*/
+int QFtp::list(const QString &dir)
+{
+    QStringList cmds;
+    cmds << QLatin1String("TYPE A\r\n");
+    cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
+    if (dir.isEmpty())
+        cmds << QLatin1String("LIST\r\n");
+    else
+        cmds << (QLatin1String("LIST ") + dir + QLatin1String("\r\n"));
+    return d->addCommand(new QFtpCommand(List, cmds));
+}
+
+/*!
+    \internal
+    Changes the working directory of the server to \a dir.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa commandStarted(), commandFinished()
+*/
+int QFtp::cd(const QString &dir)
+{
+    return d->addCommand(new QFtpCommand(Cd, QStringList(QLatin1String("CWD ") + dir + QLatin1String("\r\n"))));
+}
+
+/*!
+    \internal
+    Downloads the file \a file from the server.
+
+    If \a dev is \nullptr, then the readyRead() signal is emitted when there
+    is data available to read. You can then read the data with the
+    read() or readAll() functions.
+
+    If \a dev is not \nullptr, the data is written directly to the device
+    \a dev. Make sure that the \a dev pointer is valid for the duration
+    of the operation (it is safe to delete it when the
+    commandFinished() signal is emitted). In this case the readyRead()
+    signal is \e not emitted and you cannot read data with the
+    read() or readAll() functions.
+
+    If you don't read the data immediately it becomes available, i.e.
+    when the readyRead() signal is emitted, it is still available
+    until the next command is started.
+
+    For example, if you want to present the data to the user as soon
+    as there is something available, connect to the readyRead() signal
+    and read the data immediately. On the other hand, if you only want
+    to work with the complete data, you can connect to the
+    commandFinished() signal and read the data when the get() command
+    is finished.
+
+    The data is transferred as Binary or Ascii depending on the value
+    of \a type.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa readyRead(), dataTransferProgress(), commandStarted(),
+    commandFinished()
+*/
+int QFtp::get(const QString &file, QIODevice *dev, TransferType type)
+{
+    QStringList cmds;
+    if (type == Binary)
+        cmds << QLatin1String("TYPE I\r\n");
+    else
+        cmds << QLatin1String("TYPE A\r\n");
+    cmds << QLatin1String("SIZE ") + file + QLatin1String("\r\n");
+    cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
+    cmds << QLatin1String("RETR ") + file + QLatin1String("\r\n");
+    return d->addCommand(new QFtpCommand(Get, cmds, dev));
+}
+
+/*!
+    \internal
+    \overload
+
+    Writes a copy of the given \a data to the file called \a file on
+    the server. The progress of the upload is reported by the
+    dataTransferProgress() signal.
+
+    The data is transferred as Binary or Ascii depending on the value
+    of \a type.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    Since this function takes a copy of the \a data, you can discard
+    your own copy when this function returns.
+
+    \sa dataTransferProgress(), commandStarted(), commandFinished()
+*/
+int QFtp::put(const QByteArray &data, const QString &file, TransferType type)
+{
+    QStringList cmds;
+    if (type == Binary)
+        cmds << QLatin1String("TYPE I\r\n");
+    else
+        cmds << QLatin1String("TYPE A\r\n");
+    cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
+    cmds << QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n");
+    cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");
+    return d->addCommand(new QFtpCommand(Put, cmds, data));
+}
+
+/*!
+    \internal
+    Reads the data from the IO device \a dev, and writes it to the
+    file called \a file on the server. The data is read in chunks from
+    the IO device, so this overload allows you to transmit large
+    amounts of data without the need to read all the data into memory
+    at once.
+
+    The data is transferred as Binary or Ascii depending on the value
+    of \a type.
+
+    Make sure that the \a dev pointer is valid for the duration of the
+    operation (it is safe to delete it when the commandFinished() is
+    emitted).
+*/
+int QFtp::put(QIODevice *dev, const QString &file, TransferType type)
+{
+    QStringList cmds;
+    if (type == Binary)
+        cmds << QLatin1String("TYPE I\r\n");
+    else
+        cmds << QLatin1String("TYPE A\r\n");
+    cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n");
+    if (!dev->isSequential())
+        cmds << QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n");
+    cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");
+    return d->addCommand(new QFtpCommand(Put, cmds, dev));
+}
+
+/*!
+    \internal
+    Deletes the file called \a file from the server.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa commandStarted(), commandFinished()
+*/
+int QFtp::remove(const QString &file)
+{
+    return d->addCommand(new QFtpCommand(Remove, QStringList(QLatin1String("DELE ") + file + QLatin1String("\r\n"))));
+}
+
+/*!
+    \internal
+    Creates a directory called \a dir on the server.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa commandStarted(), commandFinished()
+*/
+int QFtp::mkdir(const QString &dir)
+{
+    return d->addCommand(new QFtpCommand(Mkdir, QStringList(QLatin1String("MKD ") + dir + QLatin1String("\r\n"))));
+}
+
+/*!
+    \internal
+    Removes the directory called \a dir from the server.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa commandStarted(), commandFinished()
+*/
+int QFtp::rmdir(const QString &dir)
+{
+    return d->addCommand(new QFtpCommand(Rmdir, QStringList(QLatin1String("RMD ") + dir + QLatin1String("\r\n"))));
+}
+
+/*!
+    \internal
+    Renames the file called \a oldname to \a newname on the server.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa commandStarted(), commandFinished()
+*/
+int QFtp::rename(const QString &oldname, const QString &newname)
+{
+    QStringList cmds;
+    cmds << QLatin1String("RNFR ") + oldname + QLatin1String("\r\n");
+    cmds << QLatin1String("RNTO ") + newname + QLatin1String("\r\n");
+    return d->addCommand(new QFtpCommand(Rename, cmds));
+}
+
+/*!
+    \internal
+    Sends the raw FTP command \a command to the FTP server. This is
+    useful for low-level FTP access. If the operation you wish to
+    perform has an equivalent QFtp function, we recommend using the
+    function instead of raw FTP commands since the functions are
+    easier and safer.
+
+    The function does not block and returns immediately. The command
+    is scheduled, and its execution is performed asynchronously. The
+    function returns a unique identifier which is passed by
+    commandStarted() and commandFinished().
+
+    When the command is started the commandStarted() signal is
+    emitted. When it is finished the commandFinished() signal is
+    emitted.
+
+    \sa rawCommandReply(), commandStarted(), commandFinished()
+*/
+int QFtp::rawCommand(const QString &command)
+{
+    const QString cmd = command.trimmed() + QLatin1String("\r\n");
+    return d->addCommand(new QFtpCommand(RawCommand, QStringList(cmd)));
+}
+
+/*!
+    \internal
+    Returns the number of bytes that can be read from the data socket
+    at the moment.
+
+    \sa get(), readyRead(), read(), readAll()
+*/
+qint64 QFtp::bytesAvailable() const
+{
+    return d->pi.dtp.bytesAvailable();
+}
+
+/*!
+    \internal
+    Reads \a maxlen bytes from the data socket into \a data and
+    returns the number of bytes read. Returns -1 if an error occurred.
+
+    \sa get(), readyRead(), bytesAvailable(), readAll()
+*/
+qint64 QFtp::read(char *data, qint64 maxlen)
+{
+    return d->pi.dtp.read(data, maxlen);
+}
+
+/*!
+    \internal
+    Reads all the bytes available from the data socket and returns
+    them.
+
+    \sa get(), readyRead(), bytesAvailable(), read()
+*/
+QByteArray QFtp::readAll()
+{
+    return d->pi.dtp.readAll();
+}
+
+/*!
+    \internal
+    Aborts the current command and deletes all scheduled commands.
+
+    If there is an unfinished command (i.e. a command for which the
+    commandStarted() signal has been emitted, but for which the
+    commandFinished() signal has not been emitted), this function
+    sends an \c ABORT command to the server. When the server replies
+    that the command is aborted, the commandFinished() signal with the
+    \c error argument set to \c true is emitted for the command. Due
+    to timing issues, it is possible that the command had already
+    finished before the abort request reached the server, in which
+    case, the commandFinished() signal is emitted with the \c error
+    argument set to \c false.
+
+    For all other commands that are affected by the abort(), no
+    signals are emitted.
+
+    If you don't start further FTP commands directly after the
+    abort(), there won't be any scheduled commands and the done()
+    signal is emitted.
+
+    \warning Some FTP servers, for example the BSD FTP daemon (version
+    0.3), wrongly return a positive reply even when an abort has
+    occurred. For these servers the commandFinished() signal has its
+    error flag set to \c false, even though the command did not
+    complete successfully.
+
+    \sa clearPendingCommands()
+*/
+void QFtp::abort()
+{
+    if (d->pending.isEmpty())
+        return;
+
+    clearPendingCommands();
+    d->pi.abort();
+}
+
+/*!
+    \internal
+    Clears the last error.
+
+    \sa currentCommand()
+*/
+void QFtp::clearError()
+{
+    d->error = NoError;
+}
+
+/*!
+    \internal
+    Returns the identifier of the FTP command that is being executed
+    or 0 if there is no command being executed.
+
+    \sa currentCommand()
+*/
+int QFtp::currentId() const
+{
+    if (d->pending.isEmpty())
+        return 0;
+    return d->pending.first()->id;
+}
+
+/*!
+    \internal
+    Returns the command type of the FTP command being executed or \c
+    None if there is no command being executed.
+
+    \sa currentId()
+*/
+QFtp::Command QFtp::currentCommand() const
+{
+    if (d->pending.isEmpty())
+        return None;
+    return d->pending.first()->command;
+}
+
+/*!
+    \internal
+    Returns the QIODevice pointer that is used by the FTP command to read data
+    from or store data to. If there is no current FTP command being executed or
+    if the command does not use an IO device, this function returns \nullptr.
+
+    This function can be used to delete the QIODevice in the slot connected to
+    the commandFinished() signal.
+
+    \sa get(), put()
+*/
+QIODevice* QFtp::currentDevice() const
+{
+    if (d->pending.isEmpty())
+        return nullptr;
+    QFtpCommand *c = d->pending.first();
+    if (c->is_ba)
+        return nullptr;
+    return c->data.dev;
+}
+
+/*!
+    \internal
+    Returns \c true if there are any commands scheduled that have not yet
+    been executed; otherwise returns \c false.
+
+    The command that is being executed is \e not considered as a
+    scheduled command.
+
+    \sa clearPendingCommands(), currentId(), currentCommand()
+*/
+bool QFtp::hasPendingCommands() const
+{
+    return d->pending.count() > 1;
+}
+
+/*!
+    \internal
+    Deletes all pending commands from the list of scheduled commands.
+    This does not affect the command that is being executed. If you
+    want to stop this as well, use abort().
+
+    \sa hasPendingCommands(), abort()
+*/
+void QFtp::clearPendingCommands()
+{
+    // delete all entires except the first one
+    while (d->pending.count() > 1)
+        delete d->pending.takeLast();
+}
+
+/*!
+    \internal
+    Returns the current state of the object. When the state changes,
+    the stateChanged() signal is emitted.
+
+    \sa State, stateChanged()
+*/
+QFtp::State QFtp::state() const
+{
+    return d->state;
+}
+
+/*!
+    \internal
+    Returns the last error that occurred. This is useful to find out
+    what went wrong when receiving a commandFinished() or a done()
+    signal with the \c error argument set to \c true.
+
+    If you start a new command, the error status is reset to \c NoError.
+*/
+QFtp::Error QFtp::error() const
+{
+    return d->error;
+}
+
+/*!
+    \internal
+    Returns a human-readable description of the last error that
+    occurred. This is useful for presenting a error message to the
+    user when receiving a commandFinished() or a done() signal with
+    the \c error argument set to \c true.
+
+    The error string is often (but not always) the reply from the
+    server, so it is not always possible to translate the string. If
+    the message comes from Qt, the string has already passed through
+    tr().
+*/
+QString QFtp::errorString() const
+{
+    return d->errorString;
+}
+
+/*! \internal
+*/
+void QFtpPrivate::_q_startNextCommand()
+{
+    Q_Q(QFtp);
+    if (pending.isEmpty())
+        return;
+    QFtpCommand *c = pending.constFirst();
+
+    error = QFtp::NoError;
+    errorString = QT_TRANSLATE_NOOP(QFtp, QLatin1String("Unknown error"));
+
+    if (q->bytesAvailable())
+        q->readAll(); // clear the data
+    emit q->commandStarted(c->id);
+
+    // Proxy support, replace the Login argument in place, then fall
+    // through.
+    if (c->command == QFtp::Login && !proxyHost.isEmpty()) {
+        QString loginString = c->rawCmds.first().trimmed();
+        loginString += QLatin1Char('@') + host;
+        if (port && port != 21)
+            loginString += QLatin1Char(':') + QString::number(port);
+        loginString += QLatin1String("\r\n");
+        c->rawCmds[0] = loginString;
+    }
+
+    if (c->command == QFtp::SetTransferMode) {
+        _q_piFinished(QLatin1String("Transfer mode set"));
+    } else if (c->command == QFtp::SetProxy) {
+        proxyHost = c->rawCmds.at(0);
+        proxyPort = c->rawCmds.at(1).toUInt();
+        c->rawCmds.clear();
+        _q_piFinished(QLatin1String("Proxy set to ") + proxyHost + QLatin1Char(':') + QString::number(proxyPort));
+    } else if (c->command == QFtp::ConnectToHost) {
+#ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section
+        //copy network session down to the PI
+        pi.setProperty("_q_networksession", q->property("_q_networksession"));
+#endif
+        if (!proxyHost.isEmpty()) {
+            host = c->rawCmds.at(0);
+            port = c->rawCmds.at(1).toUInt();
+            pi.connectToHost(proxyHost, proxyPort);
+        } else {
+            pi.connectToHost(c->rawCmds.at(0), c->rawCmds.at(1).toUInt());
+        }
+    } else {
+        if (c->command == QFtp::Put) {
+            if (c->is_ba) {
+                pi.dtp.setData(c->data.ba);
+                pi.dtp.setBytesTotal(c->data.ba->size());
+            } else if (c->data.dev && (c->data.dev->isOpen() || c->data.dev->open(QIODevice::ReadOnly))) {
+                pi.dtp.setDevice(c->data.dev);
+                if (c->data.dev->isSequential()) {
+                    pi.dtp.setBytesTotal(0);
+                    pi.dtp.connect(c->data.dev, SIGNAL(readyRead()), SLOT(dataReadyRead()));
+                    pi.dtp.connect(c->data.dev, SIGNAL(readChannelFinished()), SLOT(dataReadyRead()));
+                } else {
+                    pi.dtp.setBytesTotal(c->data.dev->size());
+                }
+            }
+        } else if (c->command == QFtp::Get) {
+            if (!c->is_ba && c->data.dev) {
+                pi.dtp.setDevice(c->data.dev);
+            }
+        } else if (c->command == QFtp::Close) {
+            state = QFtp::Closing;
+            emit q->stateChanged(state);
+        }
+        pi.sendCommands(c->rawCmds);
+    }
+}
+
+/*! \internal
+*/
+void QFtpPrivate::_q_piFinished(const QString&)
+{
+    if (pending.isEmpty())
+        return;
+    QFtpCommand *c = pending.constFirst();
+
+    if (c->command == QFtp::Close) {
+        // The order of in which the slots are called is arbitrary, so
+        // disconnect the SIGNAL-SIGNAL temporary to make sure that we
+        // don't get the commandFinished() signal before the stateChanged()
+        // signal.
+        if (state != QFtp::Unconnected) {
+            close_waitForStateChange = true;
+            return;
+        }
+    }
+    emit q_func()->commandFinished(c->id, false);
+    pending.removeFirst();
+
+    delete c;
+
+    if (pending.isEmpty()) {
+        emit q_func()->done(false);
+    } else {
+        _q_startNextCommand();
+    }
+}
+
+/*! \internal
+*/
+void QFtpPrivate::_q_piError(int errorCode, const QString &text)
+{
+    Q_Q(QFtp);
+
+    if (pending.isEmpty()) {
+        qWarning("QFtpPrivate::_q_piError was called without pending command!");
+        return;
+    }
+
+    QFtpCommand *c = pending.constFirst();
+
+    // non-fatal errors
+    if (c->command == QFtp::Get && pi.currentCommand().startsWith(QLatin1String("SIZE "))) {
+        pi.dtp.setBytesTotal(0);
+        return;
+    } else if (c->command==QFtp::Put && pi.currentCommand().startsWith(QLatin1String("ALLO "))) {
+        return;
+    }
+
+    error = QFtp::Error(errorCode);
+    switch (q->currentCommand()) {
+        case QFtp::ConnectToHost:
+            errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Connecting to host failed:\n%1"))
+                          .arg(text);
+            break;
+        case QFtp::Login:
+            errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Login failed:\n%1"))
+                          .arg(text);
+            break;
+        case QFtp::List:
+            errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Listing directory failed:\n%1"))
+                          .arg(text);
+            break;
+        case QFtp::Cd:
+            errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Changing directory failed:\n%1"))
+                          .arg(text);
+            break;
+        case QFtp::Get:
+            errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Downloading file failed:\n%1"))
+                          .arg(text);
+            break;
+        case QFtp::Put:
+            errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Uploading file failed:\n%1"))
+                          .arg(text);
+            break;
+        case QFtp::Remove:
+            errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing file failed:\n%1"))
+                          .arg(text);
+            break;
+        case QFtp::Mkdir:
+            errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Creating directory failed:\n%1"))
+                          .arg(text);
+            break;
+        case QFtp::Rmdir:
+            errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing directory failed:\n%1"))
+                          .arg(text);
+            break;
+        default:
+            errorString = text;
+            break;
+    }
+
+    pi.clearPendingCommands();
+    q->clearPendingCommands();
+    emit q->commandFinished(c->id, true);
+
+    pending.removeFirst();
+    delete c;
+    if (pending.isEmpty())
+        emit q->done(true);
+    else
+        _q_startNextCommand();
+}
+
+/*! \internal
+*/
+void QFtpPrivate::_q_piConnectState(int connectState)
+{
+    state = QFtp::State(connectState);
+    emit q_func()->stateChanged(state);
+    if (close_waitForStateChange) {
+        close_waitForStateChange = false;
+        _q_piFinished(QLatin1String(QT_TRANSLATE_NOOP("QFtp", "Connection closed")));
+    }
+}
+
+/*! \internal
+*/
+void QFtpPrivate::_q_piFtpReply(int code, const QString &text)
+{
+    if (q_func()->currentCommand() == QFtp::RawCommand) {
+        pi.rawCommand = true;
+        emit q_func()->rawCommandReply(code, text);
+    }
+}
+
+/*!
+    \internal
+    Destructor.
+*/
+QFtp::~QFtp()
+{
+    abort();
+    close();
+}
+
+QT_END_NAMESPACE
+
+#include "qftp.moc"
+
+#include "moc_qftp.cpp"

+ 178 - 0
common/QFtp/qftp.h

@@ -0,0 +1,178 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API.  It exists for the convenience
+// of the Network Access API.  This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QFTP_P_H
+#define QFTP_P_H
+
+#include <QtCore/qstring.h>
+#include <QtCore/qobject.h>
+#include <QFile>
+
+#include "qurlinfo.h"
+//QT_REQUIRE_CONFIG(ftp);
+
+QT_BEGIN_NAMESPACE
+
+class QFtpPrivate;
+
+class QFtp : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit QFtp(QObject *parent = nullptr);
+    virtual ~QFtp();
+
+    enum State {
+        Unconnected,
+        HostLookup,
+        Connecting,
+        Connected,
+        LoggedIn,
+        Closing
+    };
+    enum Error {
+        NoError,
+        UnknownError,
+        HostNotFound,
+        ConnectionRefused,
+        NotConnected
+    };
+    enum Command {
+        None,
+        SetTransferMode,
+        SetProxy,
+        ConnectToHost,
+        Login,
+        Close,
+        List,
+        Cd,
+        Get,
+        Put,
+        Remove,
+        Mkdir,
+        Rmdir,
+        Rename,
+        RawCommand
+    };
+    enum TransferMode {
+        Active,
+        Passive
+    };
+    enum TransferType {
+        Binary,
+        Ascii
+    };
+
+    int setProxy(const QString &host, quint16 port);
+    int connectToHost(const QString &host, quint16 port=21);
+    int login(const QString &user = QString(), const QString &password = QString());
+    int close();
+    int setTransferMode(TransferMode mode);
+    int list(const QString &dir = QString());
+    int cd(const QString &dir);
+    int get(const QString &file, QIODevice *dev=nullptr, TransferType type = Binary);
+    int put(const QByteArray &data, const QString &file, TransferType type = Binary);
+    int put(QIODevice *dev, const QString &file, TransferType type = Binary);
+    int remove(const QString &file);
+    int mkdir(const QString &dir);
+    int rmdir(const QString &dir);
+    int rename(const QString &oldname, const QString &newname);
+
+    int rawCommand(const QString &command);
+
+    qint64 bytesAvailable() const;
+    qint64 read(char *data, qint64 maxlen);
+    QByteArray readAll();
+
+    int currentId() const;
+    QIODevice* currentDevice() const;
+    Command currentCommand() const;
+    bool hasPendingCommands() const;
+    void clearPendingCommands();
+
+    State state() const;
+
+    Error error() const;
+    QString errorString() const;
+
+public Q_SLOTS:
+    void abort();
+
+Q_SIGNALS:
+    void stateChanged(int);
+    void listInfo(const QUrlInfo&);
+    void readyRead();
+    void dataTransferProgress(qint64, qint64);
+    void rawCommandReply(int, const QString&);
+
+    void commandStarted(int);
+    void commandFinished(int, bool);
+    void done(bool);
+
+protected:
+    void clearError();
+
+private:
+//    Q_DISABLE_COPY_MOVE(QFtp)
+    Q_DISABLE_COPY(QFtp)
+    QScopedPointer<QFtpPrivate> d;
+//    Q_DECLARE_PRIVATE(QFtp)
+
+    Q_PRIVATE_SLOT(d, void _q_startNextCommand())
+    Q_PRIVATE_SLOT(d, void _q_piFinished(const QString&))
+    Q_PRIVATE_SLOT(d, void _q_piError(int, const QString&))
+    Q_PRIVATE_SLOT(d, void _q_piConnectState(int))
+    Q_PRIVATE_SLOT(d, void _q_piFtpReply(int, const QString&))
+};
+
+QT_END_NAMESPACE
+
+#endif // QFTP_P_H

+ 727 - 0
common/QFtp/qurlinfo.cpp

@@ -0,0 +1,727 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qurlinfo.h"
+
+#include "qurl.h"
+#include "qdir.h"
+#include <limits.h>
+
+QT_BEGIN_NAMESPACE
+
+class QUrlInfoPrivate
+{
+public:
+    QUrlInfoPrivate() :
+        permissions(0),
+        size(0),
+        isDir(false),
+        isFile(true),
+        isSymLink(false),
+        isWritable(true),
+        isReadable(true),
+        isExecutable(false)
+    {}
+
+    QString name;
+    int permissions;
+    QString owner;
+    QString group;
+    qint64 size;
+
+    QDateTime lastModified;
+    QDateTime lastRead;
+    bool isDir;
+    bool isFile;
+    bool isSymLink;
+    bool isWritable;
+    bool isReadable;
+    bool isExecutable;
+};
+
+
+/*!
+    \class QUrlInfo
+    \brief The QUrlInfo class stores information about URLs.
+
+    \internal
+    \ingroup io
+    \ingroup network
+    \inmodule QtNetwork
+
+    The information about a URL that can be retrieved includes name(),
+    permissions(), owner(), group(), size(), lastModified(),
+    lastRead(), isDir(), isFile(), isSymLink(), isWritable(),
+    isReadable() and isExecutable().
+
+    You can create your own QUrlInfo objects passing in all the
+    relevant information in the constructor, and you can modify a
+    QUrlInfo; for each getter mentioned above there is an equivalent
+    setter. Note that setting values does not affect the underlying
+    resource that the QUrlInfo provides information about; for example
+    if you call setWritable(true) on a read-only resource the only
+    thing changed is the QUrlInfo object, not the resource.
+
+    \sa QUrl, {FTP Example}
+*/
+
+/*!
+    \enum QUrlInfo::PermissionSpec
+
+    This enum is used by the permissions() function to report the
+    permissions of a file.
+
+    \value ReadOwner The file is readable by the owner of the file.
+    \value WriteOwner The file is writable by the owner of the file.
+    \value ExeOwner The file is executable by the owner of the file.
+    \value ReadGroup The file is readable by the group.
+    \value WriteGroup The file is writable by the group.
+    \value ExeGroup The file is executable by the group.
+    \value ReadOther The file is readable by anyone.
+    \value WriteOther The file is writable by anyone.
+    \value ExeOther The file is executable by anyone.
+*/
+
+/*!
+    Constructs an invalid QUrlInfo object with default values.
+
+    \sa isValid()
+*/
+
+QUrlInfo::QUrlInfo()
+{
+    d = nullptr;
+}
+
+/*!
+    Copy constructor, copies \a ui to this URL info object.
+*/
+
+QUrlInfo::QUrlInfo(const QUrlInfo &ui)
+{
+    if (ui.d) {
+        d = new QUrlInfoPrivate;
+        *d = *ui.d;
+    } else {
+        d = nullptr;
+    }
+}
+
+/*!
+    Constructs a QUrlInfo object by specifying all the URL's
+    information.
+
+    The information that is passed is the \a name, file \a
+    permissions, \a owner and \a group and the file's \a size. Also
+    passed is the \a lastModified date/time and the \a lastRead
+    date/time. Flags are also passed, specifically, \a isDir, \a
+    isFile, \a isSymLink, \a isWritable, \a isReadable and \a
+    isExecutable.
+*/
+
+QUrlInfo::QUrlInfo(const QString &name, int permissions, const QString &owner,
+                    const QString &group, qint64 size, const QDateTime &lastModified,
+                    const QDateTime &lastRead, bool isDir, bool isFile, bool isSymLink,
+                    bool isWritable, bool isReadable, bool isExecutable)
+{
+    d = new QUrlInfoPrivate;
+    d->name = name;
+    d->permissions = permissions;
+    d->owner = owner;
+    d->group = group;
+    d->size = size;
+    d->lastModified = lastModified;
+    d->lastRead = lastRead;
+    d->isDir = isDir;
+    d->isFile = isFile;
+    d->isSymLink = isSymLink;
+    d->isWritable = isWritable;
+    d->isReadable = isReadable;
+    d->isExecutable = isExecutable;
+}
+
+
+/*!
+    Constructs a QUrlInfo object by specifying all the URL's
+    information.
+
+    The information that is passed is the \a url, file \a
+    permissions, \a owner and \a group and the file's \a size. Also
+    passed is the \a lastModified date/time and the \a lastRead
+    date/time. Flags are also passed, specifically, \a isDir, \a
+    isFile, \a isSymLink, \a isWritable, \a isReadable and \a
+    isExecutable.
+*/
+
+QUrlInfo::QUrlInfo(const QUrl &url, int permissions, const QString &owner,
+                    const QString &group, qint64 size, const QDateTime &lastModified,
+                    const QDateTime &lastRead, bool isDir, bool isFile, bool isSymLink,
+                    bool isWritable, bool isReadable, bool isExecutable)
+{
+    d = new QUrlInfoPrivate;
+    d->name = QFileInfo(url.path()).fileName();
+    d->permissions = permissions;
+    d->owner = owner;
+    d->group = group;
+    d->size = size;
+    d->lastModified = lastModified;
+    d->lastRead = lastRead;
+    d->isDir = isDir;
+    d->isFile = isFile;
+    d->isSymLink = isSymLink;
+    d->isWritable = isWritable;
+    d->isReadable = isReadable;
+    d->isExecutable = isExecutable;
+}
+
+
+/*!
+    Sets the name of the URL to \a name. The name is the full text,
+    for example, "http://qt-project.org/doc/qt-5.0/qtcore/qurl.html".
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setName(const QString &name)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->name = name;
+}
+
+
+/*!
+    If \a b is true then the URL is set to be a directory; if \a b is
+    false then the URL is set not to be a directory (which normally
+    means it is a file). (Note that a URL can refer to both a file and
+    a directory even though most file systems do not support this.)
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setDir(bool b)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->isDir = b;
+}
+
+
+/*!
+    If \a b is true then the URL is set to be a file; if \b is false
+    then the URL is set not to be a file (which normally means it is a
+    directory). (Note that a URL can refer to both a file and a
+    directory even though most file systems do not support this.)
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setFile(bool b)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->isFile = b;
+}
+
+
+/*!
+    Specifies that the URL refers to a symbolic link if \a b is true
+    and that it does not if \a b is false.
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setSymLink(bool b)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->isSymLink = b;
+}
+
+
+/*!
+    Specifies that the URL is writable if \a b is true and not
+    writable if \a b is false.
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setWritable(bool b)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->isWritable = b;
+}
+
+
+/*!
+    Specifies that the URL is readable if \a b is true and not
+    readable if \a b is false.
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setReadable(bool b)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->isReadable = b;
+}
+
+/*!
+    Specifies that the owner of the URL is called \a s.
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setOwner(const QString &s)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->owner = s;
+}
+
+/*!
+    Specifies that the owning group of the URL is called \a s.
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setGroup(const QString &s)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->group = s;
+}
+
+/*!
+    Specifies the \a size of the URL.
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setSize(qint64 size)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->size = size;
+}
+
+/*!
+    Specifies that the URL has access permissions \a p.
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setPermissions(int p)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->permissions = p;
+}
+
+/*!
+    Specifies that the object the URL refers to was last modified at
+    \a dt.
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setLastModified(const QDateTime &dt)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->lastModified = dt;
+}
+
+/*!
+    \since 4.4
+
+    Specifies that the object the URL refers to was last read at
+    \a dt.
+
+    If you call this function for an invalid URL info, this function
+    turns it into a valid one.
+
+    \sa isValid()
+*/
+
+void QUrlInfo::setLastRead(const QDateTime &dt)
+{
+    if (!d)
+        d = new QUrlInfoPrivate;
+    d->lastRead = dt;
+}
+
+/*!
+    Destroys the URL info object.
+*/
+
+QUrlInfo::~QUrlInfo()
+{
+    delete d;
+}
+
+/*!
+    Assigns the values of \a ui to this QUrlInfo object.
+*/
+
+QUrlInfo &QUrlInfo::operator=(const QUrlInfo &ui)
+{
+    if (ui.d) {
+        if (!d)
+            d= new QUrlInfoPrivate;
+        *d = *ui.d;
+    } else {
+        delete d;
+        d = nullptr;
+    }
+    return *this;
+}
+
+/*!
+    Returns the file name of the URL.
+
+    \sa isValid()
+*/
+
+QString QUrlInfo::name() const
+{
+    if (!d)
+        return QString();
+    return d->name;
+}
+
+/*!
+    Returns the permissions of the URL. You can use the \c PermissionSpec flags
+    to test for certain permissions.
+
+    \sa isValid()
+*/
+
+int QUrlInfo::permissions() const
+{
+    if (!d)
+        return 0;
+    return d->permissions;
+}
+
+/*!
+    Returns the owner of the URL.
+
+    \sa isValid()
+*/
+
+QString QUrlInfo::owner() const
+{
+    if (!d)
+        return QString();
+    return d->owner;
+}
+
+/*!
+    Returns the group of the URL.
+
+    \sa isValid()
+*/
+
+QString QUrlInfo::group() const
+{
+    if (!d)
+        return QString();
+    return d->group;
+}
+
+/*!
+    Returns the size of the URL.
+
+    \sa isValid()
+*/
+
+qint64 QUrlInfo::size() const
+{
+    if (!d)
+        return 0;
+    return d->size;
+}
+
+/*!
+    Returns the last modification date of the URL.
+
+    \sa isValid()
+*/
+
+QDateTime QUrlInfo::lastModified() const
+{
+    if (!d)
+        return QDateTime();
+    return d->lastModified;
+}
+
+/*!
+    Returns the date when the URL was last read.
+
+    \sa isValid()
+*/
+
+QDateTime QUrlInfo::lastRead() const
+{
+    if (!d)
+        return QDateTime();
+    return d->lastRead;
+}
+
+/*!
+    Returns \c true if the URL is a directory; otherwise returns \c false.
+
+    \sa isValid()
+*/
+
+bool QUrlInfo::isDir() const
+{
+    if (!d)
+        return false;
+    return d->isDir;
+}
+
+/*!
+    Returns \c true if the URL is a file; otherwise returns \c false.
+
+    \sa isValid()
+*/
+
+bool QUrlInfo::isFile() const
+{
+    if (!d)
+        return false;
+    return d->isFile;
+}
+
+/*!
+    Returns \c true if the URL is a symbolic link; otherwise returns \c false.
+
+    \sa isValid()
+*/
+
+bool QUrlInfo::isSymLink() const
+{
+    if (!d)
+        return false;
+    return d->isSymLink;
+}
+
+/*!
+    Returns \c true if the URL is writable; otherwise returns \c false.
+
+    \sa isValid()
+*/
+
+bool QUrlInfo::isWritable() const
+{
+    if (!d)
+        return false;
+    return d->isWritable;
+}
+
+/*!
+    Returns \c true if the URL is readable; otherwise returns \c false.
+
+    \sa isValid()
+*/
+
+bool QUrlInfo::isReadable() const
+{
+    if (!d)
+        return false;
+    return d->isReadable;
+}
+
+/*!
+    Returns \c true if the URL is executable; otherwise returns \c false.
+
+    \sa isValid()
+*/
+
+bool QUrlInfo::isExecutable() const
+{
+    if (!d)
+        return false;
+    return d->isExecutable;
+}
+
+/*!
+    Returns \c true if \a i1 is greater than \a i2; otherwise returns
+    false. The objects are compared by the value, which is specified
+    by \a sortBy. This must be one of QDir::Name, QDir::Time or
+    QDir::Size.
+*/
+
+bool QUrlInfo::greaterThan(const QUrlInfo &i1, const QUrlInfo &i2,
+                            int sortBy)
+{
+    switch (sortBy) {
+    case QDir::Name:
+        return i1.name() > i2.name();
+    case QDir::Time:
+        return i1.lastModified() > i2.lastModified();
+    case QDir::Size:
+        return i1.size() > i2.size();
+    default:
+        return false;
+    }
+}
+
+/*!
+    Returns \c true if \a i1 is less than \a i2; otherwise returns \c false.
+    The objects are compared by the value, which is specified by \a
+    sortBy. This must be one of QDir::Name, QDir::Time or QDir::Size.
+*/
+
+bool QUrlInfo::lessThan(const QUrlInfo &i1, const QUrlInfo &i2,
+                         int sortBy)
+{
+    return !greaterThan(i1, i2, sortBy);
+}
+
+/*!
+    Returns \c true if \a i1 equals to \a i2; otherwise returns \c false.
+    The objects are compared by the value, which is specified by \a
+    sortBy. This must be one of QDir::Name, QDir::Time or QDir::Size.
+*/
+
+bool QUrlInfo::equal(const QUrlInfo &i1, const QUrlInfo &i2,
+                      int sortBy)
+{
+    switch (sortBy) {
+    case QDir::Name:
+        return i1.name() == i2.name();
+    case QDir::Time:
+        return i1.lastModified() == i2.lastModified();
+    case QDir::Size:
+        return i1.size() == i2.size();
+    default:
+        return false;
+    }
+}
+
+/*!
+    Returns \c true if this QUrlInfo is equal to \a other; otherwise
+    returns \c false.
+
+    \sa lessThan(), equal()
+*/
+
+bool QUrlInfo::operator==(const QUrlInfo &other) const
+{
+    if (!d)
+        return other.d == nullptr;
+    if (!other.d)
+        return false;
+
+    return (d->name == other.d->name &&
+            d->permissions == other.d->permissions &&
+            d->owner == other.d->owner &&
+            d->group == other.d->group &&
+            d->size == other.d->size &&
+            d->lastModified == other.d->lastModified &&
+            d->lastRead == other.d->lastRead &&
+            d->isDir == other.d->isDir &&
+            d->isFile == other.d->isFile &&
+            d->isSymLink == other.d->isSymLink &&
+            d->isWritable == other.d->isWritable &&
+            d->isReadable == other.d->isReadable &&
+            d->isExecutable == other.d->isExecutable);
+}
+
+/*!
+    \fn bool QUrlInfo::operator!=(const QUrlInfo &other) const
+    \since 4.2
+
+    Returns \c true if this QUrlInfo is not equal to \a other; otherwise
+    returns \c false.
+
+    \sa lessThan(), equal()
+*/
+
+/*!
+    Returns \c true if the URL info is valid; otherwise returns \c false.
+    Valid means that the QUrlInfo contains real information.
+
+    You should always check if the URL info is valid before relying on
+    the values.
+*/
+bool QUrlInfo::isValid() const
+{
+    return d != nullptr;
+}
+
+QT_END_NAMESPACE

+ 132 - 0
common/QFtp/qurlinfo.h

@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QURLINFO_H
+#define QURLINFO_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API.  It exists purely as an
+// implementation detail.  This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qdatetime.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qiodevice.h>
+
+//QT_REQUIRE_CONFIG(ftp);
+
+QT_BEGIN_NAMESPACE
+
+class QUrl;
+class QUrlInfoPrivate;
+
+class QUrlInfo
+{
+public:
+    enum PermissionSpec {
+        ReadOwner = 00400, WriteOwner = 00200, ExeOwner = 00100,
+        ReadGroup = 00040, WriteGroup = 00020, ExeGroup = 00010,
+        ReadOther = 00004, WriteOther = 00002, ExeOther = 00001 };
+
+    QUrlInfo();
+    QUrlInfo(const QUrlInfo &ui);
+    QUrlInfo(const QString &name, int permissions, const QString &owner,
+             const QString &group, qint64 size, const QDateTime &lastModified,
+             const QDateTime &lastRead, bool isDir, bool isFile, bool isSymLink,
+             bool isWritable, bool isReadable, bool isExecutable);
+    QUrlInfo(const QUrl &url, int permissions, const QString &owner,
+             const QString &group, qint64 size, const QDateTime &lastModified,
+             const QDateTime &lastRead, bool isDir, bool isFile, bool isSymLink,
+             bool isWritable, bool isReadable, bool isExecutable);
+    QUrlInfo &operator=(const QUrlInfo &ui);
+    virtual ~QUrlInfo();
+
+    virtual void setName(const QString &name);
+    virtual void setDir(bool b);
+    virtual void setFile(bool b);
+    virtual void setSymLink(bool b);
+    virtual void setOwner(const QString &s);
+    virtual void setGroup(const QString &s);
+    virtual void setSize(qint64 size);
+    virtual void setWritable(bool b);
+    virtual void setReadable(bool b);
+    virtual void setPermissions(int p);
+    virtual void setLastModified(const QDateTime &dt);
+    void setLastRead(const QDateTime &dt);
+
+    bool isValid() const;
+
+    QString name() const;
+    int permissions() const;
+    QString owner() const;
+    QString group() const;
+    qint64 size() const;
+    QDateTime lastModified() const;
+    QDateTime lastRead() const;
+    bool isDir() const;
+    bool isFile() const;
+    bool isSymLink() const;
+    bool isWritable() const;
+    bool isReadable() const;
+    bool isExecutable() const;
+
+    static bool greaterThan(const QUrlInfo &i1, const QUrlInfo &i2,
+                             int sortBy);
+    static bool lessThan(const QUrlInfo &i1, const QUrlInfo &i2,
+                          int sortBy);
+    static bool equal(const QUrlInfo &i1, const QUrlInfo &i2,
+                       int sortBy);
+
+    bool operator==(const QUrlInfo &i) const;
+    inline bool operator!=(const QUrlInfo &i) const
+    { return !operator==(i); }
+
+private:
+    QUrlInfoPrivate *d;
+};
+
+QT_END_NAMESPACE
+
+#endif // QURLINFO_H

+ 36 - 0
common/Shadow/oneshadow.cpp

@@ -0,0 +1,36 @@
+#include "oneshadow.h"
+
+#include "imageblur.h"
+
+OneShadow::OneShadow(QSize contentSize,int radius,QColor shadowColor)
+{
+
+    /* 图片(阴影)宽和高 */
+    int imageWidth = contentSize.width()+radius*2;
+    int imageHeight = contentSize.height()+radius*2;
+    m_image = new QImage(imageWidth, imageHeight, QImage::Format_ARGB32);
+    m_image->fill(shadowColor);
+    //边框设为透明
+    for(int x= 0 ;x < m_image->width();x++)
+    {
+        for(int y=0;y<m_image->height();y++)
+        {
+            /* 设置阴影圈部分透明 */
+            if(x>=radius && x<(m_image->width()-radius) && y>=radius && y<(m_image->height()-radius)) continue;
+            m_image->setPixelColor(QPoint(x,y), Qt::transparent);
+        }
+    }
+    /* 通过高斯模糊来设置阴影 */
+    GaussBlur::Blur(*m_image, radius);
+}
+
+OneShadow::~OneShadow()
+{
+    delete m_image;
+}
+
+
+QImage& OneShadow::image()
+{
+    return *m_image;
+}

+ 32 - 0
common/Shadow/oneshadow.h

@@ -0,0 +1,32 @@
+#ifndef ONESHADOW_H
+#define ONESHADOW_H
+
+#include <QSize>
+#include <QColor>
+#include <QImage>
+
+/**
+ * OneShadow的用法,把这个类放到需要显示阴影的控件下面
+ * 也就是他的父类上,在父类的paintEvent()函数里绘制image
+ *
+ *  QPainter painter(this);
+ *  painter.setRenderHint(QPainter::Antialiasing);
+ *  painter.drawImage(QPoint(0,0),m_shadow->image());
+ */
+
+class OneShadow
+{
+public:
+    OneShadow(QSize contentSize,int radius = 5,QColor shadowColor = QColor(0, 0, 0, 60));
+    ~OneShadow();
+
+    QImage& image();
+
+private:
+//    int radius;                 /* 阴影的宽度 */
+//    QSize contentSize;          /* 阴影上面内容的大小 */
+//    QColor shadowColor;         /* 颜色 */
+    QImage* m_image = nullptr;
+};
+
+#endif // ONESHADOW_H

+ 131 - 3
common/ftp/QtFtp.cpp

@@ -68,6 +68,34 @@ void QtFtp::putFile(const QString &fileName, const QString &farPath)
     connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SIGNAL(signal_error(QNetworkReply::NetworkError)));
 #endif
 }
+
+
+/**
+ * @brief 重载函数,上传文件,直接上传内存数据
+ * 
+ * @param data 需要上传的数据
+ * @param farPath 远程文件名
+ */
+void QtFtp::putFile(const QByteArray& data,const QString& farPath)
+{
+    m_isFinished = false;
+    m_result = false;
+
+    m_url.setPath(farPath);
+    m_reply = m_manager.put(QNetworkRequest(m_url),data);
+    /* 将信号进行一次转发 */
+    connect(m_reply,SIGNAL(uploadProgress(qint64,qint64)),this,SIGNAL(signal_uploadProgress(qint64,qint64)));
+    /* 连接信号和槽 */
+    connect(m_reply,SIGNAL(finished()),this,SLOT(do_uploadFinished()));
+    connect(m_reply,SIGNAL(uploadProgress(qint64,qint64)),this,SLOT(do_uploadProgress(qint64,qint64)));
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+    connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SIGNAL(signal_error(QNetworkReply::NetworkError)));
+#endif
+}
+
+
+
 /**
  * @brief QtFtp::getFile
  * @param fileName 本机文件路径和文件名
@@ -99,6 +127,27 @@ void QtFtp::getFile(const QString &fileName,const QString &srcPath)
 #endif
 }
 
+/* 下载文件,直接下载到内存 */
+void QtFtp::getFile(QByteArray& data,const QString& srcPath)
+{
+    m_isFinished = false;
+    m_result = false;
+
+    m_downloadData = &data;
+    m_url.setPath(srcPath);
+    m_reply = m_manager.get(QNetworkRequest(m_url));
+
+    /* 转发信号 */
+    connect(m_reply,SIGNAL(downloadProgress(qint64,qint64)),this,SIGNAL(signal_downloadProgress(qint64,qint64)));
+    /* 连接信号和槽 */
+    connect(m_reply,SIGNAL(finished()),this,SLOT(do_downloadFinishedToData()));
+    connect(m_reply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(do_downloadProgress(qint64,qint64)));
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+    connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SIGNAL(signal_error(QNetworkReply::NetworkError)));
+#endif
+}
+
 /**
  * @brief 等待完成,同时设置超时时间,超时后,断开reply的信号和槽的连接
  *        参数设置为0表示查询是否完成,设置为-1表示一直等待
@@ -152,16 +201,51 @@ void QtFtp::do_uploadFinished()
         m_result = true;
         emit signal_uploadState(true);
         LOG_INFO("----- FTP upload success -----");
-    }
-    else
+    } else
     {
-        LOG_WARN("FTP上传失败:" + m_reply->errorString());
+        LOG_WARN("FTP upload failed : " + m_reply->errorString());
         emit signal_uploadState(false);
     }
     m_reply->deleteLater();
     m_reply = nullptr;
     m_isFinished = true;
 }
+
+
+/* 创建目录 */
+bool QtFtp::createDir(const QString& path)
+{
+    m_isFinished = false;
+    m_result = false;
+
+    QNetworkRequest request;
+    QUrl url;
+    url.setHost(m_url.host());
+    url.setPort(m_url.port());
+    url.setUserName(m_url.userName());
+    url.setPassword(m_url.password());
+    url.setPath(path);
+    LOG_DEBUG("create dir : " + url.path());
+    
+    request.setUrl(url);
+    request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
+    
+    QByteArray data;
+    data.append("command=MKD");
+
+    m_reply = m_manager.post(request,data);
+
+    /* 连接信号和槽 */
+    connect(m_reply,SIGNAL(finished()),this,SLOT(do_finished()));
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+    connect(m_reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SIGNAL(signal_error(QNetworkReply::NetworkError)));
+#endif
+
+    return true;
+}
+
+
 /* 完成,保存文件 */
 void QtFtp::do_downloadFinished()
 {
@@ -186,6 +270,29 @@ void QtFtp::do_downloadFinished()
     m_isFinished = true;
 }
 
+/* 下载完成,保存数据 */
+void QtFtp::do_downloadFinishedToData()
+{
+    if(m_reply->error() == QNetworkReply::NoError)
+    {
+        *m_downloadData = m_reply->readAll();
+        
+        emit signal_downloadState(true);
+        m_result = true;
+        LOG_DEBUG("----- FTP download success -----");
+    }else
+    {
+        LOG_WARN("FTP download failed:" + m_reply->errorString());
+        
+        emit signal_downloadState(false);
+    }
+    m_downloadData = nullptr;
+
+    m_reply->deleteLater();
+    m_reply = nullptr;
+    m_isFinished = true;
+}
+
 /* 上传进度 */
 void QtFtp::do_uploadProgress(qint64 bytesSent, qint64 bytesTotal)
 {
@@ -198,6 +305,27 @@ void QtFtp::do_downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
     LOG_DEBUG("received / total : " + QString::number(bytesReceived) + " / " + QString::number(bytesTotal));
 }
 
+/* ftp其他任务完成槽函数 */
+void QtFtp::do_finished()
+{
+    m_isFinished = true;
+    if(m_reply->error() == QNetworkReply::NoError)
+    {
+        LOG_INFO("----- FTP operation success -----");
+        
+        m_result = true;
+    }else
+    {
+        LOG_WARN("FTP operation failed : " + m_reply->errorString());
+
+        m_result = false;
+    }
+
+    m_reply->deleteLater();
+    m_reply = nullptr;
+    m_isFinished = true;
+}
+
 /* ftp发送错误槽函数 */
 #if defined (QT_VERSION) && QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 void QtFtp::do_ftpReplyError(QNetworkReply::NetworkError error)

+ 42 - 14
common/ftp/QtFtp.h

@@ -21,28 +21,55 @@ public:
     explicit QtFtp(QObject* parent = nullptr);
     ~QtFtp();
 
-    void setHostAndPort(const QString& host,int port = 21);         /* 设置目标主机IP和端口 */
-    void setUserPasswd(const QString& user,const QString& pw);      /* 设置目标设备的用户名和密码 */
-    void putFile(const QString& fileName,const QString& farPath);   /* 上传文件 */
-    void getFile(const QString& fileName,const QString &srcPath);   /* 下载文件 */
-    bool waitFinished(int msecs = 30000);                           /* 等待完成 */
-    bool getResult();                                               /* 获取结果 */
+    /* 设置目标主机IP和端口 */
+    void setHostAndPort(const QString& host,int port = 21);
+    /* 设置目标设备的用户名和密码 */    
+    void setUserPasswd(const QString& user,const QString& pw);
+    /* 上传文件 */   
+    void putFile(const QString& fileName,const QString& farPath);
+    /* 上传文件,直接上传内存数据 */   
+    void putFile(const QByteArray& data,const QString& farPath);
+    /* 下载文件 */
+    void getFile(const QString& fileName,const QString &srcPath);   
+    /* 下载文件,直接下载到内存 */
+    void getFile(QByteArray& data,const QString& srcPath);
+    /* 等待完成 */
+    bool waitFinished(int msecs = 30000);
+    /* 获取结果 */
+    bool getResult();                                               
+
+    /* 创建目录 */
+    bool createDir(const QString& path);
 
 signals:
-    void signal_uploadState(bool flag);                             /* 文件上传成功或失败 */
-    void signal_downloadState(bool flag);                           /* 文件下载成功或失败 */
-    void signal_uploadProgress(qint64 bytesSent, qint64 bytesTotal);       /* 上传进度 */
-    void signal_downloadProgress(qint64 bytesReceived, qint64 bytesTotal); /* 下载进度 */
+    /* 文件上传成功或失败 */
+    void signal_uploadState(bool flag);
+    /* 文件下载成功或失败 */
+    void signal_downloadState(bool flag);
+    /* 上传进度 */                           
+    void signal_uploadProgress(qint64 bytesSent, qint64 bytesTotal);
+    /* 下载进度 */      
+    void signal_downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+
 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
     /* 将QNetworkReply的一些信号转换成自定义的信号 */
     void signal_error(QNetworkReply::NetworkError);
 #endif
 
 private slots:
-    void do_uploadFinished();                                       /* 上传完成 */
-    void do_downloadFinished();                                     /* 下载完成,保存文件 */
-    void do_uploadProgress(qint64 bytesSent, qint64 bytesTotal);    /* 上传进度 */
-    void do_downloadProgress(qint64 bytesReceived, qint64 bytesTotal); /* 下载进度 */
+    /* 上传完成 */
+    void do_uploadFinished();
+    /* 下载完成,保存文件 */
+    void do_downloadFinished();
+    /* 下载完成,保存数据 */
+    void do_downloadFinishedToData();
+    /* 上传进度 */
+    void do_uploadProgress(qint64 bytesSent, qint64 bytesTotal);
+    /* 下载进度 */
+    void do_downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+    /* ftp其他任务完成槽函数 */
+    void do_finished();
+
     #if defined (QT_VERSION) && QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
     void do_ftpReplyError(QNetworkReply::NetworkError error);       /* ftp发送错误槽函数 */
     #endif
@@ -52,6 +79,7 @@ private:
     QTimer m_timer;                                                 /* 定时器,用于等待上传或下载完成 */
     QUrl m_url;
     QFile m_file;                                                   /* 存储下载的文件 */
+    QByteArray *m_downloadData = nullptr;                           /* 存储下载的数据 */
     QNetworkAccessManager m_manager;
     QNetworkReply* m_reply = nullptr;
 };