如何在QFutureWatcher

时间:2017-05-04 14:57:41

标签: qt c++14 shared-ptr future qtconcurrent

我试图一次阻止多个文件,然后将它们复制到另一个位置。

应同时阻止源文件和目标文件。因此,我无法使用static QFile::copy()功能。

要保留并移动由于QSharedPointer< QFile >而使用QFile的文件既不可复制也不可移动。

要完全执行整个操作,我使用QtConcurrent框架。即:QtConcurrent::mappedReducedQFutureWatcher

要打开我使用 map 仿函数的所有文件对,然后按顺序复制它们我使用 reduce 仿函数。

using PFile = QSharedPointer< QFile >;

using PFileList = QList< PFile >;

template< typename Result, typename Functor >
struct FunctorWithResultType
        : std::decay_t< Functor >
{

    using result_type = Result;

    FunctorWithResultType(Functor & functor)
        : std::decay_t< Functor >{std::forward< Functor >(functor)}
    { ; }

};

template< typename Result, typename Functor >
FunctorWithResultType< Result, Functor >
addResultType(Functor && functor)
{
    return {functor};
}

class Test
{
public :

    QDir currentDirectory;
    QFutureWatcher< PFileList > fileCopyFutureWatcher;
    // ...
};

// ...

Test::Test()
{
    auto onFilesCopied = [&]
    {
        PFileList copiedFiles = fileCopyFutureWatcher.result();
        qCInfo(usbDevice) << "Files copy operation from drive finished.";
        qCInfo(usbDevice) << copiedFiles.size();
        for (PFile const & file : copiedFiles) {
            //file->close(); // I want copiedFiles to be closed automatically at the end of current scope
        }
    };
    connect(&fileCopyFutureWatcher, &fileCopyFutureWatcher.finished, onFilesCopied);
}

// ...

void Test::onDeviceAdded(QString deviceName)
{
    qCInfo(usbDevice) << "USB device" << deviceName << "is added.";
    if (!fileCopyFutureWatcher.isFinished()) {
        return;
    }
    QDir sourceDirectory{deviceName};
    if (!sourceDirectory.cd("dir")) {
        qCInfo(usbDevice) << "Drive" << deviceName << "does not contain dir subdirectory.";
        return;
    }
    auto destinationDirectory = currentDirectory;
    if (!destinationDirectory.mkpath("fileCache")) {
        qCCritical(usbDevice) << "Can't create fileCache subdirectory in"
                                     << destinationDirectory.absolutePath()
                                     << "directory";
        return;
    }
    if (!destinationDirectory.cd("fileCache")) {
        qCCritical(usbDevice) << "Can't change directory to fileCache subdirectory in"
                                     << destinationDirectory.absolutePath()
                                     << "directory";
        return;
    }
    struct PFilePair
    {
        PFile source, destination;
    };
    auto openSourceAndDestinationFiles = [&, destinationDirectory] (QFileInfo const & fileInfo) -> PFilePair
    {
        auto source = PFile::create(fileInfo.absoluteFilePath());
        QFile & sourceFile = *source;
        if (!sourceFile.open(QFile::ReadOnly)) {
            qCCritical(usbDevice) << "Can't open file" << sourceFile.fileName()
                                         << "to read:" << sourceFile.errorString();
            return {};
        }
        auto destination = PFile::create(destinationDirectory.absoluteFilePath(fileInfo.fileName()));
        QFile & destinationFile = *destination;;
        if (!destinationFile.open(QFile::ReadWrite | QFile::Truncate)) {
            qCCritical(usbDevice) << "Can't open file" << destinationFile.fileName()
                                         << "to write:" << destinationFile.errorString();
            return {};
        }
        return {qMove(source), qMove(destination)};
    };
    auto copyFiles = [&] (PFileList & files, PFilePair const & filePair)
    {
        if (filePair.source.isNull() || filePair.destination.isNull()) {
            return;
        }
        QFile & sourceFile = *filePair.source;
        QFile & destinationFile = *filePair.destination;
        qCInfo(usbDevice) << sourceFile.fileName() << "->" << destinationFile.fileName();
        constexpr int size = (1 << 20); // 1MiB
        QByteArray buffer{size, 0};
        char * const data = buffer.data();
        while (!sourceFile.atEnd()) {
            auto bytesRead = sourceFile.read(data, size);
            if (bytesRead < 0) {
                qCCritical(usbDevice) << "Can't read file" << sourceFile.fileName()
                                             << ":" << sourceFile.errorString();
                return;
            }
            auto bytesWritten = destinationFile.write(data, bytesRead);
            while (bytesWritten < bytesRead) {
                auto sizeWritten = destinationFile.write(data + bytesWritten, bytesRead - bytesWritten);
                if (sizeWritten < 0) {
                    qCCritical(usbDevice) << "Can't write file" << destinationFile.fileName()
                                                 << ":" << destinationFile.errorString();
                    return;
                }
                bytesWritten += sizeWritten;
            }
            Q_ASSERT(bytesWritten == bytesRead);
        }
        Q_ASSERT(sourceFile.size() == destinationFile.size());
        destinationFile.flush();
        files.append(filePair.destination);
    };
    QStringList nameFilters;
    nameFilters << "file.dat";
    // many other entries
    auto entryInfoList = sourceDirectory.entryInfoList(nameFilters, (QDir::Readable | QDir::Files));
    fileCopyFutureWatcher.setFuture(QtConcurrent::mappedReduced< PFileList >(qMove(entryInfoList), addResultType< PFilePair >(openSourceAndDestinationFiles), copyFiles));
}

