Home · All Classes · Main Classes · Grouped Classes · Modules · Functions

filemanager.cpp Example File
network/torrent/filemanager.cpp

 /****************************************************************************
 **
 ** Copyright (C) 2004-2008 Trolltech ASA. All rights reserved.
 **
 ** This file is part of the example classes of the Qt Toolkit.
 **
 ** Licensees holding a valid Qt License Agreement may use this file in
 ** accordance with the rights, responsibilities and obligations
 ** contained therein.  Please consult your licensing agreement or
 ** contact sales@trolltech.com if any conditions of this licensing
 ** agreement are not clear to you.
 **
 ** Further information about Qt licensing is available at:
 ** http://www.trolltech.com/products/qt/licensing.html or by
 ** contacting info@trolltech.com.
 **
 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 **
 ****************************************************************************/

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

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

 extern "C" {
 #include "3rdparty/sha1.h"
 }

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

     SHA1Context sha;
     SHA1Reset(&sha);
     SHA1Input(&sha, (const unsigned char *)block.constData(), block.size());
     SHA1Result(&sha);

     QByteArray sha1Sum(20, ' ');
     unsigned char *digest = (unsigned char *)sha.Message_Digest;
     for (int i = 0; i < 5; ++i) {
 #if Q_BYTE_ORDER == Q_BIG_ENDIAN
         sha1Sum[i * 4 + 3] = digest[i * 4 + 3];
         sha1Sum[i * 4 + 2] = digest[i * 4 + 2];
         sha1Sum[i * 4 + 1] = digest[i * 4 + 1];
         sha1Sum[i * 4 + 0] = digest[i * 4 + 0];
 #else
         sha1Sum[i * 4 + 0] = digest[i * 4 + 3];
         sha1Sum[i * 4 + 1] = digest[i * 4 + 2];
         sha1Sum[i * 4 + 2] = digest[i * 4 + 1];
         sha1Sum[i * 4 + 3] = digest[i * 4 + 0];
 #endif
     }

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

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


Copyright © 2008 Trolltech Trademarks
Qt 4.3.5