使用Qt复制目录

26
我想要将一个目录从一张硬盘复制到另一张硬盘。我选择的目录包含许多子目录和文件。
使用Qt如何实现相同的操作?
10个回答

28
void copyPath(QString src, QString dst)
{
    QDir dir(src);
    if (! dir.exists())
        return;

    foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
        QString dst_path = dst + QDir::separator() + d;
        dir.mkpath(dst_path);
        copyPath(src+ QDir::separator() + d, dst_path);
    }

    foreach (QString f, dir.entryList(QDir::Files)) {
        QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f);
    }
}

我不知道是我的电脑有问题还是这个 Qt 5.6 的代码有问题,但它似乎卡住了。它在复制目录,但从未完成。很奇怪。 - kayleeFrye_onDeck
嗯,是的。第一次复制操作有效,然后随后的复制操作出现了问题。创建一个目录,然后将该目录复制到子目录中。第一次可以,第二次就出问题了。 - kayleeFrye_onDeck

13

手动操作的话,您可以进行以下操作:

1)使用下面的函数生成目标文件夹/文件列表(递归)- 目标文件。

static void recurseAddDir(QDir d, QStringList & list) {

    QStringList qsl = d.entryList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files);

    foreach (QString file, qsl) {

        QFileInfo finfo(QString("%1/%2").arg(d.path()).arg(file));

        if (finfo.isSymLink())
            return;

        if (finfo.isDir()) {

            QString dirname = finfo.fileName();
            QDir sd(finfo.filePath());

            recurseAddDir(sd, list);

        } else
            list << QDir::toNativeSeparators(finfo.filePath());
    }
}

2). 然后您可以开始将目标列表中的文件复制到新的源目录中,就像这样:

for (int i = 0; i < gtdStringList.count(); i++) {

    progressDialog.setValue(i);
    progressDialog.setLabelText(tr("%1 Coping file number %2 of %3 ")
        .arg((conf->isConsole) ? tr("Making copy of the Alta-GTD\n") : "")
        .arg(i + 1)
        .arg(gtdStringList.count()));

    qApp->processEvents(QEventLoop::ExcludeUserInputEvents);

    if (progressDialog.wasCanceled()) {

        // removing tmp files/folders
        rmDirectoryRecursive(tmpFolder);
        rmDirectoryRecursive(tmpFolderPlus);
        setEnableGUI(true);
        return;
    }

    // coping
    if (!QFile::copy(gtdStringList.at(i), tmpStringList.at(i))) {

        if (warningFlag) {

            QMessageBox box(this);
            QString name = tr("Question");
            QString file1 = getShortName(gtdStringList.at(i), QString("\\...\\"));
            QString file2 = getShortName(tmpStringList.at(i), QString("\\...\\"));
            QString text = tr("Cannot copy <b>%1</b> <p>to <b>%2</b>"   \
               "<p>This file will be ignored, just press <b>Yes</b> button" \
               "<p>Press <b>YesToAll</b> button to ignore other warnings automatically..."  \
               "<p>Or press <b>Abort</b> to cancel operation").arg(file1).arg(file2);

            box.setModal(true);
            box.setWindowTitle(name);
            box.setText(QString::fromLatin1("%1").arg(text));
            box.setIcon(QMessageBox::Question);
            box.setStandardButtons(QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::Abort);

            switch (box.exec()) {                   
                case (QMessageBox::YesToAll):
                    warningFlag = false;
                    break;
                case (QMessageBox::Yes):
                    break;
                case (QMessageBox::Abort):
                    rmDirectoryRecursive(tmpFolder);
                    rmDirectoryRecursive(tmpFolderPlus);
                    setEnableGUI(true);
                    return;
            }
        }
    }
}

就是这样了,祝你好运!


2
我已经尝试过将我的更正作为编辑发布,但似乎没有人理解,所以我会在这里发布。使用 QString("%1/%2").arg(d.path()).arg(file) 通常不是一个好主意,因为 '%1' 或 '%2' 可以在大多数文件系统中找到(在文件或路径名中)。以 cygwin 创建的此路径为例:c:\cyg\ftp%3a%2f%2fcygwin.mirrorcatalogs.com%2fcygwin%2f - vikki
3
假设d.path()中保存了以下内容且文件名为text.txt。"%1"将被替换为 d.path(),形成"c:\cyg\ftp%3a%2f%2fcygwin.mirrorcatalogs.com%2fcygwin%2f/%2"。最终你会得到"c:\cyg\ftp%3atext.txtftext.txtfcygwin.mirrorcatalogs.comtext.txtfcygwintext.txtf/text.txt"。更好的选择是使用d.path().append('/').append(file) - vikki
如果您使用“append”函数,它会返回一个引用。如果您在其他地方再次使用该引用,它可能会发生更改。如果您决定使用“append”函数,请小心。 - Shihe Zhang
安全的选择是 QString("%1/%2").arg(d.path(), file) - 但最好使用 d.filePath(file) 来组合路径名,这将可移植地使用平台的目录分隔符。当然,你仍然需要使用 QString::arg(QString, QString) 来组合消息框文本。 - Toby Speight

