2015-10-05 43 views
3

我正在處理大型文件,數量超過數百GB。用戶需要能夠在磁盤之間移動這些文件,並且位於沒有默認文件管理器的受限制系統中。用戶可能意識到他們犯了一個錯誤並取消了操作,並且據我所知,用戶必須等待當前的複製或重命名操作才能完成。這可能會讓他們感到沮喪,因爲他們會等待幾分鐘,只能看到他們的許多GB文件仍然被複制。在複製的情況下,我可以刪除第二個文件,但是在重命名的情況下,我正在使用它來移動文件,我不得不重複該操作來撤銷它,這是不可接受的。無阻塞的工作人員 - 中斷文件副本

有什麼方法可以中斷我在QFile的文檔中沒有看到的copy()和rename(),還是我需要將我自己的類放在一起來處理複製和重命名?

回答

6

我不認爲文件大小對重命名需要多長時間有任何影響。

對於副本 - Qt沒有提供任何內置功能,您必須自己實現它。這裏關鍵的一個問題是,你必須找到一些方法來輪詢不斷取消複製。這意味着您無法鎖定主線程以便能夠處理事件。

無論你是爲了保持主線程響應,還是決定使用主線程,都需要額外的線程 - 在這兩種情況下,您都需要實現「分段」複製 - 一次使用緩衝區執行一個塊,直到文件被複制或複製被取消。您需要這樣才能夠處理用戶事件並跟蹤複製進度。

我建議你實施一個QObject派生的副本助手工人類,它跟蹤文件名,總大小,緩衝區大小,進度和清除取消。那麼是否將在主線程或專用線程中使用它是一個選擇問題。

編輯:找到它,但你最好仔細檢查它,因爲它是作爲一個例子做,並沒有被徹底的測試:

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的進度。您也可以輕鬆調整它以移動文件。

+0

我很害怕這個。只是幾個小時我寧願花在其他地方。我會按照你所建議的模式做一些事情,並且必須在另一個線程中確保GUI可以隨着進度而更新。光明的一面,我可以通過這種方式提供具體的文件。 – MildWolfie

+0

至於重命名(),我用它來移動文件。當移動到同一磁盤上的另一個目錄時,它將保持不變,但從一個磁盤到另一個磁盤需要花費時間,具體取決於文件大小。 – MildWolfie

+0

@MildWolfie - 我想我已經做了這樣的事情,會在幾個小時後檢查我什麼時候回家。 – dtech

1

我不認爲你正在尋找的功能存在。

你可以做的不是使用copy()函數,而是使用舊文件創建一個新文件並逐漸讀取(qint64 maxSize)到QByteArray,並將新字符寫入(const QByteArray & byteArray)。 這樣你可以自己控制流量,只需檢查用戶是否在每次讀/寫操作之間都沒有按下取消按鈕。