Non-blocking worker – interrupt file copy

I don’t think the file size has any effect on how long a renaming will take.

For the copy – Qt offers nothing built in, you have to implement it yourself. The key gotcha here is that you will have to find some way to poll for a copy cancellation continuously. This means you cannot lock the main thread in order to be able to process events.

Whether you go for an extra thread in order to keep the main thread responsive, or decide to use the main thread – in both cases you will need to implement “fragmented” copying – one chunk at a time using a buffer, until the file is copied or copying is cancelled. You need this to be able to process user events and track copying progress.

I suggest you implement a QObject derived copy helper worker class which tracks file name, total size, buffer size, progress and clean up on cancellation. Then it is a matter of choice whether you will use it in the main thread or in a dedicated thread.

EDIT: Found it, but you better double check it, since it was done as an example and has not been thoroughly tested:

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;
};

The done() signal is used to deleteLater() the copy helper / close copy dialog or whatever. You can enable the elapsed timer and use it to implement an elapsed time property and estimated time as well. Pausing is another possible feature to implement. Using QMetaObject::invokeMethod() allows the event loop to periodically process user events so you can cancel and update progress, which goes from 0 to 1. You can easily tweak it for moving files as well.

Leave a Comment