8

我也想找一些类似的东西,本着试试的心态在谷歌上搜索了一下,但无果。现在我要分享一下自己已经做到的:

static bool cpDir(const QString &srcPath, const QString &dstPath)
{
    rmDir(dstPath);
    QDir parentDstDir(QFileInfo(dstPath).path());
    if (!parentDstDir.mkdir(QFileInfo(dstPath).fileName()))
        return false;

    QDir srcDir(srcPath);
    foreach(const QFileInfo &info, srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) {
        QString srcItemPath = srcPath + "/" + info.fileName();
        QString dstItemPath = dstPath + "/" + info.fileName();
        if (info.isDir()) {
            if (!cpDir(srcItemPath, dstItemPath)) {
                return false;
            }
        } else if (info.isFile()) {
            if (!QFile::copy(srcItemPath, dstItemPath)) {
                return false;
            }
        } else {
            qDebug() << "Unhandled item" << info.filePath() << "in cpDir";
        }
    }
    return true;
}

它使用一个名为rmDir的函数,看起来非常相似:
static bool rmDir(const QString &dirPath)
{
    QDir dir(dirPath);
    if (!dir.exists())
        return true;
    foreach(const QFileInfo &info, dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) {
        if (info.isDir()) {
            if (!rmDir(info.filePath()))
                return false;
        } else {
            if (!dir.remove(info.fileName()))
                return false;
        }
    }
    QDir parentDir(QFileInfo(dirPath).path());
    return parentDir.rmdir(QFileInfo(dirPath).fileName());
}

这个并没有处理链接和特殊文件,顺便提一句。

7
用最原始的方法:逐个复制每个文件。
  • 使用QDir::entryList()遍历目录内容
  • 使用QDir::cd()QDir::cdUp()进入和退出目录
  • 使用QDir::mkdir()QDir::mkpath()创建新的文件夹树
  • 最后,使用QFile::copy()复制实际文件

2
这基本上是petch的答案,但由于在Qt 5.6中它对我不起作用(这是最热门的问题),所以稍作修改,因此所有功劳归功于petch函数
bool copyPath(QString sourceDir, QString destinationDir, bool overWriteDirectory)
{
    QDir originDirectory(sourceDir);

    if (! originDirectory.exists())
    {
        return false;
    }

    QDir destinationDirectory(destinationDir);

    if(destinationDirectory.exists() && !overWriteDirectory)
    {
        return false;
    }
    else if(destinationDirectory.exists() && overWriteDirectory)
    {
        destinationDirectory.removeRecursively();
    }

    originDirectory.mkpath(destinationDir);

    foreach (QString directoryName, originDirectory.entryList(QDir::Dirs | \
                                                              QDir::NoDotAndDotDot))
    {
        QString destinationPath = destinationDir + "/" + directoryName;
        originDirectory.mkpath(destinationPath);
        copyPath(sourceDir + "/" + directoryName, destinationPath, overWriteDirectory);
    }

    foreach (QString fileName, originDirectory.entryList(QDir::Files))
    {
        QFile::copy(sourceDir + "/" + fileName, destinationDir + "/" + fileName);
    }

    /*! Possible race-condition mitigation? */
    QDir finalDestination(destinationDir);
    finalDestination.refresh();

    if(finalDestination.exists())
    {
        return true;
    }

    return false;
}

使用:
/*! Overwrite existing directories. */
bool directoryCopied = copyPath(sourceDirectory, destinationDirectory, true);

/*! Do not overwrite existing directories. */
bool directoryCopied = copyPath(sourceDirectory, destinationDirectory, false);

2
有什么不同之处,为什么? - Toby Speight

2

试试这个:

