root/src/apps/haikudepot/process/AbstractProcess.cpp
/*
 * Copyright 2018-2025, Andrew Lindesay <apl@lindesay.co.nz>.
 * All rights reserved. Distributed under the terms of the MIT License.
 */
#include "AbstractProcess.h"

#include <errno.h>
#include <string.h>
#include <unistd.h>

#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <Locker.h>
#include <StopWatch.h>

#include "HaikuDepotConstants.h"
#include "Logger.h"
#include "ProcessListener.h"


static const bigtime_t kProcessUpdateMinimumDelay = 250000;


AbstractProcess::AbstractProcess()
        :
        fLock(),
        fListener(NULL),
        fWasStopped(false),
        fProcessState(PROCESS_INITIAL),
        fErrorStatus(B_OK),
        fDurationSeconds(0.0),
        fLastProgressUpdate(0)
{
}


AbstractProcess::~AbstractProcess()
{
}


void
AbstractProcess::SetListener(ProcessListener* listener)
{
        if (fListener != listener) {
                AutoLocker<BLocker> locker(&fLock);
                fListener = listener;
        }
}


status_t
AbstractProcess::Run()
{
        ProcessListener* listener;

        {
                AutoLocker<BLocker> locker(&fLock);

                if (ProcessState() != PROCESS_INITIAL) {
                        HDINFO("cannot start process as it is not idle");
                        return B_NOT_ALLOWED;
                }

                if (fWasStopped) {
                        HDINFO("cannot start process as it was stopped");
                        return B_CANCELED;
                }

                fProcessState = PROCESS_RUNNING;
                listener = fListener;
        }

        if (listener != NULL)
                listener->ProcessChanged();

        BStopWatch stopWatch("process", true);
        status_t runResult = RunInternal();
        fDurationSeconds = static_cast<double>(stopWatch.ElapsedTime()) / 1000000.0;

        if (runResult != B_OK)
                HDERROR("[%s] an error has arisen; %s", Name(), strerror(runResult));

        {
                AutoLocker<BLocker> locker(&fLock);
                fProcessState = PROCESS_COMPLETE;
                fErrorStatus = runResult;
        }

        // this process may be part of a larger bulk-load process and
        // if so, the process orchestration needs to know when this
        // process has completed.
        if (listener != NULL)
                listener->ProcessChanged();

        return runResult;
}


bool
AbstractProcess::WasStopped()
{
        AutoLocker<BLocker> locker(&fLock);
        return fWasStopped;
}


status_t
AbstractProcess::ErrorStatus()
{
        AutoLocker<BLocker> locker(&fLock);
        return fErrorStatus;
}


/*!     This method will stop the process.  The actual process may carry on to
        perform some tidy-ups on its thread so this does not stop the thread or
        change the state of the process; just indicates to the running thread that
        it should stop.  If it has not yet been started then it will be put into
        finished state.
*/

status_t
AbstractProcess::Stop()
{
        status_t result = B_CANCELED;
        ProcessListener* listener = NULL;

        {
                AutoLocker<BLocker> locker(&fLock);

                if (!fWasStopped) {
                        fWasStopped = true;
                        result = StopInternal();

                        if (fProcessState == PROCESS_INITIAL) {
                                listener = fListener;
                                fProcessState = PROCESS_COMPLETE;
                        }
                }
        }

        if (listener != NULL)
                listener->ProcessChanged();

        return result;
}


status_t
AbstractProcess::StopInternal()
{
        return B_NOT_ALLOWED;
}


bool
AbstractProcess::IsRunning()
{
        return ProcessState() == PROCESS_RUNNING;
}


process_state
AbstractProcess::ProcessState()
{
        AutoLocker<BLocker> locker(&fLock);
        return fProcessState;
}


float
AbstractProcess::Progress()
{
        return kProgressIndeterminate;
}


void
AbstractProcess::_NotifyChanged()
{
        ProcessListener* listener = NULL;
        {
                AutoLocker<BLocker> locker(&fLock);
                listener = fListener;
        }
        if (listener != NULL)
                listener->ProcessChanged();
}


BString
AbstractProcess::LogReport()
{
        BString result;
        AutoLocker<BLocker> locker(&fLock);
        result.SetToFormat("%s [%c] %6.3f", Name(), _ProcessStateIdentifier(ProcessState()),
                fDurationSeconds);
        return result;
}


/*!     This method should be called before processing any progress. It will prevent
        progress updates from coming through too frequently.
*/
bool
AbstractProcess::_ShouldProcessProgress()
{
        bigtime_t now = system_time();

        if (now - fLastProgressUpdate < kProcessUpdateMinimumDelay)
                return false;

        fLastProgressUpdate = now;
        return true;
}


/*static*/ char
AbstractProcess::_ProcessStateIdentifier(process_state value)
{
        switch (value) {
                case PROCESS_INITIAL:
                        return 'I';
                case PROCESS_RUNNING:
                        return 'R';
                case PROCESS_COMPLETE:
                        return 'C';
                default:
                        return '?';
        }
}