
/*
 * See the files COPYING and README for copyright information and how to reach 
 * the author.
 *
 *  $Id: process.c,v 1.3 2004/12/02 15:02:52 lordjaxom Exp $
 */

#include "jobs.h"
#include "process.h"
#include "pipe.h"
#include "menuburn.h"
#include "setup.h"
#include <vdr/interface.h>
#include <vdr/tools.h>
#include <vdr/videodir.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>

cBurnProcess::cBurnProcess(int NumSteps)
{
    char *path;
    asprintf(&path, "%s/.vdr-burn.XXXXXX", Datadir);
    mStatus = rsNone;
    mCmdline = NULL;
    mProcedure = NULL;
    mProcData = NULL;
    mActive = false;
    mProgress = 0;
    mNumSteps = NumSteps;
    mCurStep = 0;
    mNextStep = nsContinue;
    mSubStep = -1;
    mVerifying = false;
    mBurning = false;
    mIsPremastered = false;
    mBurnProgress = 0;
    mTempDir = mkdtemp(path);
    mLogFile = NULL;
    mJob = NULL;
}

cBurnProcess::~cBurnProcess()
{
    Stop();
    if (mLogFile) {
        fclose(mLogFile);
        mLogFile = NULL;
    }
    free(mCmdline);
    Execute("rm -rf %s", mTempDir);
    free(mTempDir);
}

void cBurnProcess::InitRepeat()
{
    mActive = false;
    mProgress = 0;
    mCurStep = 0;
    mNextStep = nsContinue;
    mSubStep = -1;
    mVerifying = false;
    mBurning = false;
    mBurnProgress = 0;
    mStatus = rsNone;
}

void cBurnProcess::Repeat()
{
    InitRepeat();
    Start();
}

void cBurnProcess::Stop(void)
{
    if (mActive) {
        mActive = false;
#if VDRVERSNUM < 10314
        WakeUp();
#endif
        Cancel(10);

        if (mStatus == rsNone) {
            mStatus = rsError;
            cBurnMenuBurn::NextJob();
        }
    }
}

void cBurnProcess::SetProgress(int SubSteps, int Percent)
{
    mProgress = mCurStep * 100 / mNumSteps +
            mSubStep * 100 / mNumSteps / SubSteps +
            Percent / mNumSteps / SubSteps;
}

void cBurnProcess::SetProgress(int SubSteps, int SubSub, int SubSubs,
        int Percent)
{
    mProgress = mCurStep * 100 / mNumSteps +
            mSubStep * 100 / mNumSteps / SubSteps +
            SubSub * 100 / mNumSteps / SubSteps / SubSubs +
            Percent / mNumSteps / SubSteps / SubSubs;
}

void cBurnProcess::SetCmdline(const char *Format, ...)
{
    va_list ap;
    va_start(ap, Format);
    vasprintf(&mCmdline, Format, ap);
    va_end(ap);
}

void cBurnProcess::SetLogfile(const char *Format, ...)
{
    char *path;
    va_list ap;
    if (mLogFile) {
        fclose(mLogFile);
    }
    va_start(ap, Format);
    vasprintf(&path, Format, ap);
    va_end(ap);
    mLogFile = fopen(path, "w");
    free(path);
}

void cBurnProcess::Execute(const char *Format, ...)
{
    char *cmd;
    va_list ap;
    va_start(ap, Format);
    vasprintf(&cmd, Format, ap);
    va_end(ap);
    if (mLogFile) {
        fprintf(mLogFile, "++ executing: sh -c '%s'\n", cmd);
        fflush(mLogFile);
    }
    Dprintf("executing: sh -c '%s'\n", cmd);
    SystemExec(cmd);
    free(cmd);
}

void cBurnProcess::MakeDirectory(const char *Format, ...)
{
    char *path;
    va_list ap;
    va_start(ap, Format);
    vasprintf(&path, Format, ap);
    va_end(ap);
    mkdir(path, 0700);
    free(path);
}

bool cBurnProcess::HasNext(void)
{
    mNextStep = nsContinue;
    ++mSubStep;
    if (Continue(mCurStep, mSubStep)) {
        switch (mNextStep) {
            case nsNext:
                SetProgress(mSubStep + 1, 100);
                ++mCurStep;
                // fall through
            case nsRetry:
                mSubStep = -1;
                break;

            case nsContinue:
                break;
        }
        return mCurStep < mNumSteps;
    }
    return false;
}

void cBurnProcess::PutLine(const char *Line)
{
    if (mLogFile) {
        fprintf(mLogFile, "%s\n", Line);
        fflush(mLogFile);
    }
    Process(mCurStep, mSubStep, Line);
}

