Qt 5.9 | Qt Network | Torrent Example | filemanager.cpp Example File

Qt 5.9.9 Reference Documentation

# filemanager.cpp Example File

torrent/filemanager.cpp

      /****************************************************************************
      **
      ** Copyright (C) 2016 The Qt Company Ltd.
      ** Contact: https://www.qt.io/licensing/
      **
      ** This file is part of the examples of the Qt Toolkit.
      **
      ** $QT_BEGIN_LICENSE:BSD$
      ** Commercial License Usage
      ** Licensees holding valid commercial Qt licenses may use this file in
      ** accordance with the commercial license agreement provided with the
      ** Software or, alternatively, in accordance with the terms contained in
      ** a written agreement between you and The Qt Company. For licensing terms
      ** and conditions see https://www.qt.io/terms-conditions. For further
      ** information use the contact form at https://www.qt.io/contact-us.
      **
      ** BSD License Usage
      ** Alternatively, you may use this file under the terms of the BSD license
      ** as follows:
      **
      ** "Redistribution and use in source and binary forms, with or without
      ** modification, are permitted provided that the following conditions are
      ** met:
      **   * Redistributions of source code must retain the above copyright
      **     notice, this list of conditions and the following disclaimer.
      **   * Redistributions in binary form must reproduce the above copyright
      **     notice, this list of conditions and the following disclaimer in
      **     the documentation and/or other materials provided with the
      **     distribution.
      **   * Neither the name of The Qt Company Ltd nor the names of its
      **     contributors may be used to endorse or promote products derived
      **     from this software without specific prior written permission.
      **
      **
      ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
      ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
      ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
      ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
      **
      ** $QT_END_LICENSE$
      **
      ****************************************************************************/

      #include "filemanager.h"
      #include "metainfo.h"

      #include <QByteArray>
      #include <QDir>
      #include <QFile>
      #include <QTimer>
      #include <QTimerEvent>
      #include <QCryptographicHash>

      FileManager::FileManager(QObject *parent)
          : QThread(parent)
      {
          quit = false;
          totalLength = 0;
          readId = 0;
          startVerification = false;
          wokeUp = false;
          newFile = false;
          numPieces = 0;
          verifiedPieces.fill(false);
      }

      FileManager::~FileManager()
      {
          quit = true;
          cond.wakeOne();
          wait();

          foreach (QFile *file, files) {
              file->close();
              delete file;
          }
      }

      int FileManager::read(int pieceIndex, int offset, int length)
      {
          ReadRequest request;
          request.pieceIndex = pieceIndex;
          request.offset = offset;
          request.length = length;

          QMutexLocker locker(&mutex);
          request.id = readId++;
          readRequests << request;

          if (!wokeUp) {
              wokeUp = true;
              QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
          }

          return request.id;
      }

      void FileManager::write(int pieceIndex, int offset, const QByteArray &data)
      {
          WriteRequest request;
          request.pieceIndex = pieceIndex;
          request.offset = offset;
          request.data = data;

          QMutexLocker locker(&mutex);
          writeRequests << request;

          if (!wokeUp) {
              wokeUp = true;
              QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
          }
      }

      void FileManager::verifyPiece(int pieceIndex)
      {
          QMutexLocker locker(&mutex);
          pendingVerificationRequests << pieceIndex;
          startVerification = true;

          if (!wokeUp) {
              wokeUp = true;
              QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
          }
      }

      int FileManager::pieceLengthAt(int pieceIndex) const
      {
          QMutexLocker locker(&mutex);
          return (sha1s.size() == pieceIndex + 1)
              ? (totalLength % pieceLength) : pieceLength;
      }

      QBitArray FileManager::completedPieces() const
      {
          QMutexLocker locker(&mutex);
          return verifiedPieces;
      }

      void FileManager::setCompletedPieces(const QBitArray &pieces)
      {
          QMutexLocker locker(&mutex);
          verifiedPieces = pieces;
      }

      QString FileManager::errorString() const
      {
          return errString;
      }

      void FileManager::run()
      {
          if (!generateFiles())
              return;

          do {
              {
                  // Go to sleep if there's nothing to do.
                  QMutexLocker locker(&mutex);
                  if (!quit && readRequests.isEmpty() && writeRequests.isEmpty() && !startVerification)
                      cond.wait(&mutex);
              }

              // Read pending read requests
              mutex.lock();
              QList<ReadRequest> newReadRequests = readRequests;
              readRequests.clear();
              mutex.unlock();
              while (!newReadRequests.isEmpty()) {
                  ReadRequest request = newReadRequests.takeFirst();
                  QByteArray block = readBlock(request.pieceIndex, request.offset, request.length);
                  emit dataRead(request.id, request.pieceIndex, request.offset, block);
              }

              // Write pending write requests
              mutex.lock();
              QList<WriteRequest> newWriteRequests = writeRequests;
              writeRequests.clear();
              while (!quit && !newWriteRequests.isEmpty()) {
                  WriteRequest request = newWriteRequests.takeFirst();
                  writeBlock(request.pieceIndex, request.offset, request.data);
              }

              // Process pending verification requests
              if (startVerification) {
                  newPendingVerificationRequests = pendingVerificationRequests;
                  pendingVerificationRequests.clear();
                  verifyFileContents();
                  startVerification = false;
              }
              mutex.unlock();
              newPendingVerificationRequests.clear();

          } while (!quit);

          // Write pending write requests
          mutex.lock();
          QList<WriteRequest> newWriteRequests = writeRequests;
          writeRequests.clear();
          mutex.unlock();
          while (!newWriteRequests.isEmpty()) {
              WriteRequest request = newWriteRequests.takeFirst();
              writeBlock(request.pieceIndex, request.offset, request.data);
          }
      }

      void FileManager::startDataVerification()
      {
          QMutexLocker locker(&mutex);
          startVerification = true;
          cond.wakeOne();
      }

      bool FileManager::generateFiles()
      {
          numPieces = -1;

          // Set up the thread local data
          if (metaInfo.fileForm() == MetaInfo::SingleFileForm) {
              QMutexLocker locker(&mutex);
              MetaInfoSingleFile singleFile = metaInfo.singleFile();

              QString prefix;
              if (!destinationPath.isEmpty()) {
                  prefix = destinationPath;
                  if (!prefix.endsWith('/'))
                      prefix += '/';
                  QDir dir;
                  if (!dir.mkpath(prefix)) {
                      errString = tr("Failed to create directory %1").arg(prefix);
                      emit error();
                      return false;
                  }
              }
              QFile *file = new QFile(prefix + singleFile.name);
              if (!file->open(QFile::ReadWrite)) {
                  errString = tr("Failed to open/create file %1: %2")
                              .arg(file->fileName()).arg(file->errorString());
                  emit error();
                  delete file;
                  return false;
              }

              if (file->size() != singleFile.length) {
                  newFile = true;
                  if (!file->resize(singleFile.length)) {
                      errString = tr("Failed to resize file %1: %2")
                                  .arg(file->fileName()).arg(file->errorString());
                      delete file;
                      emit error();
                      return false;
                  }
              }
              fileSizes << file->size();
              files << file;
              file->close();

              pieceLength = singleFile.pieceLength;
              totalLength = singleFile.length;
              sha1s = singleFile.sha1Sums;
          } else {
              QMutexLocker locker(&mutex);
              QDir dir;
              QString prefix;

              if (!destinationPath.isEmpty()) {
                  prefix = destinationPath;
                  if (!prefix.endsWith('/'))
                      prefix += '/';
              }
              if (!metaInfo.name().isEmpty()) {
                  prefix += metaInfo.name();
                  if (!prefix.endsWith('/'))
                      prefix += '/';
              }
              if (!dir.mkpath(prefix)) {
                  errString = tr("Failed to create directory %1").arg(prefix);
                  emit error();
                  return false;
              }

              foreach (const MetaInfoMultiFile &entry, metaInfo.multiFiles()) {
                  QString filePath = QFileInfo(prefix + entry.path).path();
                  if (!QFile::exists(filePath)) {
                      if (!dir.mkpath(filePath)) {
                          errString = tr("Failed to create directory %1").arg(filePath);
                          emit error();
                          return false;
                      }
                  }

                  QFile *file = new QFile(prefix + entry.path);
                  if (!file->open(QFile::ReadWrite)) {
                      errString = tr("Failed to open/create file %1: %2")
                                  .arg(file->fileName()).arg(file->errorString());
                      emit error();
                      delete file;
                      return false;
                  }

                  if (file->size() != entry.length) {
                      newFile = true;
                      if (!file->resize(entry.length)) {
                          errString = tr("Failed to resize file %1: %2")
                                      .arg(file->fileName()).arg(file->errorString());
                          emit error();
                          delete file;
                          return false;
                      }
                  }
                  fileSizes << file->size();
                  files << file;
                  file->close();

                  totalLength += entry.length;
              }

              sha1s = metaInfo.sha1Sums();
              pieceLength = metaInfo.pieceLength();
          }
          numPieces = sha1s.size();
          return true;
      }

      QByteArray FileManager::readBlock(int pieceIndex, int offset, int length)
      {
          QByteArray block;
          qint64 startReadIndex = (quint64(pieceIndex) * pieceLength) + offset;
          qint64 currentIndex = 0;

          for (int i = 0; !quit && i < files.size() && length > 0; ++i) {
              QFile *file = files[i];
              qint64 currentFileSize = fileSizes.at(i);
              if ((currentIndex + currentFileSize) > startReadIndex) {
                  if (!file->isOpen()) {
                      if (!file->open(QFile::ReadWrite)) {
                          errString = tr("Failed to read from file %1: %2")
                              .arg(file->fileName()).arg(file->errorString());
                          emit error();
                          break;
                      }
                  }

                  file->seek(startReadIndex - currentIndex);
                  QByteArray chunk = file->read(qMin<qint64>(length, currentFileSize - file->pos()));
                  file->close();

                  block += chunk;
                  length -= chunk.size();
                  startReadIndex += chunk.size();
                  if (length < 0) {
                      errString = tr("Failed to read from file %1 (read %3 bytes): %2")
                                  .arg(file->fileName()).arg(file->errorString()).arg(length);
                      emit error();
                      break;
                  }
              }
              currentIndex += currentFileSize;
          }
          return block;
      }

      bool FileManager::writeBlock(int pieceIndex, int offset, const QByteArray &data)
      {
          qint64 startWriteIndex = (qint64(pieceIndex) * pieceLength) + offset;
          qint64 currentIndex = 0;
          int bytesToWrite = data.size();
          int written = 0;

          for (int i = 0; !quit && i < files.size(); ++i) {
              QFile *file = files[i];
              qint64 currentFileSize = fileSizes.at(i);

              if ((currentIndex + currentFileSize) > startWriteIndex) {
                  if (!file->isOpen()) {
                      if (!file->open(QFile::ReadWrite)) {
                          errString = tr("Failed to write to file %1: %2")
                              .arg(file->fileName()).arg(file->errorString());
                          emit error();
                          break;
                      }
                  }

                  file->seek(startWriteIndex - currentIndex);
                  qint64 bytesWritten = file->write(data.constData() + written,
                                                    qMin<qint64>(bytesToWrite, currentFileSize - file->pos()));
                  file->close();

                  if (bytesWritten <= 0) {
                      errString = tr("Failed to write to file %1: %2")
                                  .arg(file->fileName()).arg(file->errorString());
                      emit error();
                      return false;
                  }

                  written += bytesWritten;
                  startWriteIndex += bytesWritten;
                  bytesToWrite -= bytesWritten;
                  if (bytesToWrite == 0)
                      break;
              }
              currentIndex += currentFileSize;
          }
          return true;
      }

      void FileManager::verifyFileContents()
      {
          // Verify all pieces the first time
          if (newPendingVerificationRequests.isEmpty()) {
              if (verifiedPieces.count(true) == 0) {
                  verifiedPieces.resize(sha1s.size());

                  int oldPercent = 0;
                  if (!newFile) {
                      int numPieces = sha1s.size();

                      for (int index = 0; index < numPieces; ++index) {
                          verifySinglePiece(index);

                          int percent = ((index + 1) * 100) / numPieces;
                          if (oldPercent != percent) {
                              emit verificationProgress(percent);
                              oldPercent = percent;
                          }
                      }
                  }
              }
              emit verificationDone();
              return;
          }

          // Verify all pending pieces
          foreach (int index, newPendingVerificationRequests)
              emit pieceVerified(index, verifySinglePiece(index));
      }

      bool FileManager::verifySinglePiece(int pieceIndex)
      {
          QByteArray block = readBlock(pieceIndex, 0, pieceLength);
          QByteArray sha1Sum = QCryptographicHash::hash(block, QCryptographicHash::Sha1);

          if (sha1Sum != sha1s.at(pieceIndex))
              return false;
          verifiedPieces.setBit(pieceIndex);
          return true;
      }

      void FileManager::wakeUp()
      {
          QMutexLocker locker(&mutex);
          wokeUp = false;
          cond.wakeOne();
      }