Qt之QFtp 在客户端实现文件上传、下载、新建文件夹、重命名、删除和刷新等功能

简述     

        本来是打算用新的类QNetworkAccessManager实现的客户端的文件上传、下载、新建文件夹、重命名、删除和刷新等功能,但是QNetworkAccessManager没有提供原本在QFtp提供的list()、cd()、remove()、mkdir()、rmdir()、rename() 和 rawCommand()等操作,所以无奈之下只能选用了旧版本的QFtp来实现,毕竟既然官方都废弃了QFtp而选用QNetworkAccessManager来代替,那肯定是后者比前者更加可靠稳定。 

        虽然QFtp在Qt5.0之后就被官方移除了,但是其基本功能还是挺全的,虽然在上传下载文件的时候带中文时会乱码,但也是能解决的。

        最近在做一个项目也是用到了QFtp上传文件到别人的服务器,突然就想到了要做一个简单的客户端实现其基本的功能,于是在闲暇时就慢慢的把功能给加上去了,虽然有点简陋但是还是实现了有不少的功能,当然也包括了前面提到上传下载带中文的文件乱码问题。

登录和退出

        其实登录很简单,只需要先connect主机然后在登录,connect的时候需要主机的IP地址和端口,登录的时候需要用户名和密码,当然如果没设置当然就不需要了。

void MainWnd::on_tbConnent_clicked()
{
    QString serverAddress = ui->leServerAddress->text();
    if (serverAddress.isEmpty())
    {
        statusBar()->showMessage("服务器地址为空!", 2000);
        return;
    }

    QString port = ui->lePort->text();
    if (port.isEmpty())
    {
        statusBar()->showMessage("端口号为空!", 2000);
        return;
    }

    QString account = ui->leAccount->text();
    QString password = ui->lePassword->text();

    // 如果已经登录了就不需要重复登录
    if (ftp.state() != QFtp::LoggedIn)
    {
        ftp.connectToHost(serverAddress, port.toInt());
        ftp.login(account, password);
    }

    // 保存到配置文件
    saveToIni();
}

上传和下载

        文件的上传和下载都是有可能带有中文的,所以在上传的时候在获取到的路径后需要把路径转为QFtp可识别的格式,下载也一样在右键获取到选中行的名称后需要把目录一起转格式后再传递给QFtp,因为都是从本地上传到FTP所以转的格式都是一样的,如果是列举获取到FTP目录时则是从FTP到本地,则转换刚好相反,后满会在列出目录时给出。

void MainWnd::onUpload()
{
    QString path = QFileDialog::getOpenFileName(NULL, "", QString("C:/Users/Pangs/Desktop/"));
    if (path.isEmpty()) return;

    file.setFileName(path);
    if (!file.open(QIODevice::ReadOnly)) return;

    uploadPath = path;

    // 解决中文乱码问题
    QString name = path.mid(path.lastIndexOf("/") + 1);
    path = QString("%1/%2").arg(currentPath).arg(name);
    ftp.put(&file, QString::fromLatin1(path.toLocal8Bit()));
}

void MainWnd::onDownload()
{
    int row = ui->tableWidget->currentRow();
    if (row < 0) return;

    QString name = ui->tableWidget->item(row, 0)->text();
    if (listPath[name]) return;

    QString path = QFileDialog::getSaveFileName(NULL, "", QString("C:/Users/Pangs/Desktop/%1").arg(name));
    if (path.isEmpty()) return;

    file.setFileName(path);
    if (!file.open(QIODevice::WriteOnly)) return;

    // 解决中文乱码问题
    path = QString("%1/%2").arg(currentPath).arg(name);
    ftp.get(QString::fromLatin1(path.toLocal8Bit()), &file);
}

新建文件夹、重命名、删除

        新建文件夹首先就是自己判断列表中的名字然后在生成新的名称了,然后日期等当然是当前生成的时间了,然后发送命令去建立目录,当然这之前也要处理乱码问题之前就说过了这里就不再复述,最后因为是新建的文件夹我们只是默认给定一个目录名,具体需要什么还需要自己决定,所以我们默认就打开编辑器,当用户点击到别的地方时才会关闭编辑器。

