Неблокирующий рабочий - копия файла прерывания

Я имею дело с очень большими файлами размером более сотни ГБ. Пользователь должен иметь возможность перемещать эти файлы между дисками и находится в ограниченной системе без файлового менеджера по умолчанию. Пользователь может понять, что совершил ошибку и отменил операцию, и насколько я могу судить, пользователю придется дождаться завершения текущей операции копирования или переименования. Это может вызвать у них чувство разочарования, поскольку они ждут потенциально минут, только чтобы увидеть, что их много файлов GB все еще копируются. В случае с копией я могу удалить второй файл, но в случае переименования, который я использую для перемещения файлов, мне пришлось бы повторить операцию в обратном порядке, чтобы отменить ее, и это просто неприемлемо.

Есть ли способ прервать copy() и rename(), которые я не вижу в документации для QFile, или мне нужно собрать собственный класс для обработки копии и переименования?

Ответ 1

Я не думаю, что размер файла влияет на продолжительность переименования.

Для копии - Qt не предлагает ничего встроенного, вы должны реализовать его самостоятельно. Ключевым вопросом здесь является то, что вам нужно будет найти способ опроса для отмены копирования непрерывно. Это означает, что вы не можете заблокировать основной поток, чтобы иметь возможность обрабатывать события.

Если вы идете на дополнительный поток, чтобы поддерживать основной поток в ответ или решили использовать основной поток, в обоих случаях вам потребуется реализовать "фрагментированное" копирование - по одному фрагменту за раз, используя буфер, пока файл копируется или копирование отменяется. Это нужно, чтобы обрабатывать пользовательские события и отслеживать ход копирования.

Я предлагаю вам реализовать класс рабочего помощника QObject, который отслеживает имя файла, общий размер, размер буфера, прогресс и очистку от отмены. Тогда вы можете выбрать, будете ли вы использовать его в основном потоке или в выделенном потоке.

EDIT: нашел его, но вам лучше проверить его, поскольку это было сделано в качестве примера и не было тщательно протестировано:

class CopyHelper : public QObject {
    Q_OBJECT
    Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
public:
    CopyHelper(QString sPath, QString dPath, quint64 bSize = 1024 * 1024) :
        isCancelled(false), bufferSize(bSize), prog(0.0), source(sPath), destination(dPath), position(0) { }
    ~CopyHelper() { free(buff); }

    qreal progress() const { return prog; }
    void setProgress(qreal p) {
        if (p != prog) {
            prog = p;
            emit progressChanged();
        }
    }

public slots:
    void begin() {
        if (!source.open(QIODevice::ReadOnly)) {
            qDebug() << "could not open source, aborting";
            emit done();
            return;
        }
        fileSize = source.size();
        if (!destination.open(QIODevice::WriteOnly)) {
            qDebug() << "could not open destination, aborting";
            // maybe check for overwriting and ask to proceed
            emit done();
            return;
        }
        if (!destination.resize(fileSize)) {
            qDebug() << "could not resize, aborting";
            emit done();
            return;
        }
        buff = (char*)malloc(bufferSize);
        if (!buff) {
            qDebug() << "could not allocate buffer, aborting";
            emit done();
            return;
        }
        QMetaObject::invokeMethod(this, "step", Qt::QueuedConnection);
        //timer.start();
    }
    void step() {
        if (!isCancelled) {
            if (position < fileSize) {
                quint64 chunk = fileSize - position;
                quint64 l = chunk > bufferSize ? bufferSize : chunk;
                source.read(buff, l);
                destination.write(buff, l);
                position += l;
                source.seek(position);
                destination.seek(position);
                setProgress((qreal)position / fileSize);
                //std::this_thread::sleep_for(std::chrono::milliseconds(100)); // for testing
                QMetaObject::invokeMethod(this, "step", Qt::QueuedConnection);
            } else {
                //qDebug() << timer.elapsed();
                emit done();
                return;
            }
        } else {
            if (!destination.remove()) qDebug() << "delete failed";
            emit done();
        }
    }
    void cancel() { isCancelled = true; }

signals:
    void progressChanged();
    void done();

private:
    bool isCancelled;
    quint64 bufferSize;
    qreal prog;
    QFile source, destination;
    quint64 fileSize, position;
    char * buff;
    //QElapsedTimer timer;
};

Сигнал done() используется для deleteLater() диалогового окна копирования/закрытия копии или любого другого. Вы можете включить истекший таймер и использовать его для реализации свойства истекшего времени и расчетного времени. Приостановка - еще одна возможная функция для реализации. Использование QMetaObject::invokeMethod() позволяет циклу событий периодически обрабатывать пользовательские события, чтобы вы могли отменить и обновить ход, который идет от 0 до 1. Вы можете легко настроить его и для перемещения файлов.

Ответ 2

Я не думаю, что функция, которую вы ищете, существует.

Что вы можете сделать, вместо использования функции copy(), создать новый файл и выполнить постепенное чтение (qint64 maxSize) в QByteArray из старого файла и записать (const QByteArray и byteArray) в новый файл. Таким образом, вы можете контролировать поток самостоятельно, просто проверьте, не нажимал ли пользователь отмену между каждым чтением/записью.