QFtp源碼學習及目錄下載

背景

須要在QT5中進行FTP文件下載,並須要支持整目錄下載,通過對比選擇,最後決定使用Qt4中的QFtp來完成咱們的需求。所以決定學習源碼,看清結構,作到能真正解決所要面對的問題。git

分解源碼

Qftp一共只有四個文件,主要文件是qftp.cpp,這個文件中,有太多的類,首先按類分解到各自文件中,這樣利用官方的示例代碼,跑起來後,能夠方便的查看代碼。github

類說明

  • class QFtpCommand : 此類是對FTP命令的封裝,將命令與QIODevice設備關聯起來,並返回一個惟一的標識ID。
  • class QFtpPI : 此類是對FTP協議的封裝,processReply是主要函數,應答服務端響應。
  • class QFtpDTP : 此類是數據操做封裝,數據讀取、解析、存儲都是在此類中處理。
  • class QFtpPrivate : 此類是QFtp的實際操做類,被組合到QFtp類中,是邏輯處理中心。
  • class QFtp : 此類是外殼,用戶直接面對。
  • class QUrlInfo : 此爲信息類,存儲接收到的每一條文件數據信息。

運行流程

全部的客戶端命令被壓入到命令堆棧。一個命令運行有兩個入口:一是命令被壓入堆棧時,若堆棧中只有一條命令,即被運行;二是做響應服務端響應時,類型爲idle或not waiting。
每一個命運被構造時,都會返回惟一ID,這是很重要的一點,由於命令大多關聯着一本地IO設備,在清理IO時,要注意與命令對應,由於全部的操做都是異步的。架構

改造list響應

QFtp列當前目錄的原有邏輯是取一條數據就發送一條文件或目錄的消息,這樣在咱們連續遍歷目錄時,沒法分清楚是哪一個目錄下的數據,沒法進行正確的遞歸。這樣改造效率應該會好一些且徹底控制整個目錄的顯示,好比,目錄顯示在上面。固然也能夠將遞歸放到commandFinished消息響應中去。 app

QFtpDTP::socketReadyRead() - 修改讀取目錄列表時的信號發送方式異步

if (pi->currentCommand().startsWith(QLatin1String("LIST"))) {
        QVector<QUrlInfo> infos;                        //增長vector來存儲整個目錄信息
        while (socket->canReadLine()) {
            QUrlInfo i;
            QByteArray line = socket->readLine();
            if (parseDir(line, QLatin1String(""), &i)) {
                infos.push_back(i);
                //emit listInfo(i);                     //原來在循環內,讀一條數據發送一個listInfo信號
            }
            else {
                if (line.endsWith("No such file or directory\r\n"))
                    err = QString::fromLatin1(line);
            }
        }
        emit listInfos(infos);                          //改成在循環外發送新增的listInfos信號
    }

FtpWindow::addToList(const QVector<QUrlInfo>& urlInfos) - listInfos響應修改socket

for (int i = 0; i < urlInfos.size(); i++)
    {
        QTreeWidgetItem* item = new QTreeWidgetItem;
        QUrlInfo urlInfo = urlInfos[i];
        if (urlInfo.name().compare(".") != 0) {
            item->setText(0, urlInfo.name().toLatin1());
            item->setText(1, QString::number(urlInfo.size()));
            item->setText(2, QString::number(urlInfo.isDir()));
            item->setText(3, urlInfo.owner());
            item->setText(4, urlInfo.group());
            item->setText(5, urlInfo.lastModified().toString("MMM dd yyyy"));
            QPixmap pixmap(urlInfo.isDir() ? ":/images/dir.png" : ":/images/file.png");
            item->setIcon(0, pixmap);
            isDirectory[urlInfo.name()] = urlInfo.isDir();
            fileList->addTopLevelItem(item);
        }
    }

目錄下載

將FtpWindow::downloadFile() slot分解成兩個函數,增長downAllFile(QString rootDir)來完成目錄遞歸。函數

void FtpWindow::downloadFile()
{
    files.clear();      //初始化本地設備
    downDirs.clear();   //清空須要下載的目錄堆棧

    downAllFile(currentPath);   //下載具體操做,另外一個入口在list的響應中
    showProgressDialog();       //進度條顯示
}

下載的真實操做函數學習

void FtpWindow::downAllFile(QString rootDir) {
    QString thisRoot(rootDir + "/");        //要下載的父目錄
    QList<QTreeWidgetItem*> selectedItemList = fileList->selectedItems();
    for (int i = 0; i < selectedItemList.size(); i++)
    {
        QString fileName = selectedItemList[i]->text(0);
        if (isDirectory.value(fileName)) {      //如果子目錄,組合完成的目錄,壓入待下載目錄堆棧
            if(fileName != "..")
                downDirs.push(thisRoot + fileName);
        }
        else {
            downloadTotalBytes += selectedItemList[i]->text(1).toLongLong();    //統計須要下載的字節量
            ...
            QFile* file = new QFile(dirTmp.append("/").append(fileName));
            //文件下載請求,是異步操做
            int id = ftp->get(QString::fromLatin1((selectedItemList[i]->text(0)).toStdString().c_str()), file);
            files.insert(id, file);     //本地IO設備與其命令綁定並存儲
        }
    }
    if (downDirs.size() > 0) {      //待下載目錄堆棧不空,處理一條
        enterSubDir = true;         //表示正在下載目錄
        QString nextDir(downDirs.pop());  //取須要處理的下一個目錄
        ftp->cd(nextDir);                 //切換到這個目錄
        currentDownPath = nextDir;
        ftp->list();                        //列目錄,在其響應中將再遞歸調用本函數~~~~
    }
}

list響應的遞歸處理部分ui

if (!enterSubDir) {     //下載的文件中沒有目錄
        ...     
    }
    else {                              //正處理於目錄下載中
        fileList->selectAll();          //選中列表中全部
        downAllFile(currentDownPath);   //遞歸調用下載處理函數
    }

項目地址

https://github.com/zhoutk/qtDemo

命令行編譯

git clone https://github.com/zhoutk/qtDemo
cd qtDemo/ftpClient & mkdir build & cd build
cmake ..
cmake --build .

編譯時注意:cmake默認爲x86架構,須要與你安裝的Qt版本對應;編譯好了,運行前,請注意目錄結構是否正確。this

小結

我選擇的這種目錄下載方式比較麻煩,沒有放到後臺再開一個進程去處理,試圖作總體考慮,且整個運行過程都是異步的,調試也比較難,其中進度條控件控制還有些坑,須要當心處理。過程艱難,收穫頗多。

相關文章
相關標籤/搜索