<commit_before>/* BEGIN_COMMON_COPYRIGHT_HEADER
 * (c)LGPL2+
 *
 * Flacon - audio File Encoder
 * https://github.com/flacon/flacon
 *
 * Copyright: 2017
 *   <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * END_COMMON_COPYRIGHT_HEADER */


#include "discpipline.h"

#include <QUuid>

#include "splitter.h"
#include "encoder.h"
#include "gain.h"
#include "cuecreator.h"
#include "copycover.h"
#include "project.h"
#include "inputaudiofile.h"
#include "profiles.h"

#include <QThread>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QCoreApplication>
#include <errno.h>


/************************************************
 *
 ************************************************/
class WorkerThread: public QThread
{
public:
    explicit WorkerThread(Worker *worker, QObject *parent = nullptr):
        QThread(parent),
        mWorker(worker)
    {
        worker->moveToThread(this);
    }

    virtual ~WorkerThread()
    {
        quit();
        if (!wait(3000))
        {
            qWarning() << "Can't quit from thread" << mWorker;
            this->terminate();
            if (!wait(3000))
                qWarning() << "Can't terminate from thread" << mWorker;
        }
        mWorker->deleteLater();
    }

    void run()
    {
        mWorker->run();
    }


private:
    Worker *mWorker;
};


struct GainRequest
{
    const Track *track;
    QString inputFile;
};

struct EncoderRequest
{
    const Track *track;
    QString inputFile;
    QString outFile;
};

/************************************************
 *
 ************************************************/
class DiscPipeline::Data
{
public:
    Data(const Converter::Job &job, const Profile &profile, DiscPipeline *pipeline):
        pipeline(pipeline),
        job(job),
        profile(profile)
    {
    }

    ~Data()
    {
        delete workDir;
    }

    DiscPipeline *pipeline;
    Converter::Job job;
    const Profile profile;
    bool needStartSplitter = true;
    QHash<const Track*, TrackState> trackStates;
    QList<EncoderRequest> encoderRequests;
    QList<GainRequest> gainRequests;
    bool interrupted = false;
    PreGapType preGapType = PreGapType::Skip;
    bool extractPregap = false;
    int trackCount = 0;
    CoverMode coverMode = CoverMode::Disable;
    int coverImageSize = 0;
    QString tmpDirName;
    QTemporaryDir *workDir = nullptr;
    QVector<WorkerThread*> threads;

    bool init();
    void interrupt(TrackState state);
    void startSplitterThread();
    void startEncoderThread(const EncoderRequest &req);
    void startTrackGainThread(const GainRequest &req);
    void startAlbumGainThread(QList<GainRequest> &reqs);
    bool createDir(const QString &dirName) const;
    bool createCue() const;
    bool copyCoverImage() const;
};



/************************************************
 *
 ************************************************/
bool DiscPipeline::Data::createDir(const QString &dirName) const
{
    QDir dir(dirName);

    if (! dir.mkpath("."))
    {
        QString msg = QObject::tr("I can't create directory \"%1\".").arg(dir.path());
        QString err = strerror(errno);
        Messages::error(msg + "<br><br>" + err);
        return false;
    }

    if (!QFileInfo(dir.path()).isWritable())
    {
        QString msg = QObject::tr("I can't write to directory \"%1\".").arg(dir.path());
        QString err = strerror(errno);
        Messages::error(msg + "<br><br>" + err);
        return false;
    }

    return true;
}


/************************************************
 *
 ************************************************/
DiscPipeline::DiscPipeline(const Converter::Job &job, const Profile &profile, QObject *parent) :
    QObject(parent),
    mData(new Data(job, profile, this))
{
    mData->preGapType = mData->profile.isCreateCue() ? mData->profile.preGapType() : PreGapType::Skip;

    // If the first track starts with zero second, doesn't make sense to create pregap track.
    mData->extractPregap = (mData->preGapType == PreGapType::ExtractToFile &&
                            job.disc->track(0)->cueIndex(1).milliseconds() > 0);

    mData->trackCount = mData->job.tracks.count();
    if (mData->extractPregap)
        mData->trackCount += 1;
}


/************************************************

 ************************************************/
DiscPipeline::~DiscPipeline()
{
    delete mData;
}


/************************************************
 *
 ************************************************/
bool DiscPipeline::init()
{
    return mData->init();
}


/************************************************
 *
 ************************************************/
bool DiscPipeline::Data::init()
{
    QString dir = !tmpDirName.isEmpty() ?
                   tmpDirName :
                   QFileInfo(job.tracks.first()->resultFilePath()).dir().absolutePath();

    qDebug() << "Create tmp dir" << dir;

    if (!createDir(dir)) {
        qWarning() << "Can't create tmp dir" << dir;
        return false;
    }

    workDir = new QTemporaryDir(QString("%1/flacon").arg(dir));
    workDir->setAutoRemove(true);

    foreach (const Track *track, job.tracks) {
        trackStates.insert(track, TrackState::NotRunning);

        if (!createDir(QFileInfo(track->resultFilePath()).absoluteDir().path())) {
            qWarning() << "Can't create out dir" << QFileInfo(track->resultFilePath()).absoluteDir().path();
            return false;
        }
    }

    return true;
}


/************************************************
 CREATE WORKER CHAINS
 ************************************************
              +--> Encoder ---> Track gain -->+
   Splitter ->+            ...                +-> Album gain --> this
              +--> Encoder ---> Track gain -->+
<commit_msg>Rename temporary dir. Issue #125: temp file names and locations<commit_after>/* BEGIN_COMMON_COPYRIGHT_HEADER
 * (c)LGPL2+
 *
 * Flacon - audio File Encoder
 * https://github.com/flacon/flacon
 *
 * Copyright: 2017
 *   <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * END_COMMON_COPYRIGHT_HEADER */


