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 =  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 & 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 & mutex);
    writeRequests < <  request;
    if  (! wokeUp) {
        wokeUp =  true ;
        QMetaObject :: invokeMethod(this ,  "wakeUp" ,  Qt :: QueuedConnection);
    }
}
void  FileManager:: verifyPiece(int  pieceIndex)
{
    QMutexLocker & mutex);
    pendingVerificationRequests < <  pieceIndex;
    startVerification =  true ;
    if  (! wokeUp) {
        wokeUp =  true ;
        QMetaObject :: invokeMethod(this ,  "wakeUp" ,  Qt :: QueuedConnection);
    }
}
int  FileManager:: pieceLengthAt(int  pieceIndex) const 
{
    QMutexLocker & mutex);
    return  (sha1s. size() = =  pieceIndex +  1 )
        ?  (totalLength %  pieceLength) : pieceLength;
}
QBitArray :: completedPieces() const 
{
    QMutexLocker & mutex);
    return  verifiedPieces;
}
void  FileManager:: setCompletedPieces(const  QBitArray & pieces)
{
    QMutexLocker & mutex);
    verifiedPieces =  pieces;
}
QString :: errorString() const 
{
    return  errString;
}
void  FileManager:: run()
{
    if  (! generateFiles())
        return ;
    do  {
        {
            // Go to sleep if there's nothing to do. 
            QMutexLocker & 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 =  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 & mutex);
    startVerification =  true ;
    cond . wakeOne();
}
bool FileManager:: generateFiles()
{
    numPieces =  - 1 ;
    // Set up the thread local data 
    if  (metaInfo. fileForm() = =  MetaInfo:: SingleFileForm) {
        QMutexLocker & mutex);
        MetaInfoSingleFile singleFile =  metaInfo. singleFile();
        QString if  (! destinationPath. isEmpty()) {
            prefix =  destinationPath;
            if  (! prefix. endsWith('/' ))
                prefix + =  '/' ;
            QDir if  (! dir. mkpath(prefix)) {
                errString =  tr("Failed to create directory %1" ). arg(prefix);
                emit  error();
                return  false ;
            }
        }
        QFile * file =  new  QFile +  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 & mutex);
        QDir QString 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 =  QFileInfo +  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 +  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 :: readBlock(int  pieceIndex,  int  offset,  int  length)
{
    QByteArray qint64 =  (quint64 *  pieceLength) +  offset;
    qint64 =  0 ;
    for  (int  i =  0 ; ! quit & &  i <  files. size() & &  length >  0 ; + + i) {
        QFile * file =  files[ i] ;
        qint64 =  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 =  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 =  (qint64 *  pieceLength) +  offset;
    qint64 =  0 ;
    int  bytesToWrite =  data. size();
    int  written =  0 ;
    for  (int  i =  0 ; ! quit & &  i <  files. size(); + + i) {
        QFile * file =  files[ i] ;
        qint64 =  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 =  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 =  readBlock(pieceIndex,  0 ,  pieceLength);
    QByteArray =  QCryptographicHash :: hash(block,  QCryptographicHash :: Sha1);
    if  (sha1Sum ! =  sha1s. at(pieceIndex))
        return  false ;
    verifiedPieces. setBit(pieceIndex);
    return  true ;
}
void  FileManager:: wakeUp()
{
    QMutexLocker & mutex);
    wokeUp =  false ;
    cond . wakeOne();
}