void MainWnd::onCreateFolder()
{
    QString name = createFolderName();

    // 在底部插入
    int row = ui->tableWidget->rowCount();

    // 插入新的一行
    ui->tableWidget->insertRow(row);

    // 名称
    ui->tableWidget->setItem(row, 0, new QTableWidgetItem(folderIcon(), name));

    // 日期
    ui->tableWidget->setItem(row, 1, new QTableWidgetItem(QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm")));

    // 类型
    QString type = folderType();
    ui->tableWidget->setItem(row, 2, new QTableWidgetItem(type));


    // 创建目录 解决中文乱码问题
    oldName = name;
    createFolder = true;
    ftp.mkdir(QString::fromLatin1(oldName.toLocal8Bit()));

    editRow = row;
    listPath[oldName] = true;
    listType[oldName] = type;

    // 打开编辑
    QTableWidgetItem *item = ui->tableWidget->item(row, 0);
    ui->tableWidget->setCurrentCell(row, 0);
    ui->tableWidget->openPersistentEditor(item);    // 打开编辑
    ui->tableWidget->editItem(item);
}

        重命名跟删除文件事先都会判断当前要选中的行是否是返回上一级的那一行,如果不是才会进行下一步处理,重命名也是跟新建文件夹一样打开选中那一行的名称那项的编辑器,让用户输入后点击别的地方后关闭,注意的是如果用户输入的名称已存在则在用户点击其他地方关闭编辑器的时候把文件名还原回没改之前,如果不重复则可以rename修改。

        删除文件还要看用户选中的是文件还是目录,删除文件用remove删除目录用rmdir,传入的都是要删除的那项的名称。

void MainWnd::onRename()
{
    // 如果是多级目录,则选中的第一级就不给重命名
    int row = ui->tableWidget->currentIndex().row();
    if (currentPath.indexOf("/") >= 0 && row <= 0) return;

    editRow = row;
    oldName = ui->tableWidget->item(row, 0)->text();

    QTableWidgetItem *item = ui->tableWidget->item(row, 0);
    ui->tableWidget->setCurrentCell(row, 0);
    ui->tableWidget->openPersistentEditor(item);    // 打开编辑
    ui->tableWidget->editItem(item);
}

void MainWnd::closePersistentEditor()
{
    if (editRow < 0 || editRow >= ui->tableWidget->rowCount()) return;

    QTableWidgetItem *item = ui->tableWidget->item(editRow, 0);
    ui->tableWidget->closePersistentEditor(item);   // 关闭编辑

    // 重命名
    QString newname = ui->tableWidget->item(editRow, 0)->text();
    for (int i = 0; i < ui->tableWidget->rowCount(); i++)
    {
        QString name = ui->tableWidget->item(i, 0)->text();
        if ((name == newname) && (listType[oldName] == listType[newname]))
        {
            if (!createFolder) statusBar()->showMessage("文件名已存在!", 2000);
            ui->tableWidget->item(editRow, 0)->setText(oldName);
            editRow = -1;
            createFolder = false;
            return;
        }
    }

    editRow = -1;
    ftp.rename(QString::fromLatin1(oldName.toLocal8Bit()),QString::fromLatin1(newname.toLocal8Bit()));

    listPath[newname] = listPath[oldName];
    listPath.remove(oldName);
    listType[newname] = listType[oldName];
    listType.remove(oldName);
}

void MainWnd::onRemove()
{
    // 如果是多级目录,则选中的第一级就不给删
    int row = ui->tableWidget->currentIndex().row();
    if (currentPath.indexOf("/") >= 0 && row <= 0) return;

    removeRow = row;

    // 解决中文乱码问题
    QString name = ui->tableWidget->item(row, 0)->text();

    if (listPath[name]) ftp.rmdir(QString::fromLatin1(name.toLocal8Bit()));
    else                ftp.remove(QString::fromLatin1(name.toLocal8Bit()));
}

 刷新

         刷新只是先把列表数据全部删除,然后再根据当前是否是根目录,如果当前不是根目录则先插入一行用来双击返回上一级用,最后发送list命令列出当前目录下所有文件及目录,这里也要注意使用listPath以及listType记录列出来的文件或目录用作双击返回或者进入下一级等用途。

void MainWnd::clear()
{
    listPath.clear();
    listType.clear();    

    int rowCount = ui->tableWidget->rowCount();
    for (int i = 0; i < rowCount; i++)
    {
        ui->tableWidget->removeRow(0);
    }
}

void MainWnd::onRefresh()
{
    // 清除表格
    clear();

    // 如果当前目录不是根目录,则先插入一行用来双击返回上一级
    if (currentPath.indexOf("/") >= 0)
    {
        ui->tableWidget->insertRow(0);
        ui->tableWidget->setItem(0, 0, new QTableWidgetItem(folderIcon(), "..."));
        listType["..."] = folderType();
    }

    ftp.list();
}

        在获取文件列表信息的时候文件名是有可能为中文的,所以这里需要转格式把QFtp的格式转为Qt的格式才不会乱码,所以之前是Qt的格式转为QFtp的格式使用QString::fromLatin1(name.toLocal8Bit());这里就返过来把QFtp格式转为Qt的格式使用QString::fromLocal8Bit(name.toLatin1())。

void MainWnd::listInfo(QUrlInfo url)
{
    // 解决中文乱码问题
    QString name = QString::fromLocal8Bit(url.name().toLatin1());
    QString type = url.isDir() ? folderType() : fileType(name);

    // 记录是否为目录
    listType[name] = type;
    listPath[name] = url.isDir();

    int row = ui->tableWidget->rowCount();

    // 插入新的一行
    ui->tableWidget->insertRow(row);

    // 名称
    ui->tableWidget->setItem(row, 0, new QTableWidgetItem(url.isDir() ? folderIcon() : fileIcon(name), name));

    // 日期
    ui->tableWidget->setItem(row, 1, new QTableWidgetItem(url.lastModified().toString("yyyy/MM/dd hh:mm")));

    // 类型
    ui->tableWidget->setItem(row, 2, new QTableWidgetItem(type));    

    // 大小
    if (url.isDir()) return;
    ui->tableWidget->setItem(row, 3, new QTableWidgetItem(QString("%1 KB").arg(qMax(int(url.size() / 1000), 1))));
}

进入下一级、返回上一级

        如果当前双击的是目录则把当前记录的目录名在加上现在进入的一层的名称,因为要显示新的数据所以要把旧数据清除,现在是进入下一级所以一定在顶端是有一条返回上一级的目录,所以在发送list前就把返回的目录建立好。

        如果当前双击的是目录并且是返回上一级的目录,则把当前记录的目录名去掉最后一层,然后清除数据,做好准备接受新数据,因为是返回上一级,所以也有可能上一级就是根目录所以这时候就没有必要建立返回上一级的节点因为没有上一级了,如果上一级还不是根目录则需要建立一个返回上一级的节点。

void MainWnd::on_tableWidget_doubleClicked(const QModelIndex &index)
{
    int row = index.row();
    QString name = ui->tableWidget->item(row, 0)->text();

    // 如果双击的是第0行,并且不是根目录,因为根目录没有返回上一级项,表示返回上一级
    if (row == 0 && currentPath.indexOf("/") >= 0)
    {
        // 将当前目录减少一级
        currentPath = currentPath.left(currentPath.lastIndexOf("/"));

        // 清除表格
        clear();

        // 如果当前目录不是根目录,则先插入一行用来双击返回上一级
        if (currentPath.indexOf("/") >= 0)
        {
            ui->tableWidget->insertRow(0);
            ui->tableWidget->setItem(0, 0, new QTableWidgetItem(folderIcon(), "..."));
            listType["..."] = folderType();
        }

        // 发送命令返回上一级,然后列出所有项
        ftp.cd("../");
        ftp.list();
    }
    // 如果双击的是其他行,并且是目录行,表示进入下一级
    else if (listPath[name])
    {    
        // 当前目录进入下一级
        currentPath += QString("/%1").arg(name);

        // 清除表格
        clear();

        // 如果当前目录不是根目录,则先插入一行用来双击返回上一级
        ui->tableWidget->insertRow(0);
        ui->tableWidget->setItem(0, 0, new QTableWidgetItem(folderIcon(), "..."));
        listType["..."] = folderType();

        // 发送命令进入下一级,然后列出所有项
        ftp.cd(currentPath);
        ftp.list();
    }
}

 右键弹出菜单

        在右键弹出菜单之前先判断是否有正在编辑名称的项,如果有则先要关闭,然后再把菜单的各个项加入到菜单并添加信号槽,使用exec函数阻塞,contextMenuEvent函数是重写了QTableWidget后重写的,只不过把它转到主窗口方便点。

void MainWnd::contextMenuEvent(QContextMenuEvent *event)
{
    Q_UNUSED(event);
    if (ui->tableWidget->rowCount() <= 0) return;

    // 如果有未关闭的编辑项则先关闭
    closePersistentEditor();

    QMenu menu;
    menu.addAction("上传", this, &onUpload);
    menu.addAction("下载", this, &onDownload);
    menu.addSeparator();
    menu.addAction(folderIcon(), "新建文件夹", this, &onCreateFolder);
    menu.addSeparator();
    menu.addAction("重命名", this, &onRename);
    menu.addAction("删除", this, &onRemove);
    menu.addAction("刷新", this, &onRefresh);

    menu.exec(QCursor::pos());
}

QFtp命令结束响应函数

        在各个命令结束后会响应commandFinished函数,如果命令成功err为false,否则为true,然后再根据各个步骤做出其他操作,statusBar()->showMessage()第二个参数2000表示显示多长时间,单位是ms。

void MainWnd::commandFinished(int, bool err)
{
    int cmd = ftp.currentCommand();
    switch (cmd)
    {
    case QFtp::Login:
        if (!err) ftp.list();   // 成功则显示列表
        statusBar()->showMessage(err ? ftp.errorString() : "服务器连接成功!", 2000);
        break;

    case QFtp::Close:
        if (!err) {clear(); currentPath.clear(); }     // 清除列表
        statusBar()->showMessage(err ? ftp.errorString() : "断开服务器连接!", 2000);
        break;

    case QFtp::Get:
        if (file.isOpen()) { file.flush(); file.close(); }
        statusBar()->showMessage(err ? ftp.errorString() : "文件下载成功!", 2000);
        break;

    case QFtp::Put:
        if (file.isOpen()) file.close();
        if (!err) onInsertRow();
        statusBar()->showMessage(err ? ftp.errorString() : "文件上传成功!", 2000);
        break;

    case QFtp::Rename:
        if (!createFolder) statusBar()->showMessage(err ? ftp.errorString() : "重命名成功!", 2000);
        createFolder = false;
        break;

    case QFtp::Remove:
        if (!err) ui->tableWidget->removeRow(removeRow);
        statusBar()->showMessage(err ? ftp.errorString() : "删除文件成功!", 2000);
        break;

    case QFtp::Mkdir:
        statusBar()->showMessage(err ? ftp.errorString() : "创建文件夹成功!", 2000);
        break;

    case QFtp::Rmdir:
        if (!err) ui->tableWidget->removeRow(removeRow);
        statusBar()->showMessage(err ? ftp.errorString() : "删除文件夹成功!", 2000);
        break;
    }
}

QFtp读写进度响应函数 

        readBytes表示当前上传或者下载的进度,totalBytes表示文件总大小,当开始下载或者上传时显示进度条,其他时间隐藏进度条。

void MainWnd::dataTransferProgress(qint64 readBytes, qint64 totalBytes)
{
    progress.setVisible(readBytes != totalBytes);
    progress.setMaximum(totalBytes);
    progress.setValue(readBytes);
}

去掉QTableWidget选中虚框

        重载了QStyledItemDelegate,然后重载paint把选中后的焦点去掉,这样没有焦点就不会有虚线框了。

class MyStyledItemDelegate : QStyledItemDelegate
{
    Q_OBJECT
public:
    MyStyledItemDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) {}
    ~MyStyledItemDelegate() {}

private:
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        QStyleOptionViewItem viewOption(option);
        if (option.state.testFlag((QStyle::State_HasFocus)))
        {
            viewOption.state = viewOption.state ^ QStyle::State_HasFocus;
        }

        QStyledItemDelegate::paint(painter, viewOption, index);
    }
};