#include "discpipline.h"

#include <QUuid>

#include "splitter.h"
#include "encoder.h"
#include "gain.h"
#include "cuecreator.h"
#include "copycover.h"
#include "project.h"
#include "inputaudiofile.h"
#include "profiles.h"

#include <QThread>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QCoreApplication>
#include <errno.h>


/************************************************
 *
 ************************************************/
class WorkerThread: public QThread
{
public:
    explicit WorkerThread(Worker *worker, QObject *parent = nullptr):
        QThread(parent),
        mWorker(worker)
    {
        worker->moveToThread(this);
    }

    virtual ~WorkerThread()
    {
        quit();
        if (!wait(3000))
        {
            qWarning() << "Can't quit from thread" << mWorker;
            this->terminate();
            if (!wait(3000))
                qWarning() << "Can't terminate from thread" << mWorker;
        }
        mWorker->deleteLater();
    }

    void run()
    {
        mWorker->run();
    }


private:
    Worker *mWorker;
};


struct GainRequest
{
    const Track *track;
    QString inputFile;
};

struct EncoderRequest
{
    const Track *track;
    QString inputFile;
    QString outFile;
};

/************************************************
 *
 ************************************************/
class DiscPipeline::Data
{
public:
    Data(const Converter::Job &job, const Profile &profile, DiscPipeline *pipeline):
        pipeline(pipeline),
        job(job),
        profile(profile)
    {
    }

    ~Data()
    {
        delete workDir;
    }

    DiscPipeline *pipeline;
    Converter::Job job;
    const Profile profile;
    bool needStartSplitter = true;
    QHash<const Track*, TrackState> trackStates;
    QList<EncoderRequest> encoderRequests;
    QList<GainRequest> gainRequests;
    bool interrupted = false;
    PreGapType preGapType = PreGapType::Skip;
    bool extractPregap = false;
    int trackCount = 0;
    CoverMode coverMode = CoverMode::Disable;
    int coverImageSize = 0;
    QString tmpDirName;
    QTemporaryDir *workDir = nullptr;
    QVector<WorkerThread*> threads;

    bool init();
    void interrupt(TrackState state);
    void startSplitterThread();
    void startEncoderThread(const EncoderRequest &req);
    void startTrackGainThread(const GainRequest &req);
    void startAlbumGainThread(QList<GainRequest> &reqs);
    bool createDir(const QString &dirName) const;
    bool createCue() const;
    bool copyCoverImage() const;
};



/************************************************
 *
 ************************************************/
bool DiscPipeline::Data::createDir(const QString &dirName) const
{
    QDir dir(dirName);

    if (! dir.mkpath("."))
    {
        QString msg = QObject::tr("I can't create directory \"%1\".").arg(dir.path());
        QString err = strerror(errno);
        Messages::error(msg + "<br><br>" + err);
        return false;
    }

    if (!QFileInfo(dir.path()).isWritable())
    {
        QString msg = QObject::tr("I can't write to directory \"%1\".").arg(dir.path());
        QString err = strerror(errno);
        Messages::error(msg + "<br><br>" + err);
        return false;
    }

    return true;
}


/************************************************
 *
 ************************************************/
DiscPipeline::DiscPipeline(const Converter::Job &job, const Profile &profile, QObject *parent) :
    QObject(parent),
    mData(new Data(job, profile, this))
{
    mData->preGapType = mData->profile.isCreateCue() ? mData->profile.preGapType() : PreGapType::Skip;

    // If the first track starts with zero second, doesn't make sense to create pregap track.
    mData->extractPregap = (mData->preGapType == PreGapType::ExtractToFile &&
                            job.disc->track(0)->cueIndex(1).milliseconds() > 0);

    mData->trackCount = mData->job.tracks.count();
    if (mData->extractPregap)
        mData->trackCount += 1;
}


/************************************************

 ************************************************/
DiscPipeline::~DiscPipeline()
{
    delete mData;
}


/************************************************
 *
 ************************************************/
bool DiscPipeline::init()
{
    return mData->init();
}


/************************************************
 *
 ************************************************/
bool DiscPipeline::Data::init()
{
    QString dir = !tmpDirName.isEmpty() ?
                   tmpDirName :
                   QFileInfo(job.tracks.first()->resultFilePath()).dir().absolutePath();

    qDebug() << "Create tmp dir" << dir;

    if (!createDir(dir)) {
        qWarning() << "Can't create tmp dir" << dir;
        return false;
    }

    workDir = new QTemporaryDir(QString("%1/tmp").arg(dir));
    workDir->setAutoRemove(true);

    foreach (const Track *track, job.tracks) {
        trackStates.insert(track, TrackState::NotRunning);

        if (!createDir(QFileInfo(track->resultFilePath()).absoluteDir().path())) {
            qWarning() << "Can't create out dir" << QFileInfo(track->resultFilePath()).absoluteDir().path();
            return false;
        }
    }

    return true;
}


/************************************************
 CREATE WORKER CHAINS
 ************************************************
              +--> Encoder ---> Track gain -->+
   Splitter ->+            ...                +-> Album gain --> this
              +--> Encoder ---> Track gain -->+

{'fraction_non_alphanumeric': 0.23398350165754375, 'fraction_numerical': 0.005627939249094133, 'mean_word_length': 3.299635399403381, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 101, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7626847', 'n_tokens_mistral': 3723, 'n_tokens_neox': 3558, 'n_words': 927}