// ...

QFutureWatcher::finished事件中读取结果后,我的应用程序仍会打开和阻止所有目标文件。因此,我可以得出结论,QSharedPointer< QFile >的副本仍然存在。我怀疑他们离开QFuture QFutureWatcher。如何在不使用假QFile::~QFile()实例调用QFutureWatcher::setFuture的情况下清除所有这些文件(即如何使QFuture关闭所有文件)?

要实现这一点,我需要从QFuture窃取结果,而不是复制。

1 个答案:

答案 0 :(得分:0)

我的解决方法如下:

using PFile = QSharedPointer< QFile >;

using PFileList = QList< PFile >;

template< typename Result, typename Functor >
struct FunctorWithResultType
        : std::decay_t< Functor >
{

    using result_type = Result;

    FunctorWithResultType(Functor & functor)
        : std::decay_t< Functor >{std::forward< Functor >(functor)}
    { ; }

};

template< typename Result, typename Functor >
FunctorWithResultType< Result, Functor >
addResultType(Functor && functor)
{
    return {functor};
}

class Test
{
public :

    QDir currentDirectory;
    // ...
};

// ...

void Test::onDeviceAdded(QString deviceName)
{
    qCInfo(usbDevice) << "USB device" << deviceName << "is added.";
    QDir sourceDirectory{deviceName};
    if (!sourceDirectory.cd("dir")) {
        qCInfo(usbDevice) << "Drive" << deviceName << "does not contain dir subdirectory.";
        return;
    }
    auto destinationDirectory = currentDirectory;
    if (!destinationDirectory.mkpath("fileCache")) {
        qCCritical(usbDevice) << "Can't create fileCache subdirectory in"
                                     << destinationDirectory.absolutePath()
                                     << "directory";
        return;
    }
    if (!destinationDirectory.cd("fileCache")) {
        qCCritical(usbDevice) << "Can't change directory to fileCache subdirectory in"
                                     << destinationDirectory.absolutePath()
                                     << "directory";
        return;
    }
    struct PFilePair
    {
        PFile source, destination;
    };
    auto openSourceAndDestinationFiles = [&, destinationDirectory] (QFileInfo const & fileInfo) -> PFilePair
    {
        auto source = PFile::create(fileInfo.absoluteFilePath());
        QFile & sourceFile = *source;
        if (!sourceFile.open(QFile::ReadOnly)) {
            qCCritical(usbDevice) << "Can't open file" << sourceFile.fileName()
                                         << "to read:" << sourceFile.errorString();
            return {};
        }
        auto destination = PFile::create(destinationDirectory.absoluteFilePath(fileInfo.fileName()));
        QFile & destinationFile = *destination;;
        if (!destinationFile.open(QFile::ReadWrite | QFile::Truncate)) {
            qCCritical(usbDevice) << "Can't open file" << destinationFile.fileName()
                                         << "to write:" << destinationFile.errorString();
            return {};
        }
        return {qMove(source), qMove(destination)};
    };
    auto copyFiles = [&] (PFileList & files, PFilePair const & filePair)
    {
        if (filePair.source.isNull() || filePair.destination.isNull()) {
            return;
        }
        QFile & sourceFile = *filePair.source;
        QFile & destinationFile = *filePair.destination;
        qCInfo(usbDevice) << sourceFile.fileName() << "->" << destinationFile.fileName();
        constexpr int size = (1 << 20); // 1MiB
        QByteArray buffer{size, 0};
        char * const data = buffer.data();
        while (!sourceFile.atEnd()) {
            auto bytesRead = sourceFile.read(data, size);
            if (bytesRead < 0) {
                qCCritical(usbDevice) << "Can't read file" << sourceFile.fileName()
                                             << ":" << sourceFile.errorString();
                return;
            }
            auto bytesWritten = destinationFile.write(data, bytesRead);
            while (bytesWritten < bytesRead) {
                auto sizeWritten = destinationFile.write(data + bytesWritten, bytesRead - bytesWritten);
                if (sizeWritten < 0) {
                    qCCritical(usbDevice) << "Can't write file" << destinationFile.fileName()
                                                 << ":" << destinationFile.errorString();
                    return;
                }
                bytesWritten += sizeWritten;
            }
            Q_ASSERT(bytesWritten == bytesRead);
        }
        Q_ASSERT(sourceFile.size() == destinationFile.size());
        destinationFile.flush();
        files.append(filePair.destination);
    };

    auto & fileCopyFutureWatcher = *new QFutureWatcher< PFileList >{this};

    auto onFilesCopied = [&]
    {
        fileCopyFutureWatcher.deleteLater();
        PFileList copiedFiles = fileCopyFutureWatcher.result();
        qCInfo(usbDevice) << "Files copy operation from drive finished.";
        qCInfo(usbDevice) << copiedFiles.size();
        for (PFile const & file : copiedFiles) {
            // ...
        }
    };
    connect(&fileCopyFutureWatcher, &fileCopyFutureWatcher.finished, onFilesCopied);

    QStringList nameFilters;
    nameFilters << "*.dat";
    auto entryInfoList = sourceDirectory.entryInfoList(nameFilters, (QDir::Readable | QDir::Files));
    fileCopyFutureWatcher.setFuture(QtConcurrent::mappedReduced< PFileList >(qMove(entryInfoList), addResultType< PFilePair >(openSourceAndDestinationFiles), copyFiles));
}

// ...

我几乎可以肯定,fileCopyFutureWatcher实例将delete可靠。