須要在QT5中進行FTP文件下載,並須要支持整目錄下載,通過對比選擇,最後決定使用Qt4中的QFtp來完成咱們的需求。所以決定學習源碼,看清結構,作到能真正解決所要面對的問題。git
Qftp一共只有四個文件,主要文件是qftp.cpp,這個文件中,有太多的類,首先按類分解到各自文件中,這樣利用官方的示例代碼,跑起來後,能夠方便的查看代碼。github
全部的客戶端命令被壓入到命令堆棧。一個命令運行有兩個入口:一是命令被壓入堆棧時,若堆棧中只有一條命令,即被運行;二是做響應服務端響應時,類型爲idle或not waiting。
每一個命運被構造時,都會返回惟一ID,這是很重要的一點,由於命令大多關聯着一本地IO設備,在清理IO時,要注意與命令對應,由於全部的操做都是異步的。架構
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
我選擇的這種目錄下載方式比較麻煩,沒有放到後臺再開一個進程去處理,試圖作總體考慮,且整個運行過程都是異步的,調試也比較難,其中進度條控件控制還有些坑,須要當心處理。過程艱難,收穫頗多。