QFtp源碼學習及目錄下載

背景

需要在QT5中進行FTP文件下載,并需要支持整目錄下載,經過對比選擇,最后決定使用Qt4中的QFtp來完成我們的需求。因此決定學習源碼,看清結構,做到能真正解決所要面對的問題。

分解源碼

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

類說明

  • 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列當前目錄的原有邏輯是取一條數據就發(fā)送一條文件或目錄的消息,這樣在我們連續(xù)遍歷目錄時,無法分清楚是哪個目錄下的數據,無法進行正確的遞歸。這樣改造效率應該會好一些且完全控制整個目錄的顯示,比如,目錄顯示在上面。當然也可以將遞歸放到commandFinished消息響應中去。

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

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);                     //原來在循環(huán)內,讀一條數據發(fā)送一個listInfo信號
            }
            else {
                if (line.endsWith("No such file or directory\r\n"))
                    err = QString::fromLatin1(line);
            }
        }
        emit listInfos(infos);                          //改為在循環(huán)外發(fā)送新增的listInfos信號
    }

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

    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();    //統計需要下載的字節(jié)量
            ...
            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響應的遞歸處理部分

    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版本對應;編譯好了,運行前,請注意目錄結構是否正確。

小結

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

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯系作者。

相關閱讀更多精彩內容

友情鏈接更多精彩內容