bool copyDirectoryFiles(const QString &fromDir, const QString &toDir, bool coverFileIfExist)
{
    QDir sourceDir(fromDir);
    QDir targetDir(toDir);
    if(!targetDir.exists()){    /* if directory don't exists, build it */
        if(!targetDir.mkdir(targetDir.absolutePath()))
            return false;
    }

    QFileInfoList fileInfoList = sourceDir.entryInfoList();
    foreach(QFileInfo fileInfo, fileInfoList){
        if(fileInfo.fileName() == "." || fileInfo.fileName() == "..")
            continue;

        if(fileInfo.isDir()){    /* if it is directory, copy recursively*/
            if(!copyDirectoryFiles(fileInfo.filePath(),  
                targetDir.filePath(fileInfo.fileName()), 
                coverFileIfExist)) 
                return false;
        }
        else{            /* if coverFileIfExist == true, remove old file first */
            if(coverFileIfExist && targetDir.exists(fileInfo.fileName())){
                targetDir.remove(fileInfo.fileName());
            }

            // files copy
            if(!QFile::copy(fileInfo.filePath(),  
                targetDir.filePath(fileInfo.fileName()))){ 
                    return false;
            }
        }
    }
    return true;
}

1
感谢您提供这段代码片段,它可能会立即提供一些帮助。通过展示为什么这是一个好的解决方案,适当的解释将极大地提高其教育价值,并使其对未来有类似但不完全相同的问题的读者更有用。请编辑您的答案以添加解释,并指出适用的限制和假设。 - Toby Speight

1

我制作了一个库,通过shell命令风格的API来操作文件。它支持递归复制文件并处理了几个更多的条件。

https://github.com/benlau/qtshell#cp

例子
cp("-a", ":/*", "/target"); // copy all files from qrc resource to target path recursively

cp("tmp.txt", "/tmp");

cp("*.txt", "/tmp");

cp("/tmp/123.txt", "456.txt");

cp("-va","src/*", "/tmp");

cp("-a", ":resource","/target");

0

由于我在macOS上遇到了一些App-Bundles的问题,这里提供一个使用QDirIterator的解决方案

void copyAndReplaceFolderContents(const QString &fromDir, const QString &toDir, bool copyAndRemove = false) {
QDirIterator it(fromDir, QDirIterator::Subdirectories);
QDir dir(fromDir);
const int absSourcePathLength = dir.absoluteFilePath(fromDir).length();

while (it.hasNext()){
    it.next();
    const auto fileInfo = it.fileInfo();
    if(!fileInfo.isHidden()) { //filters dot and dotdot
        const QString subPathStructure = fileInfo.absoluteFilePath().mid(absSourcePathLength);
        const QString constructedAbsolutePath = toDir + subPathStructure;
        
        if(fileInfo.isDir()){
            //Create directory in target folder
            dir.mkpath(constructedAbsolutePath);
        } else if(fileInfo.isFile()) {
            //Copy File to target directory

            //Remove file at target location, if it exists. Otherwise QFile::copy will fail
            QFile::remove(constructedAbsolutePath);
            QFile::copy(fileInfo.absoluteFilePath(), constructedAbsolutePath);
        }
    }
}

if(copyAndRemove)
    dir.removeRecursively();

}


0

如果您正在使用基于Linux的系统,并且存在并可以运行cp命令,则可以使用QProcess启动bash:

auto copy = new QProcess(this);
copy->start(QStringLiteral("cp -rv %1 %2").arg(sourceFolder, destinationFolder));
copy->waitForFinished();
copy->close();

cp 详细信息:

  • -r 表示递归复制
  • -v 表示打印成功复制的文件

注意:如果复制操作时间较长,则需要处理 UI 冻结问题,如此处所述 here


0
假设目标位置为空"没有同名的现有文件或文件夹",并且您可以使用递归函数!来递归地复制一个目录,那么代码将类似于这样。

void copy_all(QString dst_loc, QString src_loc)
{
    QDir src(src_loc);
    for(QFileInfo file_info : src.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)){
        if(file_info.isDir()){
            src.mkpath(dst_loc+'/'+file_info.fileName());
            copy_all(dst_loc+'/'+file_info.fileName(),file_info.absoluteFilePath());
        }
        QFile::copy(file_info.absoluteFilePath(), dst_loc+'/'+file_info.fileName());
    }
}

如果你曾经处理过树形数据结构并尝试创建一个递归函数来执行“深度优先搜索”算法,那么你将得到一个85%相似的算法,实际上我就是从这个想法中得到的。

  • 第二种方法是使用映射数据结构来保存目录中当前fileInfoList的状态,并显示是否已经使用了此fileInfo。首先从源位置收集有关子目录和文件的所有信息。

这就是大多数操作系统和其他文件管理器复制数据的方式,它会向您显示要复制的文件大小、要复制的文件和文件夹数量,最后,在您启动复制之前,如果存在具有相同名称的文件或文件夹的冲突,则会显示出来“如果您将在目标位置执行相同的算法以匹配文件名”。

祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接