void cBurnProcess::Action(void)
{
    mActive = true;
    isyslog("BURN: Subprocess watcher started (pid=%d)", getpid());

    while (mActive && HasNext()) {
        cBurnPipe pipe;

        mStatus = rsNone;

        if (mCmdline == NULL && mProcedure == NULL) {
            continue;
        }

        pipe.SetWorkdir(mTempDir);
        if (mCmdline != NULL) {
            if (!pipe.Launch(mCmdline)) {
                mActive = false;
                break;
            }
            if (mLogFile) {
                fprintf(mLogFile, " ++ started: sh -c '%s'\n", mCmdline);
                fflush(mLogFile);
            }
        } else if (mProcedure != NULL) {
            if (!pipe.Launch(mProcedure, mProcData)) {
                mActive = false;
                break;
            }
            if (mLogFile) {
                fprintf(mLogFile, " ++ started an internal procedure\n");
                fflush(mLogFile);
            }
        }
        free(mCmdline);
        mCmdline = NULL;
        mProcedure = NULL;

        while (mActive) {
            char *line;
            if (!pipe.GetLine(line)) {
                mActive = false;
                break;
            }
            if (line) {
                PutLine(line);
                free(line);
            } else {
                break;
            }
        }

        if (!pipe.Exit(mActive)) {
            mStatus = rsError;
            mActive = false;
        }
    }

    if (mActive && BurnSetup.VerifyAfterBurn && mJob &&
            (mJob->StoreMode() == smCreateBurn ||
            mJob->StoreMode() == smBurnISO) &&
            !cBurnMenuBurn::Confirm(tr("Skip DVD verification?")))
    {
        Verify();
    }

    if (mActive) {
        mActive = false;
        mProgress = 100;
        if (mStatus != rsError) {
            cBurnMenuBurn::Confirm(tr("DVD burn finished."));
        } else {
            cBurnMenuBurn::Confirm(tr("DVD burn failed."));
        }
    }
    if (mStatus != rsError) {
        mStatus = rsOk;
    }

    isyslog("BURN: Subprocess watcher stopped");

    cBurnMenuBurn::NextJob();
}

double cBurnProcess::SizeMB()
{
    if (mJob) {
        return mJob->SizeMB();
    }
    return 0.0;
}

void cBurnProcess::Verify()
{
    int pos = 0;
    FILE *f = fopen(DVDdev, "r");
    if (!f) {
        mStatus = rsError;
        cBurnMenuBurn::Confirm(tr("Unable to open DVD device."));
        return;
    }
    mVerifying = true;
    mCurStep = 0;
    mSubStep = 0;
    int oldNumSteps = mNumSteps;
    mNumSteps = 1;
    if (mLogFile) {
        fprintf(mLogFile, "Start verify\n");
        fflush(mLogFile);
    }
    while (mActive) {
        char buf[1024 * 64];
        pos += fread(buf, 1024, 64, f);
        if (ferror(f)) {
            if (mLogFile) {
                fprintf(mLogFile, "Verify failed\n");
                fflush(mLogFile);
            }
            mStatus = rsError;
            break;
        } else if (feof(f)) {
            if (mLogFile) {
                fprintf(mLogFile, "DVD ok\n");
                fflush(mLogFile);
            }
            mStatus = rsOk;
            break;
        } else {
            double total = SizeMB();
            if (total > 0) {
                mBurnProgress = (int)(100.0 * pos / (1024 * total));
                if (mBurnProgress > 100) {
                    mBurnProgress = 100;
                }
                SetProgress(1, mBurnProgress);
                if (!(pos % 2048) && mLogFile) {
                    fprintf(mLogFile, "%i MBytes %i%%\n", pos / 1024,
                            mBurnProgress);
                    fflush(mLogFile);
                }
            }
        }
    }
    if (mLogFile) {
        fflush(mLogFile);
    }
    mVerifying = false;
    mNumSteps = oldNumSteps;
    fclose(f);
}

void cBurnProcess::SetBurning(bool Burning, int Progress)
{
    mBurning = Burning;
    mBurnProgress = Progress;
    mIsPremastered = true;
}

void cBurnProcess::SetProcedure(BurnProc Procedure, void *Data)
{
    mProcedure = Procedure;
    mProcData = Data;
}

bool cBurnProcess::Burning(int &Progress) const
{
    if (mBurning) {
        Progress = mBurnProgress;
    }
    return mBurning;
}

bool cBurnProcess::Verifying(int &Progress) const
{
    if (mVerifying) {
        Progress = mBurnProgress;
    }
    return mVerifying;
}
