Qt Reference Documentation

filemanager.cpp Example File

network/torrent/filemanager.cpp
 /****************************************************************************
 **
 ** Copyright (C) 2015 The Qt Company Ltd.
 ** Contact: http://www.qt.io/licensing/
 **
 ** This file is part of the examples of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:BSD$
 ** 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();
             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());
                 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();
                 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();
                     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();
 }