Разбиение файла на несколько частей

Я пытаюсь сделать приложение для копирования файлов в Qt. Мне нужно разбить исходный файл на несколько файлов, чтобы моя функция копирования могла копировать порции данных одну за другой и обновлять QProgressBar, Для точного обновления прогресса мне нужно разделить исходный файл на 1% от его первоначального размера. Мой подход неверен? Я не могу найти много ресурсов по этой теме. Как я могу разделить исходный файл на несколько частей одинакового размера?

2 ответа

Решение

Ниже приведен автономный эскиз такого асинхронного копировщика файлов. Есть некоторые недостатки:

  1. Отчеты об ошибках также должны сообщать коды ошибок QFile.

  2. Для работы с мьютексом RAII нужен специальный класс блокировки мьютекса.

При взгляде на код убедитесь, что вы рассматриваете реализацию и интерфейс (его использование) отдельно. Интерфейс делает его простым в использовании. Относительная сложность реализации делает возможным простой в использовании интерфейс.

#include <QApplication>
#include <QByteArray>
#include <QProgressDialog>
#include <QFileDialog>
#include <QBasicTimer>
#include <QElapsedTimer>
#include <QThread>
#include <QMutex>
#include <QFile>
#include <limits>

class FileCopier : public QObject {
   Q_OBJECT
   QMutex mutable m_mutex;
   QByteArray m_buf;
   QBasicTimer m_copy, m_progress;
   QString m_error;
   QFile m_fi, m_fo;
   qint64 m_total, m_done;
   int m_shift;

   void close() {
      m_copy.stop();
      m_progress.stop();
      m_fi.close();
      m_fo.close();
      m_mutex.unlock();
   }
   /// Takes the error string from given file and emits an error indication.
   /// Closes the files and stops the copy. Always returns false
   bool error(QFile & f) {
      m_error = f.errorString();
      m_error.append(QStringLiteral("(in %1 file").arg(f.objectName()));
      emit finished(false, m_error);
      close();
      return false;
   }
   void finished() {
      emitProgress();
      emit finished(m_done == m_total, m_error);
      close();
   }
   void emitProgress() {
      emit progressed(m_done, m_total);
      emit hasProgressValue(m_done >> m_shift);
   }
   void timerEvent(QTimerEvent * ev) {
      if (ev->timerId() == m_copy.timerId()) {
         // Do the copy
         qint64 read = m_fi.read(m_buf.data(), m_buf.size());
         if (read == -1) { error(m_fi); return; }
         if (read == 0) return finished();
         qint64 written = m_fo.write(m_buf.constData(), read);
         if (written == -1) { error(m_fo); return; }
         Q_ASSERT(written == read);
         m_done += read;
      }
      else if (ev->timerId() == m_progress.timerId())
         emitProgress();
   }
   Q_INVOKABLE void cancelImpl() {
      if (!m_fi.isOpen()) return;
      m_error = "Canceled";
      finished();
   }
public:
   explicit FileCopier(QObject * parent = 0) :
      QObject(parent),
      // Copy 64kbytes at a time. On a modern hard drive, we'll copy
      // on the order of 1000 such blocks per second.
      m_buf(65536, Qt::Uninitialized)
   {
      m_fi.setObjectName("source");
      m_fo.setObjectName("destination");
   }
   /// Copies a file to another with progress indication.
   /// Returns false if the files cannot be opened.
   /// This method is thread safe.
   Q_SLOT bool copy(const QString & src, const QString & dst) {
      bool locked = m_mutex.tryLock();
      Q_ASSERT_X(locked, "copy",
                 "Another copy is already in progress");
      m_error.clear();

      // Open the files
      m_fi.setFileName(src);
      m_fo.setFileName(dst);
      if (! m_fi.open(QIODevice::ReadOnly)) return error(m_fi);
      if (! m_fo.open(QIODevice::WriteOnly)) return error(m_fo);
      m_total = m_fi.size();
      if (m_total < 0) return error(m_fi);

      // File size might not fit into an integer, calculate the number of
      // binary digits to shift it right by. Recall that QProgressBar etc.
      // all use int, not qint64!
      m_shift = 0;
      while ((m_total>>m_shift) >= std::numeric_limits<int>::max()) m_shift++;
      emit hasProgressMaximum(m_total>>m_shift);

      m_done = 0;
      m_copy.start(0, this);
      m_progress.start(100, this); // Progress is emitted at 10Hz rate
      return true;
   }
   /// This method is thread safe only when a copy is not in progress.
   QString lastError() const {
      bool locked = m_mutex.tryLock();
      Q_ASSERT_X(locked, "lastError",
                 "A copy is in progress. This method can only be used when"
                 "a copy is done");
      QString error = m_error;
      m_mutex.unlock();
      return error;
   }
   /// Cancels a pending copy operation. No-op if no copy is underway.
   /// This method is thread safe.
   Q_SLOT void cancel() {
      QMetaObject::invokeMethod(this, "cancelImpl");
   }
   /// Signal for progress indication with number of bytes
   Q_SIGNAL void progressed(qint64 done, qint64 total);
   /// Signals for progress that uses abstract integer values
   Q_SIGNAL void hasProgressMaximum(int total);
   Q_SIGNAL void hasProgressValue(int done);
   ///
   Q_SIGNAL void finished(bool ok, const QString & error);
};

/// A thread that is always destructible: if quits the event loop and waits
/// for it to finish.
class Thread : public QThread {
public:
   ~Thread() { quit(); wait(); }
};

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   QString src = QFileDialog::getOpenFileName(0, "Source File");
   if (src.isEmpty()) return 1;
   QString dst = QFileDialog::getSaveFileName(0, "Destination File");
   if (dst.isEmpty()) return 1;

   QProgressDialog dlg("File Copy Progress", "Cancel", 0, 100);
   Q_ASSERT(!dlg.isModal());
   Thread thread;
   FileCopier copier;
   copier.moveToThread(&thread);
   thread.start();
   dlg.connect(&copier, SIGNAL(hasProgressMaximum(int)),
               SLOT(setMaximum(int)));
   dlg.connect(&copier, SIGNAL(hasProgressValue(int)),
               SLOT(setValue(int)));
   copier.connect(&dlg, SIGNAL(canceled()), SLOT(cancel()));
   a.connect(&copier, SIGNAL(finished(bool,QString)), SLOT(quit()));
   // The copy method is thread safe.
   copier.copy(src, dst);
   return a.exec();
}

#include "main.moc"

Обычно копирование файлов с помощью индикатора выполнения выполняется следующим образом:

  1. открыть исходный файл (для чтения)
  2. открыть файл назначения (для записи)
  3. прочитать блок (64 КБ или около того) из исходного файла и записать его в целевой файл
  4. обновить индикатор выполнения
  5. повторите шаги 3. и 4. до конца исходного файла
  6. готово:) закройте файлы

Нет необходимости разбивать файл на несколько файлов. Просто обработайте файл небольшими блоками (блок за блоком), а не целым файлом сразу.

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

Другие вопросы по тегам