其他

        设置表格列宽,重新设置itemDelegate,添加进度条,添加信号槽。

    // 设置表格列宽
    ui->tableWidget->setColumnWidth(0, 140);
    ui->tableWidget->setColumnWidth(1, 120);
    ui->tableWidget->setColumnWidth(2, 90);
    ui->tableWidget->setColumnWidth(3, 70);

    // 去除选中的虚线框
    ui->tableWidget->setItemDelegate((QStyledItemDelegate *)new MyStyledItemDelegate);

    // 设置进度条
    progress.hide();
    progress.setFixedHeight(10);
    progress.setAlignment(Qt::AlignCenter);
    statusBar()->addWidget(&progress, width());

    // 信号槽
    connect(&ftp, SIGNAL(listInfo(QUrlInfo)), SLOT(listInfo(QUrlInfo)));
    connect(&ftp, SIGNAL(commandFinished(int,bool)), SLOT(commandFinished(int,bool)));
    connect(&ftp, SIGNAL(dataTransferProgress(qint64,qint64)), SLOT(dataTransferProgress(qint64,qint64)));

        冲忙写完,当然了也会有很多的问题,这里就不再多做修改了,写这个简单的客户端:一是为了让自己更加对FTP传输更加的了解,二是给出具体步骤代码让更多人的少走歪路,三是想记录自己的新的体会;当然了,也少不了各位网友的帮助,比如去掉虚线框、乱码、获取系统图标、文件夹类型等。

        源码下载1(折扣):https://item.taobao.com/item.htm?ft=t&id=703097790814

        源码下载2(无折扣):https://download.csdn.net/download/Ilson_/19418828

        对于搭建FTP服务不太了解的可以看看:https://blog.csdn.net/Ilson_/article/details/97818689

        QFtp在客户端实现给服务器一次性创建多级目录:https://blog.csdn.net/Ilson_/article/details/98654137

        QetworkAccessManager实现FTP文件上传/下载功能:https://blog.csdn.net/Ilson_/article/details/97829233

  • 15
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
Qt提供了QFtp类来实现FTP客户端功能,而线程则可以实现并行下载和上传多个文件功能。 首先,我们需要创建一个QFtp对象,并连接相关的信号和槽函数: ```cpp QFtp* ftp = new QFtp(this); connect(ftp, SIGNAL(commandStarted(int)), this, SLOT(ftpCommandStarted(int))); connect(ftp, SIGNAL(commandFinished(int,bool)), this, SLOT(ftpCommandFinished(int,bool))); connect(ftp, SIGNAL(listInfo(QUrlInfo)), this, SLOT(addToList(QUrlInfo))); ``` 接下来,我们可以实现下载和上传文件功能。在这里,我们可以创建一个线程来执行下载或上传任务: ```cpp QThread* thread = new QThread(this); FtpWorker* worker = new FtpWorker(); worker->moveToThread(thread); connect(thread, SIGNAL(started()), worker, SLOT(process())); connect(worker, SIGNAL(finished()), thread, SLOT(quit())); connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); thread->start(); ``` 在FtpWorker类中,我们可以实现下载和上传文件的具体操作。在下载文件时,我们可以使用QFtp的get()函数,将文件下载到本地磁盘上: ```cpp void FtpWorker::downloadFile(QString remoteFilePath, QString localFilePath) { QFile* file = new QFile(localFilePath); if (!file->open(QIODevice::WriteOnly)) { emit error("Failed to open file"); return; } ftp->get(remoteFilePath, file); ftp->waitForFinished(); if (ftp->error() != QFtp::NoError) { emit error(ftp->errorString()); } file->close(); delete file; } ``` 在上传文件时,我们可以使用QFtp的put()函数,将本地文件上传FTP服务器上: ```cpp void FtpWorker::uploadFile(QString localFilePath, QString remoteFilePath) { QFile* file = new QFile(localFilePath); if (!file->open(QIODevice::ReadOnly)) { emit error("Failed to open file"); return; } ftp->put(file, remoteFilePath); ftp->waitForFinished(); if (ftp->error() != QFtp::NoError) { emit error(ftp->errorString()); } file->close(); delete file; } ``` 最后,在FtpWorker类的process()函数中,我们可以调用downloadFile()或uploadFile()函数来执行具体的操作: ```cpp void FtpWorker::process() { ftp = new QFtp(); ftp->connectToHost(server, port); ftp->login(username, password); if (!ftp->waitForConnected()) { emit error("Failed to connect to FTP server"); return; } if (mode == DownloadMode) { downloadFile(remoteFilePath, localFilePath); } else if (mode == UploadMode) { uploadFile(localFilePath, remoteFilePath); } ftp->close(); emit finished(); } ``` 这样,我们就可以利用线程来实现并行下载和上传文件功能了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ilson_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值