#include "ListThread.h"
#include <QStorageInfo>
#include <QtGlobal>
#include <algorithm>
#include "../../../cpp11addition.h"

// TransferThreadImpl typedef comes from ListThread.h
#ifdef Q_OS_LINUX
#include <sys/sysinfo.h>
#endif
#ifdef Q_OS_WIN32
#include <sysinfoapi.h>
#endif

ListThread::ListThread(FacilityInterface * facilityInterface) :
    numberOfInodeOperation(0),
    sourceDriveMultiple(false),
    destinationDriveMultiple(false),
    destinationFolderMultiple(false),
    stopIt(false),
    numberOfTransferIntoToDoList(0),
    bytesToTransfer(0),
    bytesTransfered(0),
    #ifdef ULTRACOPIER_PLUGIN_RSYNC
    rsync(false),
    #endif
    idIncrementNumber(1),
    actualRealByteTransfered(0),
    alwaysDoThisActionForFolderExists(FolderExists_NotSet),//was uninitialised: read in newScanThread() (ListThreadScan.cpp) before any setFolderCollision()
    checkDestinationFolderExists(false),
    parallelizeIfSmallerThan(1024),
    moveTheWholeFolder(false),
    deletePartiallyTransferredFiles(true),
    inodeThreads(1),
    renameTheOriginalDestination(false),
    checkDiskSpace(true),
    buffer(false),
    checksum(false),
    followTheStrictOrder(true),
    ignoreBlackList(false),
    mode(Ultracopier::CopyMode::Copy),
    forcedMode(false),
    actionToDoListTransfer_count(0),
    actionToDoListInode_count(0),
    doTransfer(false),
    doInode(false),
    oversize(0),
    currentProgression(0),
    copiedSize(0),
    totalSize(0),
    localOverSize(0),
    doRightTransfer(false),
    keepDate(false),
    coalesceSourceStat(true),
    os_spec_flags(true),
    native_copy(false),
    mkFullPath(false),
    alwaysDoThisActionForFileExists(FileExists_NotSet),
    speedLimitation(0),
    returnBoolToCopyEngine(true)
{
    actionToDoListTransferRemoved=0;
    #ifdef ULTRACOPIER_PLUGIN_DEBUG
    debug_pos=0;
    debug_total=0;
    #endif
    #if (defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)) && defined(ULTRACOPIER_PLUGIN_DEBUG)
    opShadowSched=nullptr;
    opShadowActive=false;
    opShadowViolations=0;
    opShadowChecks=0;
    opShadowSummaryLogged=false;
    #endif
    moveToThread(this);
    start(HighPriority);
    this->facilityInterface=facilityInterface;
    #ifdef ULTRACOPIER_PLUGIN_SPEED_SUPPORT
    clockForTheCopySpeed            = NULL;
    multiForBigSpeed                = 0;
    blockSize                       = 65536;
    blockSizeAfterSpeedLimitation   = blockSize;
    #endif
    putInPause                      = false;
    autoStart=true;
    retryScheduler                  = NULL;//created+started on this thread in run()
    safetyStallTicks                = 0;

    //can't be static into WriteThread, linked by instance then by ListThread
    #if defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)
    writeFileList=new QMultiHash<QString,TransferThreadPipelined *>();
    #else
    writeFileList=new QMultiHash<QString,WriteThread *>();
    #endif
    writeFileListMutex=new QMutex();

    #ifdef ULTRACOPIER_PLUGIN_DEBUG_WINDOW
    if(!connect(&timerUpdateDebugDialog,&QTimer::timeout,this,&ListThread::timedUpdateDebugDialog))
        abort();
    timerUpdateDebugDialog.start(ULTRACOPIER_PLUGIN_DEBUG_WINDOW_TIMER);
    #endif
    if(!connect(this,           &ListThread::tryCancel,							this,&ListThread::cancel,                               Qt::QueuedConnection))
        abort();
    if(!connect(this,           &ListThread::askNewTransferThread,				this,&ListThread::createTransferThread,					Qt::QueuedConnection))
        abort();
    if(!connect(&mkPathQueue,	&MkPath::firstFolderFinish,						this,&ListThread::mkPathFirstFolderFinish,				Qt::QueuedConnection))
        abort();
    if(!connect(&mkPathQueue,	&MkPath::errorOnFolder,							this,&ListThread::mkPathErrorOnFolder,                  Qt::QueuedConnection))
        abort();
    if(!connect(this,           &ListThread::send_syncTransferList,				this,&ListThread::syncTransferList_internal,			Qt::QueuedConnection))
        abort();
    if(!connect(this,           &ListThread::exportTransferListSend,			this,&ListThread::exportTransferListInternal,           Qt::QueuedConnection))
        abort();
    if(!connect(this,           &ListThread::importTransferListSend,			this,&ListThread::importTransferListInternal,			Qt::QueuedConnection))
        abort();
    #ifdef ULTRACOPIER_PLUGIN_DEBUG
    if(!connect(&mkPathQueue,	&MkPath::debugInformation,						this,&ListThread::debugInformation,	Qt::QueuedConnection))
        abort();
    if(!connect(&driveManagement,&DriveManagement::debugInformation,			this,&ListThread::debugInformation,	Qt::QueuedConnection))
        abort();
    #endif // ULTRACOPIER_PLUGIN_DEBUG

    emit askNewTransferThread();
    mkpathTransfer.release();

    int64_t MBMem=100;
    #ifdef Q_OS_WIN32
    MEMORYSTATUSEX memoryStatus;
    if(GlobalMemoryStatusEx(&memoryStatus))
        MBMem=memoryStatus.ullTotalPhys/1024;
    #endif
    #ifdef Q_OS_LINUX
    struct sysinfo info;
    if(sysinfo(&info)==0)
        MBMem=info.totalhigh*info.mem_unit/1024/1024;
    #endif
    #if !defined(ULTRACOPIER_PLUGIN_IO_URING) && !defined(ULTRACOPIER_PLUGIN_WINIOCP)
    if(MBMem<1024)
        WriteThread::numberOfBlock=4+MBMem*0.12;
    else
        WriteThread::numberOfBlock=ULTRACOPIER_PLUGIN_DEFAULT_PARALLEL_NUMBER_OF_BLOCK;
    #endif
}

ListThread::~ListThread()
{
    emit tryCancel();
    waitCancel.acquire();
    quit();
    wait();
    // ListThread's own write-collision list/mutex (allocated in the ctor) are never shared to a
    // transfer thread (each thread gets its own in createTransferThread), so free them here.
    delete writeFileList;       writeFileList=nullptr;
    delete writeFileListMutex;  writeFileListMutex=nullptr;
    #if (defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)) && defined(ULTRACOPIER_PLUGIN_DEBUG)
    if(opShadowSched!=nullptr && !opShadowSummaryLogged)
        ULTRACOPIER_DEBUGCONSOLE(opShadowViolations==0?Ultracopier::DebugLevel_Notice:Ultracopier::DebugLevel_Critical,
            QStringLiteral("OPSHADOW summary: nodes=%1 checks=%2 violations=%3").arg(opShadowSched->opCount())
            .arg(opShadowChecks).arg(opShadowViolations).toStdString());
    delete opShadowSched; opShadowSched=nullptr;
    #endif
}

#ifndef ULTRACOPIER_PLUGIN_ACTIONLIST_COMPACT_THRESHOLD
// Drop dead slots once this many tombstones pile up. Absolute (not proportional)
// so the scheduler's front-skip scan never walks more than this many dead slots,
// while keeping compaction rare (amortised O(1) per removal).
#define ULTRACOPIER_PLUGIN_ACTIONLIST_COMPACT_THRESHOLD 512
#endif

int64_t ListThread::indexOfActionToDoTransfer(const uint64_t &id) const
{
    // Linear scan skipping tombstones. Transfers complete roughly FIFO, so the
    // match sits within the first (live-running + <=threshold dead) slots, which
    // compaction keeps bounded. The scan was never the O(n^2) cost: the erase was.
    for(size_t i=0;i<actionToDoListTransfer.size();++i)
        if(!actionToDoListTransfer.at(i)->removed && actionToDoListTransfer.at(i)->id==id)
            return (int64_t)i;
    return -1;
}

void ListThread::rebuildActionToDoTransferIndex()
{
    // No separate index to maintain: find is the linear, tombstone-skipping scan
    // above. Kept as a hook so callers that reorder the vector stay self-documenting.
}

void ListThread::compactActionToDoListTransfer()
{
    if(actionToDoListTransferRemoved==0)
        return;
    actionToDoListTransfer.erase(
        std::remove_if(actionToDoListTransfer.begin(),actionToDoListTransfer.end(),
            [](const std::unique_ptr<ActionToDoTransfer> &it){return it->removed;}),
        actionToDoListTransfer.end());
    actionToDoListTransferRemoved=0;
}

void ListThread::compactActionToDoListTransferIfNeeded()
{
    if(actionToDoListTransferRemoved>=ULTRACOPIER_PLUGIN_ACTIONLIST_COMPACT_THRESHOLD)
        compactActionToDoListTransfer();
}

void ListThread::tombstoneActionToDoTransferAt(const size_t &index)
{
    // index must be a live slot. O(1): mark removed + bump the tombstone count.
    actionToDoListTransfer.at(index)->removed=true;
    actionToDoListTransferRemoved++;
    compactActionToDoListTransferIfNeeded();
}

bool ListThread::actionToDoListTransferEmpty() const
{
    // logically empty when every physical slot is a tombstone (or there are none)
    return actionToDoListTransfer.size()==actionToDoListTransferRemoved;
}

//transfer is finished
void ListThread::transferInodeIsClosed()
{
    if(stopIt)
    {
        checkIfReadyToCancel();
        return;
    }
    numberOfInodeOperation--;
    #ifdef ULTRACOPIER_PLUGIN_DEBUG_SCHEDULER
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfInodeOperation: "+std::to_string(numberOfInodeOperation));
    #endif
    TransferThreadImpl *temp_transfer_thread=qobject_cast<TransferThreadImpl *>(QObject::sender());
    if(temp_transfer_thread==NULL)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"transfer thread not located!");
        return;
    }
    if(overCheckUsedThread.find(temp_transfer_thread)==overCheckUsedThread.cend())
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"transfer thread not located in overcheck");
    else
        overCheckUsedThread.erase(temp_transfer_thread);
    if(putAtBottomAfterError.find(temp_transfer_thread)!=putAtBottomAfterError.cend())
    {
        // The put-to-end attempt has now drained (this is its postOperationStopped). Only NOW make
        // the thread reusable: reset its transferId and drop it from the set (it deliberately kept
        // the id so no concurrent pump could reuse it before this point), then reschedule.
        temp_transfer_thread->transferId=0;
        temp_transfer_thread->transferSize=0;
        putAtBottomAfterError.erase(temp_transfer_thread);
        // If this put-to-end drain EMPTIED the transfer list, the deferred folder-date (SyncDate)
        // actions parked in actionToDoListInode_afterTheTransfer must be merged into the active inode
        // list -- the SAME merge the normal-completion path does below (see the isFound branch). Without
        // it, a job whose LAST transfer leaves via put-to-end strands every folder finalization forever:
        // all bytes copied, only directories pending, CPU ~0%, never Idle -- the end-of-copy HANG seen
        // copying a tree of un-creatable symlinks (Windows 1314 ERROR_PRIVILEGE_NOT_HELD, given up after
        // the retry cap). safetyReschedule() does not inspect afterTheTransfer, so nothing else repairs it.
        if(actionToDoListTransferEmpty())
        {
            actionToDoListInode.insert(actionToDoListInode.cbegin(),actionToDoListInode_afterTheTransfer.cbegin(),actionToDoListInode_afterTheTransfer.cend());
            actionToDoListInode_afterTheTransfer.clear();
        }
        doNewActions_inode_manipulation();
        doNewActions_start_transfer();
        // A put-to-end attempt drains through THIS early-return path, which bypassed the all-lists-empty
        // updateTheStatus() of the normal-completion path below. If the reschedule above queued no
        // further work (the deferred file finally copied -- or was given up -- and nothing else is
        // pending), the job is DONE but no Idle was ever emitted: the engine's progress timers keep
        // ticking and the copy never signals completion (the transient_sector put-to-end-recovery hang).
        // Mirror the normal path's completion check here (idempotent: only fires when truly empty).
        if(actionToDoListTransferEmpty() && actionToDoListInode.empty() && actionToDoListInode_afterTheTransfer.empty())
            updateTheStatus();
        return;
    }
    bool isFound=false;
    #ifdef ULTRACOPIER_PLUGIN_DEBUG
    int countLocalParse=0;
    #endif
    if(temp_transfer_thread->getStat()!=TransferStat_Idle)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"transfer thread not idle!");
        return;
    }
    // O(1) lookup by transferId (was an O(n) linear scan), then O(1) tombstone
    // removal (was an O(n) vector erase) -> the per-file work on this serial
    // scheduler drops from O(n) to O(1) amortised.
    const int64_t foundIndex=indexOfActionToDoTransfer(temp_transfer_thread->transferId);
    unsigned int int_for_internal_loop=(foundIndex>=0)?(unsigned int)foundIndex:0;
    if(foundIndex>=0)
    {
            #ifdef ULTRACOPIER_PLUGIN_DEBUG
            std::string threadidstring="?";
            {
                size_t index=0;
                while(index<transferThreadList.size())
                {
                    if(transferThreadList.at(index)==temp_transfer_thread)
                    {
                        threadidstring=std::to_string(index);
                        break;
                    }
                    index++;
                }
            }
            #endif
            ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("[%1] have finish, put at idle; for id: %2").arg(QString::fromStdString(threadidstring))
                                     .arg((uint64_t)temp_transfer_thread->transferId).toStdString()+" "+TransferThread::internalStringTostring(temp_transfer_thread->getDestinationPath()));
            Ultracopier::ReturnActionOnCopyList newAction;
            newAction.type=Ultracopier::RemoveItem;
            newAction.userAction.moveAt=0;
            newAction.addAction=actionToDoTransferToItemOfCopyList(*actionToDoListTransfer.at(int_for_internal_loop));
            newAction.userAction.position=int_for_internal_loop;
            actionDone.push_back(newAction);
            /// \todo check if item is at the right thread
            const int64_t transferTime=temp_transfer_thread->transferTime();
            if(transferTime>=0 && temp_transfer_thread->haveStartTime)
            {
                timeToTransfer.push_back(std::pair<uint64_t,uint32_t>(temp_transfer_thread->transferSize,transferTime));
                temp_transfer_thread->haveStartTime=false;
            }
            tombstoneActionToDoTransferAt(int_for_internal_loop);
            #if (defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)) && defined(ULTRACOPIER_PLUGIN_DEBUG)
            opShadowCompleteFile(temp_transfer_thread->transferId);// STAGE 1b.0 shadow observer hook 1
            #endif
            ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("actionToDoListTransfer.size(): %1, actionToDoListInode: %2, actionToDoListInode_afterTheTransfer: %3").arg(actionToDoListTransfer.size()).arg(actionToDoListInode.size()).arg(actionToDoListInode_afterTheTransfer.size()).toStdString());
            if(actionToDoListTransferEmpty() && actionToDoListInode.empty() && actionToDoListInode_afterTheTransfer.empty())
                updateTheStatus();

            //add the current size of file, to general size because it's finish
            copiedSize=temp_transfer_thread->copiedSize();
            if(copiedSize>(qint64)temp_transfer_thread->transferSize)
            {
                oversize=copiedSize-temp_transfer_thread->transferSize;
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"add oversize of: "+std::to_string(oversize));
                bytesToTransfer+=oversize;
                bytesTransfered+=oversize;
            }
            bytesTransfered+=temp_transfer_thread->transferSize;

            ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"["+threadidstring+"] transferId=0 currentTransferThread->transferId: "+
                                     std::to_string(temp_transfer_thread->transferId)+" on thread "+std::to_string((quint64)QThread::currentThread()));
            temp_transfer_thread->transferId=0;
            temp_transfer_thread->transferSize=0;
            #ifdef ULTRACOPIER_PLUGIN_DEBUG
            countLocalParse++;
            #endif
            isFound=true;
            if(actionToDoListTransferEmpty())
            {
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"actionToDoListTransfer==0");
                actionToDoListInode.insert(actionToDoListInode.cbegin(),actionToDoListInode_afterTheTransfer.cbegin(),actionToDoListInode_afterTheTransfer.cend());
                actionToDoListInode_afterTheTransfer.clear();
                doNewActions_inode_manipulation();
                doNewActions_start_transfer();
            }
    }
    if(isFound)
    {
        //deleteTransferThread();//why? generate use after free!
    }
    else
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,QStringLiteral("unable to found item into the todo list, id: %1, index: %2").arg((uint64_t)temp_transfer_thread->transferId).arg(int_for_internal_loop).toStdString());
        temp_transfer_thread->transferId=0;
        temp_transfer_thread->transferSize=0;
    }
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("countLocalParse: %1, actionToDoList.size(): %2").arg(countLocalParse).arg(actionToDoListTransfer.size()).toStdString());
    #ifdef ULTRACOPIER_PLUGIN_DEBUG
    if(countLocalParse!=1)
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"countLocalParse != 1");
    #endif
    doNewActions_inode_manipulation();
    doNewActions_start_transfer();
}

/** \brief put the current file at bottom in case of error
\note ONLY IN CASE OF ERROR

transferInodeIsClosed() will be call after, then will do:

BUT

this function call transfer->skip();
not put at bottom, remove from to do list
*/
void ListThread::transferPutAtBottom()
{
    TransferThreadImpl *transfer=qobject_cast<TransferThreadImpl *>(QObject::sender());
    if(transfer==NULL)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"transfer thread not located!");
        return;
    }
    bool isFound=false;
    #ifdef ULTRACOPIER_PLUGIN_DEBUG
    int countLocalParse=0;
    #endif
    // Requeue exactly like every other removal path: tombstone in place + append a fresh,
    // not-running COPY (no compact/erase reshuffle of the live vector -- that destroyed the
    // unique_ptr slots in-flight siblings depend on and dropped a good file, e.g. over_1mib.dat,
    // when a *different* file hit a read error). A per-file deferCount bounds the retries: a
    // transiently-failing sector (reads on retry) still recovers, while a permanently-dead one is
    // eventually given up -- never aborting the backup, never touching the global error policy.
    // The cap is a small constant (NOT the list size) so a dead file on a 50M-file job gives up
    // after a handful of passes instead of being retried tens of millions of times.
    static constexpr uint32_t putToEndRetryCap=16;
    const int64_t putAtBottomIndex=indexOfActionToDoTransfer(transfer->transferId);
    unsigned int indexAction=(putAtBottomIndex>=0)?(unsigned int)putAtBottomIndex:0;
    if(putAtBottomIndex>=0)
    {
            //display row = number of live (non-tombstone) entries before this one
            int liveBefore=0;
            for(size_t i=0;i<(size_t)indexAction;++i)
                if(!actionToDoListTransfer.at(i)->removed)
                    liveBefore++;
            const size_t liveCount=actionToDoListTransfer.size()-actionToDoListTransferRemoved;
            ActionToDoTransfer requeued=*actionToDoListTransfer[indexAction];//same id/paths/size/mode
            requeued.isRunning=false;
            requeued.removed=false;
            requeued.deferCount++;
            tombstoneActionToDoTransferAt(indexAction);//O(1), no reshuffle (like the finished-transfer path)
            Ultracopier::ReturnActionOnCopyList newAction;
            newAction.addAction.id=transfer->transferId;
            newAction.userAction.position=liveBefore;
            if(requeued.deferCount>putToEndRetryCap)
            {
                //bounded retries exhausted: give up on THIS file only (skip it); the model drops the line
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"Give up after "+std::to_string(requeued.deferCount-1)+" retries: "+std::to_string(transfer->transferId));
                newAction.type=Ultracopier::RemoveItem;
                newAction.userAction.moveAt=0;
            }
            else
            {
                //requeue: append a fresh copy at the end and tell the interface the line moved to the bottom
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"Put at the end: "+std::to_string(transfer->transferId));
                actionToDoListTransfer.push_back(std::unique_ptr<ActionToDoTransfer>(new ActionToDoTransfer(requeued)));
                newAction.type=Ultracopier::MoveItem;
                newAction.userAction.moveAt=(int)(liveCount-1);//last live row (live count is unchanged: -1 tombstone +1 append)
            }
            actionDone.push_back(newAction);
            rebuildActionToDoTransferIndex();//no-op hook, keeps reorder callers self-documenting
            #ifdef ULTRACOPIER_PLUGIN_DEBUG
            {
                size_t index=0;
                while(index<transferThreadList.size())
                {
                    if(transferThreadList.at(index)==transfer)
                    {
                        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"["+std::to_string(index)+"] currentTransferThread->transferId: "+
                                             std::to_string(transfer->transferId)+" on thread "+std::to_string((quint64)QThread::currentThread()));
                        break;
                    }
                    index++;
                }
                if(index>=transferThreadList.size())
                    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"[?] currentTransferThread->transferId: "+
                                     std::to_string(transfer->transferId)+" on thread "+std::to_string((quint64)QThread::currentThread()));
            }
            #endif
            // Keep transferId set so the thread stays OUT of the reuse gate (Idle && transferId==0)
            // until its read/write threads have drained -- transferInodeIsClosed's put-at-bottom
            // branch resets it then. This serialises reuse strictly after the old attempt's close,
            // so a stale close can't be mis-attributed to the retry and drop the requeued file.
            transfer->transferSize=0;
            #ifdef ULTRACOPIER_PLUGIN_DEBUG
            countLocalParse++;
            #endif
            isFound=true;
            putAtBottomAfterError.insert(transfer);
    }
    if(!isFound)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,QStringLiteral("unable to found item into the todo list, id: %1, index: %2").arg((uint64_t)transfer->transferId).toStdString());
        transfer->transferId=0;
        transfer->transferSize=0;
        putAtBottomAfterError.insert(transfer);
    }
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"countLocalParse: "+std::to_string(countLocalParse));
    #ifdef ULTRACOPIER_PLUGIN_DEBUG
    if(countLocalParse!=1)
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"countLocalParse != 1");
    #endif
    transfer->skip();
}

void ListThread::fileTransfer(const INTERNALTYPEPATH &sourceFileInfo,const INTERNALTYPEPATH &destinationFileInfo,const Ultracopier::CopyMode &mode)
{
    if(stopIt)
        return;
    #ifdef Q_OS_WIN32
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Information,"source: "+TransferThread::internalStringTostring(sourceFileInfo)+
                             ", destination: "+TransferThread::internalStringTostring(destinationFileInfo)+" windows");
    #else
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Information,"source: "+TransferThread::internalStringTostring(sourceFileInfo)+
                             ", destination: "+TransferThread::internalStringTostring(destinationFileInfo)+" unix");
    #endif
    addToTransfer(sourceFileInfo,destinationFileInfo,mode);
}

void ListThread::fileTransferWithInode(const INTERNALTYPEPATH &sourceFileInfo,const INTERNALTYPEPATH &destinationFileInfo,
                                       const Ultracopier::CopyMode &mode,const TransferThread::dirent_uc &inode)
{
    if(stopIt)
        return;
    #ifdef Q_OS_WIN32
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Information,"source: "+TransferThread::internalStringTostring(sourceFileInfo)+
                             ", destination: "+TransferThread::internalStringTostring(destinationFileInfo)+" windows "+std::to_string(inode.isFolder));
    addToTransfer(sourceFileInfo,destinationFileInfo,mode,inode.size);
    #else
    (void)inode;
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Information,"source: "+TransferThread::internalStringTostring(sourceFileInfo)+
                             ", destination: "+TransferThread::internalStringTostring(destinationFileInfo)+" unix");
    addToTransfer(sourceFileInfo,destinationFileInfo,mode);
    #endif
}

#if (defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)) && defined(ULTRACOPIER_PLUGIN_DEBUG)
// STAGE 1b.0 shadow observer -- see pipeline/STAGE_1B0_INTEGRATION.md and the member declarations in
// ListThread.h. Built ONCE from the fully-populated lists (called from autoStartAndCheckSpace, right
// after the scan finishes and before any dispatch), then fed purely by the existing dispatch/finish
// call sites. Read-only: it only counts/logs; it never changes what those call sites do.
void ListThread::opShadowBuild()
{
    if(opShadowSched!=nullptr)
        return; // built once per job
    // The shadow observer is a SINGLE-INODE-THREAD linearization proof. Its three hooks fire at
    // different lifecycle points (mkdir/setMetaDir at action-DISPATCH, file at transferInodeIsClosed
    // signal-processing), which coincide with the physical FS-op order ONLY when ONE inode thread owns
    // the whole op stream. With N inode threads the hook-firing order decouples from physical op
    // completion across threads, so the DAG check would see spurious `unmet` (a NON-violation) and the
    // Idle summary could race the last deferred SyncDate hooks -- meaningless noise, not a real ordering
    // bug. (The multi-thread engine's correctness is gated by content-diff + ops_dependency.py; each
    // file's open<data<close chain is thread-local and proven HERE at 1 thread; cross-file edges are
    // structural -- mkdir-before-open via mkdir-p-on-ENOENT, folder-date via SyncDate deferral.) So the
    // observer engages ONLY at inodeThreads==1 and skips HONESTLY otherwise -- it must never emit a false
    // OPSHADOW_VIOLATION in a debug multi-thread copy. The shadow test pins inodeThreads=1 to run it.
    if(inodeThreads!=1)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("OPSHADOW skipped: single-inode-thread linearization check, but inodeThreads=%1 (>1)").arg(inodeThreads).toStdString());
        return;
    }
    // First cut covers only a plain-Copy job whose queued inode actions are all MkPath/SyncDate (no
    // Move/rsync folder lifecycle, which this model does not yet represent), AND keepDate||doRightTransfer
    // (else addToMkPath skips a non-empty dir's action -- ListThread.cpp:663 -- while addToKeepAttributePath
    // still queues its SyncDate unconditionally, which would make that dir's setMetaDirOp/openOp(children)
    // never satisfiable -> a FALSE violation on a shape this shadow does not yet cover). If the shape
    // doesn't match, opShadowActive stays false and every hook below silently declines to check.
    bool shapeOk=(mode==Ultracopier::Copy) && (keepDate || doRightTransfer);
    for(size_t i=0;i<actionToDoListInode.size() && shapeOk;i++)
    {
        const ActionType t=actionToDoListInode.at(i).type;
        if(t!=ActionType_MkPath && t!=ActionType_SyncDate)
            shapeOk=false;
    }
    for(size_t i=0;i<actionToDoListTransfer.size() && shapeOk;i++)
        if(actionToDoListTransfer.at(i)->mode!=Ultracopier::Copy)
            shapeOk=false;
    if(!shapeOk)
        return;

    // Build the MODELED-dir path set FIRST (every dir the scanner actually recursed into and queued
    // an MkPath/SyncDate action for). This is the boundary between "the subtree ultracopier manages"
    // and "pre-existing filesystem" (the user's chosen dest base and everything above it, and every
    // ancestor above THAT up to the filesystem root) -- PathTree interns the FULL absolute destination
    // path, so an unbounded ancestor climb would otherwise walk through dozens of pre-existing path
    // components (e.g. /mnt/data/...) that no action ever creates/completes, permanently stalling their
    // (never-satisfiable) mkdirOp and producing a FALSE violation on every dir at the top of the
    // scanned subtree. Matching by STRING (never re-intern at run time -- see the design doc).
    std::unordered_set<INTERNALTYPEPATH> modeledDirPaths;
    for(size_t i=0;i<actionToDoListInode.size();i++)
    {
        const ActionToDoInode &a=actionToDoListInode.at(i);
        if(a.type==ActionType_MkPath || a.type==ActionType_SyncDate)
            modeledDirPaths.insert(a.destination);
    }

    std::vector<ucpipe::FsNode> nodes;
    std::unordered_map<uint32_t,int> pathTreeNodeToFs; // PathTree dir-node id -> FsNode index

    for(size_t i=0;i<actionToDoListTransfer.size();i++)
    {
        const ActionToDoTransfer &a=*actionToDoListTransfer.at(i);
        if(a.removed)
            continue;
        // Climb from the file's immediate destination parent toward the root, creating any
        // not-yet-seen MODELED ancestor dir FsNode along the way (root-ward first, so `parent`
        // indices always reference an already-pushed node). Stops -- WITHOUT creating a node for the
        // stopping point -- at PathTree::ROOT_SENTINEL, at an already-interned node (shared with a
        // prior file/dir), or at the first ancestor that is NOT in modeledDirPaths (the pre-existing
        // boundary); either way the last MODELED node on the chain gets parent=-1.
        std::vector<uint32_t> chain;
        uint32_t cur=pathTree.parentOf(a.dstNode);
        while(cur!=PathTree::ROOT_SENTINEL && pathTreeNodeToFs.find(cur)==pathTreeNodeToFs.cend())
        {
            if(modeledDirPaths.find(pathTree.resolve(cur))==modeledDirPaths.cend())
                break; // pre-existing / out-of-model boundary -- do not include this node or go higher
            chain.push_back(cur);
            cur=pathTree.parentOf(cur);
        }
        const bool curKnown=(cur!=PathTree::ROOT_SENTINEL && pathTreeNodeToFs.find(cur)!=pathTreeNodeToFs.cend());
        int parentIdx=curKnown?pathTreeNodeToFs.at(cur):-1;
        for(size_t ci=chain.size();ci>0;ci--)
        {
            const uint32_t ptNode=chain[ci-1];
            ucpipe::FsNode dn; dn.isDir=true; dn.parent=parentIdx; dn.size=0; dn.engineId=0;
            nodes.push_back(dn);
            const int idx=(int)nodes.size()-1;
            pathTreeNodeToFs[ptNode]=idx;
            parentIdx=idx;
        }
        ucpipe::FsNode fn; fn.isDir=false; fn.parent=parentIdx; fn.size=a.size; fn.engineId=a.id;
        nodes.push_back(fn);
        opShadowFileNode[a.id]=(int)nodes.size()-1;
    }

    // Match each MkPath/SyncDate action's destination STRING to the resolved path of a dir FsNode we
    // already built from the files (never re-intern at run time: intern()'s final-component semantics
    // differ from an already-interned directory -- see pipeline/STAGE_1B0_INTEGRATION.md).
    std::unordered_map<INTERNALTYPEPATH,uint32_t> resolvedToPtNode;
    for(std::unordered_map<uint32_t,int>::const_iterator it=pathTreeNodeToFs.cbegin();it!=pathTreeNodeToFs.cend();++it)
        resolvedToPtNode[pathTree.resolve(it->first)]=it->first;

    for(size_t i=0;i<actionToDoListInode.size();i++)
    {
        const ActionToDoInode &a=actionToDoListInode.at(i);
        std::unordered_map<INTERNALTYPEPATH,uint32_t>::const_iterator found=resolvedToPtNode.find(a.destination);
        if(found==resolvedToPtNode.cend())
            continue; // an empty dir (no file under it) -- not modeled in this first cut
        const int fsIdx=pathTreeNodeToFs.at(found->second);
        if(a.type==ActionType_MkPath || a.type==ActionType_SyncDate)
            opShadowDirNode[a.id]=fsIdx;
    }

    opShadowSched=new ucpipe::OpScheduler(nodes);
    opShadowActive=true;
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("OPSHADOW built: nodes=%1 files=%2 dirs-matched=%3")
        .arg(nodes.size()).arg(opShadowFileNode.size()).arg(opShadowDirNode.size()).toStdString());
}

void ListThread::opShadowCompleteFile(uint64_t engineId)
{
    if(!opShadowActive || opShadowSched==nullptr)
        return;
    std::unordered_map<uint64_t,int>::const_iterator it=opShadowFileNode.find(engineId);
    if(it==opShadowFileNode.cend())
        return;
    const int nd=it->second;
    const int ids[4]={opShadowSched->openOp(nd),opShadowSched->dataOp(nd),opShadowSched->closeOp(nd),opShadowSched->setMetaFileOp(nd)};
    for(int i=0;i<4;i++)
    {
        opShadowChecks++;
        if(opShadowSched->op(ids[i]).unmet!=0)
        {
            opShadowViolations++;
            ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,QStringLiteral("OPSHADOW_VIOLATION: file op %1 (node %2, engineId %3) unmet=%4 at completion")
                .arg(i).arg(nd).arg(engineId).arg(opShadowSched->op(ids[i]).unmet).toStdString());
        }
        opShadowSched->complete(ids[i]);
    }
}

void ListThread::opShadowCompleteMkdir(uint64_t actionId)
{
    if(!opShadowActive || opShadowSched==nullptr)
        return;
    std::unordered_map<uint64_t,int>::const_iterator it=opShadowDirNode.find(actionId);
    if(it==opShadowDirNode.cend())
        return;
    const int nd=it->second;
    const int mkOp=opShadowSched->mkdirOp(nd);
    opShadowChecks++;
    if(opShadowSched->op(mkOp).unmet!=0)
    {
        opShadowViolations++;
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,QStringLiteral("OPSHADOW_VIOLATION: mkdir (node %1, actionId %2) unmet=%3 at dispatch")
            .arg(nd).arg(actionId).arg(opShadowSched->op(mkOp).unmet).toStdString());
    }
    opShadowSched->complete(mkOp);
}

void ListThread::opShadowCompleteSetMetaDir(uint64_t actionId)
{
    if(!opShadowActive || opShadowSched==nullptr)
        return;
    std::unordered_map<uint64_t,int>::const_iterator it=opShadowDirNode.find(actionId);
    if(it==opShadowDirNode.cend())
        return;
    const int nd=it->second;
    const int smOp=opShadowSched->setMetaDirOp(nd);
    opShadowChecks++;
    if(opShadowSched->op(smOp).unmet!=0)
    {
        opShadowViolations++;
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,QStringLiteral("OPSHADOW_VIOLATION: setMetaDir (node %1, actionId %2) unmet=%3 at dispatch")
            .arg(nd).arg(actionId).arg(opShadowSched->op(smOp).unmet).toStdString());
    }
    opShadowSched->complete(smOp);
}
#endif

void ListThread::autoStartAndCheckSpace()
{
    #if (defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)) && defined(ULTRACOPIER_PLUGIN_DEBUG)
    opShadowBuild();
    #endif
    if(needMoreSpace())
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Information,"Need more space");
        return;
    }
    autoStartIfNeeded();
}

void ListThread::autoStartIfNeeded()
{
    if(autoStart)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Information,"Auto start the copy");
        startGeneralTransfer();
    }
    else
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Information,"Put the copy engine in pause");
        putInPause=true;
        emit isInPause(true);
    }
}

//set auto start
void ListThread::setAutoStart(const bool autoStart)
{
    this->autoStart=autoStart;
}

void ListThread::startGeneralTransfer()
{
    doNewActions_inode_manipulation();
}

/** \brief to sync the transfer list
 * Used when the interface is changed, useful to minimize the memory size */
void ListThread::syncTransferList()
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"start");
    emit send_syncTransferList();
}

bool ListThread::getReturnBoolToCopyEngine() const
{
    return returnBoolToCopyEngine;
}

std::pair<quint64, quint64> ListThread::getReturnPairQuint64ToCopyEngine() const
{
    return returnPairQuint64ToCopyEngine;
}

Ultracopier::ItemOfCopyList ListThread::getReturnItemOfCopyListToCopyEngine() const
{
    return returnItemOfCopyListToCopyEngine;
}

void ListThread::realByteTransfered()
{
    quint64 totalRealByteTransfered=0;
    #if defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)
    /* The pipelined backends (io_uring / IOCP) update a transfer's in-flight progress
       only per write-completion (block-sized chunks), so small files show 0 progress
       until they finish in one step. Summing ONLY the in-flight threads therefore
       barely grows on small-file-heavy copies -> the speed (computed from this value's
       delta in Core) reads ~0 and the ETA derived from it breaks. Seed the total with
       the cumulative bytes of already-completed files so the speed source is monotone.
       (Completed files and in-flight files are disjoint: bytesTransfered is incremented
       at harvest, after the thread's progression has been reset, so no double count.) */
    totalRealByteTransfered=bytesTransfered;
    #endif
    int index=0;
    int loop_sub_size_transfer_thread_search=transferThreadList.size();
    while(index<loop_sub_size_transfer_thread_search)
    {
        const TransferThreadImpl * thread=transferThreadList.at(index);
        totalRealByteTransfered+=thread->realByteTransfered();
        index++;
    }
    #ifdef ULTRACOPIER_PLUGIN_KIO
    for(const auto &entry : kioJobs)
        totalRealByteTransfered+=entry.processedBytes;
    #endif
    emit send_realBytesTransfered(totalRealByteTransfered);
}

void ListThread::checkIfReadyToCancel()
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"start");
    if(!stopIt)
        return;
    int index=0;
    int loop_size=transferThreadList.size();
    while(index<loop_size)
    {
        if(transferThreadList.at(index)!=NULL)
        {
            if(transferThreadList.at(index)->transferId!=0)
                return;
            //need wait a clean stop (and then destination remove)
            if(transferThreadList.at(index)->getStat()!=TransferStat_Idle)
                return;
            // Join the transfer thread (delete -> ~TransferThreadAsync stops+waits read+write threads)
            // BEFORE freeing the per-thread write-collision list/mutex it allocated: when the transfer
            // thread first reports Idle the WriteThread can STILL be inside internalClose()->
            // resumeNotStarted() locking writeFileListMutex, so freeing it before the join is a
            // use-after-free (ASan-confirmed). Each transfer thread owns its own pair (createTransferThread),
            // so freeing after the join is safe (not shared, no double-free).
            #if defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)
            auto * const doomedWriteFileList=transferThreadList.at(index)->writeFileList;
            auto * const doomedWriteFileListMutex=transferThreadList.at(index)->writeFileListMutex;
            #else
            auto * const doomedWriteFileList=transferThreadList.at(index)->writeThread.writeFileList;
            auto * const doomedWriteFileListMutex=transferThreadList.at(index)->writeThread.writeFileListMutex;
            #endif
            delete transferThreadList.at(index);//->deleteLayer();
            delete doomedWriteFileList;
            delete doomedWriteFileListMutex;
            transferThreadList[index]=NULL;
            transferThreadList.erase(transferThreadList.cbegin()+index);
            loop_size=transferThreadList.size();
            index--;
        }
        index++;
    }
    actionToDoListTransfer.clear();
    pathTree.clear();//drop the interned paths of the finished/cancelled job
    actionToDoListTransferRemoved=0;
    actionToDoListInode.clear();
    actionToDoListInode_afterTheTransfer.clear();
    actionDone.clear();
    progressionList.clear();
    returnListItemOfCopyListToCopyEngine.clear();
    quit();
    waitCancel.release();
    emit canBeDeleted();
}

void ListThread::updateTheStatus()
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"start");
    sendActionDone();
    bool updateTheStatus_listing=scanFileOrFolderThreadsPool.size()>0;
    bool updateTheStatus_copying=!actionToDoListTransferEmpty() || actionToDoListInode.size()>0 || actionToDoListInode_afterTheTransfer.size()>0;
    #ifdef ULTRACOPIER_PLUGIN_KIO
    if(!kioJobs.empty())
        updateTheStatus_copying=true;
    #endif
    Ultracopier::EngineActionInProgress updateTheStatus_action_in_progress;
    if(updateTheStatus_copying && updateTheStatus_listing)
        updateTheStatus_action_in_progress=Ultracopier::CopyingAndListing;
    else if(updateTheStatus_listing)
        updateTheStatus_action_in_progress=Ultracopier::Listing;
    else if(updateTheStatus_copying)
        updateTheStatus_action_in_progress=Ultracopier::Copying;
    else
        updateTheStatus_action_in_progress=Ultracopier::Idle;
    #if (defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)) && defined(ULTRACOPIER_PLUGIN_DEBUG)
    // Log the OPSHADOW summary as soon as the job goes Idle (not just the dtor): pkill/SIGTERM, the
    // mandated way to end a test run, does not run C++ destructors, so a test reading this log needs
    // the summary written WHILE the process is still alive.
    if(updateTheStatus_action_in_progress==Ultracopier::Idle && opShadowSched!=nullptr && !opShadowSummaryLogged)
    {
        opShadowSummaryLogged=true;
        ULTRACOPIER_DEBUGCONSOLE(opShadowViolations==0?Ultracopier::DebugLevel_Notice:Ultracopier::DebugLevel_Critical,
            QStringLiteral("OPSHADOW summary: nodes=%1 checks=%2 violations=%3").arg(opShadowSched->opCount())
            .arg(opShadowChecks).arg(opShadowViolations).toStdString());
    }
    #endif
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"emit actionInProgess("+std::to_string(updateTheStatus_action_in_progress)+")");
    emit actionInProgess(updateTheStatus_action_in_progress);
}

//mk path to do
uint64_t ListThread::addToMkPath(const INTERNALTYPEPATH& source,const INTERNALTYPEPATH& destination, const int& inode)
{
    if(stopIt)
        return 0;
    if(inode!=0 && (!keepDate && !doRightTransfer))
        return 0;
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"source: "+TransferThread::internalStringTostring(source)+
                             ", destination: "+TransferThread::internalStringTostring(destination));
    ActionToDoInode temp;
    temp.type       = ActionType_MkPath;
    temp.id         = generateIdNumber();
    temp.source     = source;
    temp.destination= destination;
    temp.isRunning	= false;
    actionToDoListInode.push_back(temp);
    return temp.id;
}

//add path to do
void ListThread::addToMovePath(const INTERNALTYPEPATH &source, const INTERNALTYPEPATH &destination, const int& inodeToRemove)
{
    if(stopIt)
        return;
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"source: "+TransferThread::internalStringTostring(source)+
                             ", destination: "+TransferThread::internalStringTostring(destination)+", inodeToRemove: "+std::to_string(inodeToRemove));
    ActionToDoInode temp;
    temp.type	= ActionType_MovePath;
    temp.id		= generateIdNumber();
    temp.size	= inodeToRemove;
    temp.source     = source;
    temp.destination= destination;
    temp.isRunning	= false;
    actionToDoListInode.push_back(temp);
}

void ListThread::addToKeepAttributePath(const INTERNALTYPEPATH &source, const INTERNALTYPEPATH &destination, const int& inodeToRemove)
{
    if(stopIt)
        return;
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"source: "+TransferThread::internalStringTostring(source)+
                             ", destination: "+TransferThread::internalStringTostring(destination)+", inodeToRemove: "+std::to_string(inodeToRemove));
    ActionToDoInode temp;
    temp.type	= ActionType_SyncDate;
    temp.id		= generateIdNumber();
    temp.size	= inodeToRemove;
    temp.source     = source;
    temp.destination= destination;
    temp.isRunning	= false;
    actionToDoListInode.push_back(temp);
}

void ListThread::addToRealMove(const INTERNALTYPEPATH& source,const INTERNALTYPEPATH& destination)
{
    if(stopIt)
        return;
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"source: "+TransferThread::internalStringTostring(source)+
                             ", destination: "+TransferThread::internalStringTostring(destination));
    ActionToDoInode temp;
    temp.type	= ActionType_RealMove;
    temp.id		= generateIdNumber();
    temp.size	= 0;
    temp.source     = source;
    temp.destination= destination;
    temp.isRunning	= false;
    actionToDoListInode.push_back(temp);
}

#ifdef ULTRACOPIER_PLUGIN_RSYNC
//rsync rm
void ListThread::addToRmForRsync(const std::string& destination)
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"inode: "+destination);
    ActionToDoInode temp;
    temp.type	= ActionType_RmSync;
    temp.id		= generateIdNumber();
    temp.destination= destination;
    temp.isRunning	= false;
    actionToDoListInode.push_back(temp);
}
#endif

//send the progression, after full reset of the interface (then all is empty)
void ListThread::syncTransferList_internal()
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"start");
    emit syncReady();
    actionDone.clear();
    //do list operation
    //full resync emits position-based actions; flush tombstones so the loop sees a dense list
    compactActionToDoListTransfer();
    TransferThreadImpl *transferThread;
    const int &loop_size=actionToDoListTransfer.size();
    int loop_sub_size=transferThreadList.size();
    //this loop to have at max inodeThreads*inodeThreads, not inodeThreads*transferThreadList.size()
    int int_for_internal_loop;
    for(int int_for_loop=0; int_for_loop<loop_size; ++int_for_loop) {
        const ActionToDoTransfer &item=*actionToDoListTransfer.at(int_for_loop);
        const INTERNALTYPEPATH itemSrc=pathTree.resolve(item.srcNode);//resolve once for this row
        const INTERNALTYPEPATH itemDst=pathTree.resolve(item.dstNode);
        Ultracopier::ReturnActionOnCopyList newAction;
        newAction.type				= Ultracopier::PreOperation;
        newAction.addAction.id			= item.id;
        const size_t sourceIndex=itemSrc.rfind('/');
        if(sourceIndex == std::string::npos)
        {
            newAction.addAction.sourceFullPath	= '/';
            newAction.addAction.sourceFileName	= TransferThread::internalStringTostring(itemSrc);
        }
        else
        {
            newAction.addAction.sourceFullPath	= TransferThread::internalStringTostring(itemSrc);
            newAction.addAction.sourceFileName	= TransferThread::internalStringTostring(itemSrc.substr(sourceIndex+1));
        }
        const size_t destinationIndex=itemDst.rfind('/');
        if(destinationIndex == std::string::npos)
        {
            newAction.addAction.destinationFullPath	= '/';
            newAction.addAction.destinationFileName	= TransferThread::internalStringTostring(itemDst);
        }
        else
        {
            newAction.addAction.destinationFullPath	= TransferThread::internalStringTostring(itemDst);
            newAction.addAction.destinationFileName	= TransferThread::internalStringTostring(itemDst.substr(destinationIndex+1));
        }
        newAction.addAction.size		= item.size;
        newAction.addAction.mode		= item.mode;
        actionDone.push_back(newAction);
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"id: "+std::to_string(item.id)+", size: "+std::to_string(item.size)+
                                 ", name: "+TransferThread::internalStringTostring(itemSrc)+", size2: "+std::to_string(newAction.addAction.size));
        if(item.isRunning)
        {
            for(int_for_internal_loop=0; int_for_internal_loop<loop_sub_size; ++int_for_internal_loop) {
                transferThread=transferThreadList.at(int_for_internal_loop);
                Ultracopier::ReturnActionOnCopyList newAction;
                newAction.type				= Ultracopier::PreOperation;
                newAction.addAction.id			= item.id;
                const size_t sourceIndex=itemSrc.rfind('/');
                if(sourceIndex == std::string::npos)
                {
                    newAction.addAction.sourceFullPath	= TransferThread::internalStringTostring(itemSrc);
                    newAction.addAction.sourceFileName	= TransferThread::internalStringTostring(itemSrc);
                }
                else
                {
                    newAction.addAction.sourceFullPath	= TransferThread::internalStringTostring(itemSrc);
                    newAction.addAction.sourceFileName	= TransferThread::internalStringTostring(itemSrc.substr(sourceIndex+1));
                }
                const size_t destinationIndex=itemDst.rfind('/');
                if(destinationIndex == std::string::npos)
                {
                    newAction.addAction.destinationFullPath	= TransferThread::internalStringTostring(itemDst);
                    newAction.addAction.destinationFileName	= TransferThread::internalStringTostring(itemDst);
                }
                else
                {
                    newAction.addAction.destinationFullPath	= TransferThread::internalStringTostring(itemDst);
                    newAction.addAction.destinationFileName	= TransferThread::internalStringTostring(itemDst.substr(destinationIndex+1));
                }
                newAction.addAction.size		= item.size;
                newAction.addAction.mode		= item.mode;
                actionDone.push_back(newAction);
                if(transferThread->getStat()!=TransferStat_PreOperation)
                {
                    Ultracopier::ReturnActionOnCopyList newAction;
                    switch(transferThread->getStat())
                    {
                        case TransferStat_Transfer:
                            newAction.type=Ultracopier::Transfer;
                        break;
                        /*case TransferStat_PostTransfer:
                            newAction.type=Ultracopier::PostOperation;
                        break;*/
                        case TransferStat_PostOperation:
                            newAction.type=Ultracopier::PostOperation;
                        break;
                        default:
                        break;
                    }
                    newAction.addAction.id			= item.id;
                    actionDone.push_back(newAction);
                }
            }
        }
    }
}

//add file transfer to do
uint64_t ListThread::addToTransfer(const INTERNALTYPEPATH &source, const INTERNALTYPEPATH &destination, const Ultracopier::CopyMode& mode, const int64_t sendedsize)
{
    if(stopIt)
        return 0;
    //add to transfer list
    numberOfTransferIntoToDoList++;
    uint64_t size=0;
    if(sendedsize>=0)
        size=sendedsize;
    else
    {
        const int64_t tempSize=TransferThread::file_stat_size(source);
        if(tempSize>=0)
            size=tempSize;
        else
        {
            if(TransferThread::is_file(source))
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Warning,"Unable to detect size on "+TransferThread::internalStringTostring(source));
            else
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"File not existing during listing "+TransferThread::internalStringTostring(source));
        }
    }
    const std::string &drive=driveManagement.getDrive(TransferThread::internalStringTostring(destination));
    if(!drive.empty())//can be a network drive
        if(mode!=Ultracopier::Move || drive!=driveManagement.getDrive(TransferThread::internalStringTostring(source)))
        {
            if(requiredSpace.find(drive)!=requiredSpace.cend())
            {
                requiredSpace[drive]+=size;
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("space needed add: %1, space needed: %2, on: %3").arg(size).arg(requiredSpace.at(drive)).arg(QString::fromStdString(drive)).toStdString());
            }
            else
            {
                requiredSpace[drive]=size;
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("set space %1 needed, on: %2").arg(size).arg(QString::fromStdString(drive)).toStdString());
            }
        }
    bytesToTransfer+= size;
    ActionToDoTransfer temp;
    temp.id		= generateIdNumber();
    temp.size	= size;
    // Intern source + destination into the path tree (the strings are no longer stored per entry):
    // directory chains are shared across siblings, the leaf carries only the file name.
    temp.srcNode	= pathTree.intern(source);
    temp.dstNode	= pathTree.intern(destination);
    temp.mode	= mode;
    temp.isRunning	= false;
    temp.removed	= false;
    actionToDoListTransfer.push_back(std::unique_ptr<ActionToDoTransfer>(new ActionToDoTransfer(temp)));
    //push the new transfer to interface
    Ultracopier::ReturnActionOnCopyList newAction;
    newAction.type				= Ultracopier::AddingItem;
    newAction.addAction=actionToDoTransferToItemOfCopyList(temp);
    actionDone.push_back(newAction);
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"source: "+TransferThread::internalStringTostring(source)+
                             ", destination: "+TransferThread::internalStringTostring(destination)+", add entry: "+std::to_string(temp.id)+", size: "+
                             std::to_string(temp.size)+", size2: "+std::to_string(size)+", isSymLink: "+std::to_string(TransferThread::is_symlink(source)));
    return temp.id;
}

Ultracopier::ItemOfCopyList ListThread::actionToDoTransferToItemOfCopyList(const ListThread::ActionToDoTransfer &actionToDoTransfer)
{
    Ultracopier::ItemOfCopyList itemOfCopyList;
    itemOfCopyList.id			= actionToDoTransfer.id;
    // Full paths resolved from the tree; the leaf node IS the file name (no rfind needed).
    itemOfCopyList.sourceFullPath	= TransferThread::internalStringTostring(pathTree.resolve(actionToDoTransfer.srcNode));
    itemOfCopyList.sourceFileName	= TransferThread::internalStringTostring(pathTree.name(actionToDoTransfer.srcNode));
    itemOfCopyList.destinationFullPath	= TransferThread::internalStringTostring(pathTree.resolve(actionToDoTransfer.dstNode));
    itemOfCopyList.destinationFileName	= TransferThread::internalStringTostring(pathTree.name(actionToDoTransfer.dstNode));
    itemOfCopyList.size		= actionToDoTransfer.size;
    itemOfCopyList.mode		= actionToDoTransfer.mode;
    return itemOfCopyList;
}

//generate id number
uint64_t ListThread::generateIdNumber()
{
    idIncrementNumber++;
    if(idIncrementNumber>(((quint64)1024*1024)*1024*1024*2))
        idIncrementNumber=0;
    return idIncrementNumber;
}

//do new actions
void ListThread::doNewActions_start_transfer()
{
    /*ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("actionToDoListTransfer.size(): %1, numberOfTranferRuning: %2")
                             .arg(actionToDoListTransfer.size()).arg(getNumberOfTranferRuning()).toStdString());*/
    if(stopIt || putInPause)
        return;
    int numberOfTranferRuning=getNumberOfTranferRuning();
    const int &loop_size=transferThreadList.size();
    //lunch the transfer in WaitForTheTransfer
    //high priority
    int int_for_loop=0;
    while(int_for_loop<loop_size)
    {
        TransferThreadImpl *transferThread=transferThreadList.at(int_for_loop);
        if(transferThread->getStat()==TransferStat_WaitForTheTransfer/*ready to start the transfer*/ && transferThread->transferId!=0)
        {
            //ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning));
            if(transferThread->transferSize>=parallelizeIfSmallerThan)
            {
                //ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning));
                if(numberOfTranferRuning<ULTRACOPIER_PLUGIN_MAXPARALLELTRANFER)
                {
                    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning));
                    transferThread->startTheTransfer();
                    numberOfTranferRuning++;
                }
            }
            else
            {
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning)+
                                         ", start in parallele beacuase size is "+std::to_string(transferThread->transferSize));
                transferThread->startTheTransfer();
                numberOfTranferRuning++;
            }
        }
        int_for_loop++;
    }
    //ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning));

    //low priority
    int_for_loop=0;
    while(int_for_loop<loop_size)
    {
        TransferThreadImpl *transferThread=transferThreadList.at(int_for_loop);
        if(transferThread->getStat()==TransferStat_PreOperation/*wait user dialog action*/)
        {
            //ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning));
            if(transferThread->transferSize>=parallelizeIfSmallerThan)
            {
                //ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning));
                if(numberOfTranferRuning<ULTRACOPIER_PLUGIN_MAXPARALLELTRANFER)
                {
                    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning));
                    transferThread->startTheTransfer();
                    numberOfTranferRuning++;
                }
            }
            else
            {
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning));
                transferThread->startTheTransfer();
                numberOfTranferRuning++;
            }
        }
        int_for_loop++;
    }
    //ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfTranferRuning: "+std::to_string(numberOfTranferRuning));
}

/** \brief lunch the pre-op or inode op
  1) locate the next next item to do into the both list
    1a) optimisation posible on the mkpath/rmpath
  2) determine what need be lunched
  3) lunch it, rerun the 2)
  */
void ListThread::doNewActions_inode_manipulation()
{
    #ifdef ULTRACOPIER_PLUGIN_DEBUG_SCHEDULER
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"actionToDoList.size(): "+std::to_string(actionToDoListTransfer.size()));
    #endif
    if(stopIt)
        checkIfReadyToCancel();
    if(stopIt || putInPause)
        return;
    #ifdef ULTRACOPIER_PLUGIN_DEBUG_SCHEDULER
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"start");
    #endif
    //lunch the pre-op or inode op
    TransferThreadImpl *currentTransferThread;
    int int_for_loop=0;
    int int_for_internal_loop=0;
    int int_for_transfer_thread_search=0;
    actionToDoListTransfer_count=actionToDoListTransfer.size();
    actionToDoListInode_count=actionToDoListInode.size();
    int loop_sub_size_transfer_thread_search=transferThreadList.size();
    //search the next transfer action to do
    while(int_for_loop<actionToDoListTransfer_count)
    {
        ActionToDoTransfer& currentActionToDoTransfer=*actionToDoListTransfer[int_for_loop];
        if(!currentActionToDoTransfer.removed && !currentActionToDoTransfer.isRunning)
        {
            //search the next inode action to do
            while(int_for_internal_loop<actionToDoListInode_count)
            {
                if(!actionToDoListInode.at(int_for_internal_loop).isRunning)
                {
                    if(currentActionToDoTransfer.id<actionToDoListInode.at(int_for_internal_loop).id)
                    {
                        //do the tranfer action in the next code
                        break;
                    }
                    else
                    {
                        //do the inode action
                        #include "ListThread_InodeAction.cpp"
                    }
                }
                int_for_internal_loop++;
            }
            //do the tranfer action
            while(int_for_transfer_thread_search<loop_sub_size_transfer_thread_search)
            {
                /**
                    transferThreadList.at(int_for_transfer_thread_search)->transferId==0) /!\ important!
                    Because the other thread can have call doNewAction before than this thread have the finish event parsed!
                    I this case it lose all data
                    */
                currentTransferThread=transferThreadList.at(int_for_transfer_thread_search);
                if(currentTransferThread->getStat()==TransferStat_Idle && currentTransferThread->transferId==0 &&
                        currentTransferThread->readyForReuse() && // old attempt fully drained (put-to-end safety)
                        overCheckUsedThread.find(currentTransferThread)==overCheckUsedThread.cend()) // /!\ important!
                {
                    #ifdef ULTRACOPIER_PLUGIN_DEBUG
                    {
                        size_t index=0;
                        while(index<transferThreadList.size())
                        {
                            if(transferThreadList.at(index)==currentTransferThread)
                            {
                                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"["+std::to_string(index)+"] currentTransferThread->transferId: "+
                                                     std::to_string(currentTransferThread->transferId)+" on thread "+std::to_string((quint64)QThread::currentThread()));
                                break;
                            }
                            index++;
                        }
                        if(index>=transferThreadList.size())
                            ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"[?] currentTransferThread->transferId: "+
                                             std::to_string(currentTransferThread->transferId)+" on thread "+std::to_string((quint64)QThread::currentThread()));
                    }
                    #endif
                    overCheckUsedThread.insert(currentTransferThread);

                    // resolve the two paths from the tree ONCE for this hand-off
                    const INTERNALTYPEPATH curSrc=pathTree.resolve(currentActionToDoTransfer.srcNode);
                    const INTERNALTYPEPATH curDst=pathTree.resolve(currentActionToDoTransfer.dstNode);
                    std::string drive=driveManagement.getDrive(TransferThread::internalStringTostring(curDst));
                    if(requiredSpace.find(drive)!=requiredSpace.cend() &&
                            (currentActionToDoTransfer.mode!=Ultracopier::Move ||
                             drive!=driveManagement.getDrive(TransferThread::internalStringTostring(curSrc))))
                    {
                        requiredSpace[drive]-=currentActionToDoTransfer.size;
                        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("space needed removed: %1, space needed: %2, on: %3").arg(
                                                     currentActionToDoTransfer.size).arg(requiredSpace.at(drive)).arg(QString::fromStdString(drive)).toStdString());
                    }
                    currentTransferThread->transferId=currentActionToDoTransfer.id;
                    currentTransferThread->transferSize=currentActionToDoTransfer.size;
                    // Carry the entry's put-to-end retry count onto the thread so errorOnFile can decide,
                    // BEFORE deferring again, to escalate to the Ask dialog (one deferred pass then ask).
                    currentTransferThread->deferCount=currentActionToDoTransfer.deferCount;
                    putAtBottomAfterError.erase(currentTransferThread);
                    if(!currentTransferThread->setFiles(
                        curSrc,
                        currentActionToDoTransfer.size,
                        curDst,
                        currentActionToDoTransfer.mode
                        ))
                    {
                        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Warning,"["+std::to_string(int_for_loop)+"] id: "+
                                                 std::to_string(currentTransferThread->transferId)+
                                                 " is idle, but seam busy at set name: "+TransferThread::internalStringTostring(curDst)
                                                 );
                        break;
                    }
                    else
                    {
                        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"["+std::to_string(int_for_loop)+"] id: "+
                                                 std::to_string(currentTransferThread->transferId)+
                                                 " is idle, but seam busy at set name: "+TransferThread::internalStringTostring(curDst)
                                                 );
                    }
                    currentActionToDoTransfer.isRunning=true;

                    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"["+std::to_string(int_for_loop)+"] id: "+
                                             std::to_string(currentTransferThread->transferId)+
                                             " is idle, use it for "+TransferThread::internalStringTostring(curDst)
                                             );

                    /// \note wrong position? Else write why it's here
                    Ultracopier::ReturnActionOnCopyList newAction;
                    newAction.type				= Ultracopier::PreOperation;
                    newAction.addAction.id			= currentActionToDoTransfer.id;
                    const size_t sourceIndex=curSrc.rfind('/');
                    if(sourceIndex == std::string::npos)
                    {
                        newAction.addAction.sourceFullPath	= '/';
                        newAction.addAction.sourceFileName	= TransferThread::internalStringTostring(curSrc);
                    }
                    else
                    {
                        newAction.addAction.sourceFullPath	= TransferThread::internalStringTostring(curSrc);
                        newAction.addAction.sourceFileName	= TransferThread::internalStringTostring(curSrc.substr(sourceIndex+1));
                    }
                    const size_t destinationIndex=curDst.rfind('/');
                    if(destinationIndex == std::string::npos)
                    {
                        newAction.addAction.destinationFullPath	= '/';
                        newAction.addAction.destinationFileName	= TransferThread::internalStringTostring(curDst);
                    }
                    else
                    {
                        newAction.addAction.destinationFullPath	= TransferThread::internalStringTostring(curDst);
                        newAction.addAction.destinationFileName	= TransferThread::internalStringTostring(curDst.substr(destinationIndex+1));
                    }
                    newAction.addAction.size		= currentActionToDoTransfer.size;
                    newAction.addAction.mode		= currentActionToDoTransfer.mode;
                    actionDone.push_back(newAction);
                    int_for_transfer_thread_search++;
                    numberOfInodeOperation++;
                    #ifdef ULTRACOPIER_PLUGIN_DEBUG_SCHEDULER
                    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfInodeOperation: "+std::to_string(numberOfInodeOperation));
                    #endif
                    break;
                }
                int_for_transfer_thread_search++;
            }
            if(int_for_internal_loop==loop_sub_size_transfer_thread_search)
            {
                /// \note Can be normal when all thread is not initialized
                #ifdef ULTRACOPIER_PLUGIN_DEBUG_SCHEDULER
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"unable to found free thread to do the transfer");
                #endif
                break;
            }
            #ifdef ULTRACOPIER_PLUGIN_DEBUG_SCHEDULER
            ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfInodeOperation: "+std::to_string(numberOfInodeOperation));
            #endif
            if(numberOfInodeOperation>=inodeThreads)
                break;
            if(followTheStrictOrder &&
               (sourceDriveMultiple || destinationDriveMultiple ||
                (!sourceDrive.empty() && !destinationDrive.empty() && sourceDrive!=destinationDrive)))
                break;
        }
        int_for_loop++;
    }
    //search the next inode action to do
    int_for_internal_loop=0;
    while(int_for_internal_loop<actionToDoListInode_count)
    {
        if(!actionToDoListInode.at(int_for_internal_loop).isRunning)
        {
            //do the inode action
            #include "ListThread_InodeAction.cpp"
        }
        int_for_internal_loop++;
    }
    //error  checking
    if(actionToDoListInode_count>inodeThreads)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Warning,QStringLiteral("The index have been detected as out of max range: %1>%2").arg(actionToDoListInode_count).arg(inodeThreads).toStdString());
        return;
    }
}

//restart transfer if it can
void ListThread::restartTransferIfItCan()
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"start");
    TransferThreadImpl *transfer=qobject_cast<TransferThreadImpl *>(QObject::sender());
    if(transfer==NULL)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"transfer thread not located!");
        return;
    }
    int numberOfTranferRuning=getNumberOfTranferRuning();
    if(numberOfTranferRuning<ULTRACOPIER_PLUGIN_MAXPARALLELTRANFER && transfer->getStat()==TransferStat_WaitForTheTransfer)
        transfer->startTheTransfer();
    doNewActions_start_transfer();
}


void ListThread::set_setFilters(const std::vector<Filters_rules> &include,const std::vector<Filters_rules> &exclude)
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("include.size(): %1, exclude.size(): %2").arg(include.size()).arg(exclude.size()).toStdString());
    this->include=include;
    this->exclude=exclude;
    unsigned int index=0;
    while(index<scanFileOrFolderThreadsPool.size())
    {
        scanFileOrFolderThreadsPool.at(index)->setFilters(include,exclude);
        index++;
    }
}

void ListThread::set_sendNewRenamingRules(const std::string &firstRenamingRule,const std::string &otherRenamingRule)
{
    this->firstRenamingRule=firstRenamingRule;
    this->otherRenamingRule=otherRenamingRule;
    emit send_sendNewRenamingRules(firstRenamingRule,otherRenamingRule);
}

void ListThread::set_updateMount()
{
    driveManagement.tryUpdate();
    emit send_updateMount();
}

void ListThread::mkPathFirstFolderFinish()
{
    int int_for_loop=0;
    const int &loop_size=actionToDoListInode.size();
    while(int_for_loop<loop_size)
    {
        const ActionToDoInode &actionToDoInode=actionToDoListInode.at(int_for_loop);
        if(actionToDoInode.isRunning)
        {
            if(actionToDoInode.type==ActionType_MkPath || actionToDoInode.type==ActionType_SyncDate)
            {
                //to send to the log
                emit mkPath(TransferThread::internalStringTostring(actionToDoInode.destination));
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"stop mkpath: "+TransferThread::internalStringTostring(actionToDoInode.destination));
                actionToDoListInode.erase(actionToDoListInode.cbegin()+int_for_loop);
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("actionToDoListTransfer.size(): %1, actionToDoListInode: %2, actionToDoListInode_afterTheTransfer: %3").arg(actionToDoListTransfer.size()).arg(actionToDoListInode.size()).arg(actionToDoListInode_afterTheTransfer.size()).toStdString());
                if(actionToDoListTransferEmpty() && actionToDoListInode.empty() && actionToDoListInode_afterTheTransfer.empty())
                    updateTheStatus();
                numberOfInodeOperation--;
                #ifdef ULTRACOPIER_PLUGIN_DEBUG_SCHEDULER
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfInodeOperation: "+std::to_string(numberOfInodeOperation));
                #endif
                doNewActions_inode_manipulation();
                return;
            }
            if(actionToDoInode.type==ActionType_MovePath || actionToDoInode.type==ActionType_RealMove
                    #ifdef ULTRACOPIER_PLUGIN_RSYNC
                     || actionToDoInode.type==ActionType_RmSync
                    #endif
                    )
            {
                //to send to the log
                #ifdef ULTRACOPIER_PLUGIN_RSYNC
                if(actionToDoInode.type!=ActionType_RmSync)
                    emit mkPath(actionToDoInode.destination);
                #else
                emit mkPath(TransferThread::internalStringTostring(actionToDoInode.destination));
                #endif
                emit rmPath(TransferThread::internalStringTostring(actionToDoInode.source));
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"stop mkpath: "+TransferThread::internalStringTostring(actionToDoInode.destination));
                actionToDoListInode.erase(actionToDoListInode.cbegin()+int_for_loop);
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,QStringLiteral("actionToDoListTransfer.size(): %1, actionToDoListInode: %2, actionToDoListInode_afterTheTransfer: %3").arg(actionToDoListTransfer.size()).arg(actionToDoListInode.size()).arg(actionToDoListInode_afterTheTransfer.size()).toStdString());
                if(actionToDoListTransferEmpty() && actionToDoListInode.empty() && actionToDoListInode_afterTheTransfer.empty())
                    updateTheStatus();
                numberOfInodeOperation--;
                #ifdef ULTRACOPIER_PLUGIN_DEBUG_SCHEDULER
                ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"numberOfInodeOperation: "+std::to_string(numberOfInodeOperation));
                #endif
                doNewActions_inode_manipulation();
                return;
            }

        }
        int_for_loop++;
    }
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"unable to found item into the todo list");
}

/// \note Can be call without queue because all call will be serialized
void ListThread::fileAlreadyExists(const INTERNALTYPEPATH &source,const INTERNALTYPEPATH &destination,const bool &isSame)
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,TransferThread::internalStringTostring(source)+" to "+TransferThread::internalStringTostring(destination));
    emit send_fileAlreadyExists(source,destination,isSame,qobject_cast<TransferThreadImpl *>(sender()));
}

/// \note Can be call without queue because all call will be serialized
void ListThread::errorOnFile(const INTERNALTYPEPATH &fileInfo, const std::string &errorString, const ErrorType &errorType)
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,TransferThread::internalStringTostring(fileInfo)+" error: "+errorString);
    TransferThreadImpl * transferThread=qobject_cast<TransferThreadImpl *>(sender());
    if(transferThread==NULL)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"Thread locating error");
        return;
    }
    ErrorLogEntry errorLogEntry;
    errorLogEntry.source=TransferThread::internalStringTostring(transferThread->getSourcePath());
    errorLogEntry.destination=TransferThread::internalStringTostring(transferThread->getDestinationPath());
    errorLogEntry.mode=transferThread->getMode();
    errorLogEntry.error=errorString;
    errorLog.push_back(errorLogEntry);
    emit errorToRetry(TransferThread::internalStringTostring(transferThread->getSourcePath()),
                      TransferThread::internalStringTostring(transferThread->getDestinationPath()),
                      errorString);
    emit send_errorOnFile(fileInfo,errorString,transferThread,errorType);
}

/// \note Can be call without queue because all call will be serialized
void ListThread::folderAlreadyExists(const INTERNALTYPEPATH &source,const INTERNALTYPEPATH &destination,const bool &isSame)
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,TransferThread::internalStringTostring(source)+" to "+TransferThread::internalStringTostring(destination));
    emit send_folderAlreadyExists(source,destination,isSame,qobject_cast<ScanFileOrFolder *>(sender()));
}

/// \note Can be call without queue because all call will be serialized
/// \todo all this part
void ListThread::errorOnFolder(const INTERNALTYPEPATH &fileInfo,const std::string &errorString,const ErrorType &errorType)
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,TransferThread::internalStringTostring(fileInfo)+" error: "+errorString);
    emit send_errorOnFolder(fileInfo,errorString,qobject_cast<ScanFileOrFolder *>(sender()),errorType);
}

//to run the thread
void ListThread::run()
{
    #ifdef ULTRACOPIER_PLUGIN_SPEED_SUPPORT
    clockForTheCopySpeed=new QTimer();
    #endif
    // Safety re-pump timer: MUST be created+started here (on this thread) so its timeout fires
    // on the list thread. Low frequency; its slot is O(1)-gated and a no-op unless work is stuck.
    retryScheduler=new QTimer();
    connect(retryScheduler,&QTimer::timeout,this,&ListThread::safetyReschedule);
    retryScheduler->start(500);
    exec();
}

void ListThread::safetyReschedule()
{
    // Guaranteed forward progress for the resilient backup: if nothing is in flight
    // (numberOfInodeOperation==0) yet a transfer entry is still pending, the event-driven pump
    // was missed (e.g. a put-to-end retry whose lone thread reached Idle a hair after the single
    // pump). Restart the scheduler. All checks are O(1), so during normal copying
    // (numberOfInodeOperation>0) or when idle in the tray (no pending entry) this does nothing.
    if(stopIt || putInPause)
    {
        safetyStallTicks=0;
        return;
    }
    // Backstop for the resilient backup: gate on the RELIABLE running count (iterate the threads),
    // never on numberOfInodeOperation -- a stale-completion race during a multi-file put-to-end
    // error storm can leak that counter (observed nInode=3 while 0 threads run) and would wedge a
    // nInode==0 gate forever.
    // NB this backstop deliberately fires OFTEN (getNumberOfTranferRuning() size-filters out small
    // files, so a small-file backlog reads 0): that frequent re-pump is what actually recovers a
    // put-to-end deferral whose event-driven pump was missed. The re-pump is made SAFE (no
    // double-start of a live entry) by the live-transferId guard in the reset body below -- see there.
    if(getNumberOfTranferRuning()!=0 ||
            (actionToDoListTransferEmpty() && actionToDoListInode.empty() && actionToDoListInode_afterTheTransfer.empty()))
    {
        safetyStallTicks=0;//a transfer is running, or nothing pending (idle in tray) -> not stalled
        return;
    }
    // NB the idle-gate above now ALSO requires actionToDoListInode_afterTheTransfer to be empty: a
    // non-empty afterTheTransfer with the transfer+inode lists empty is NOT "idle in tray" -- it is the
    // stranded folder-finalization state (deferred SyncDate folder-date actions that a non-normal drain
    // path -- put-to-end give-up, skip of a not-running last entry, a backend timing variant -- never
    // merged back). Falling through here lets the sustained-stall handler below rescue it (merge below).
    // "No transfer running yet work pending" -- but ONLY act if it PERSISTS. During normal copying
    // there are brief windows (between files, mid put-to-end defer) where this is momentarily true;
    // acting then would re-pump every tick, keep the process non-idle and never let it settle. A
    // real stall stays true indefinitely, so require a few consecutive ticks (~1.5s) first.
    if(++safetyStallTicks<3)
        return;
    safetyStallTicks=0;
    // GENUINE STALL (no transfer thread running for ~1.5s, yet work pending). The transfer list is
    // the source of truth; every transient in-flight flag is stale now (nothing is actually
    // running). A stale-completion race on the put-to-end path can leave: a thread Idle but still
    // owning a transferId, an entry flagged isRunning with no live transfer, a leaked
    // numberOfInodeOperation, or stale members of overCheckUsedThread/putAtBottomAfterError -- any
    // of which wedges the scheduler. Clear ALL of it and re-derive by re-pumping; this guarantees a
    // resilient backup always resumes (no permanent stall, no orphaned good file). It runs ONLY on
    // a sustained real stall, so normal copying is untouched.
    // THE #4 DOUBLE-START ROOT CAUSE. This backstop fires spuriously mid healthy copy (getNumber
    // OfTranferRuning() size-filters small transfers to 0), so it races the per-file completions,
    // which are QUEUED signals processed on THIS thread: a worker that just finished is Idle with its
    // transferId still set until its transferInodeIsClosed slot runs (it does indexOfActionToDoTransfer
    // (transferId) -> tombstone the entry + clear the id). If we blindly zero that Idle thread's id
    // here, its pending completion then looks up id 0, fails to find the entry, and NEVER tombstones
    // it -> the entry lingers isRunning=true/unremoved with no owner -> a later tick re-queues it ->
    // doNewActions hands the SAME file to a second thread and copies it AGAIN (the duplicate
    // create-opens). So:
    //  * liveEntryIds = ids that STILL have a non-tombstone entry: a thread holding such an id has a
    //    queued completion that will clean up itself -- DO NOT zero it (that is what orphaned it).
    //  * ownedTransferIds = every id any thread still owns: its entry is being transferred or is
    //    draining its completion -- it is NOT stranded, so DO NOT re-queue it (a second double-start).
    // On a GENUINE stall every completion has long since run, so every thread is Idle with id 0 and
    // both sets are empty -> the truly-stranded entries (crashed thread, missed pump) are re-queued
    // and the stale Idle ids cleared, exactly as before -- the put-to-end / missed-pump recovery is
    // preserved.
    std::unordered_set<uint64_t> liveEntryIds;
    std::unordered_set<uint64_t> ownedTransferIds;
    for(size_t i=0;i<actionToDoListTransfer.size();++i)
        if(!actionToDoListTransfer[i]->removed)
            liveEntryIds.insert(actionToDoListTransfer[i]->id);
    for(size_t t=0;t<transferThreadList.size();++t)
        if(transferThreadList.at(t)->transferId!=0)
            ownedTransferIds.insert(transferThreadList.at(t)->transferId);
    for(size_t t=0;t<transferThreadList.size();++t)
    {
        TransferThreadImpl *th=transferThreadList.at(t);
        // zero a stale id on an Idle thread ONLY when NO live entry still claims it (else its
        // completion is merely queued and would be orphaned -- see above)
        if(th->getStat()==TransferStat_Idle && th->transferId!=0 &&
                liveEntryIds.find(th->transferId)==liveEntryIds.cend())
        {
            th->transferId=0;
            th->transferSize=0;
        }
    }
    for(size_t i=0;i<actionToDoListTransfer.size();++i)
        if(!actionToDoListTransfer[i]->removed &&
                ownedTransferIds.find(actionToDoListTransfer[i]->id)==ownedTransferIds.cend())
            actionToDoListTransfer[i]->isRunning=false;//re-queue ONLY genuinely-stranded (unowned) entries
    numberOfInodeOperation=0;
    overCheckUsedThread.clear();
    putAtBottomAfterError.clear();
    // Rescue deferred folder-date (SyncDate) actions stranded in afterTheTransfer by a drain path that
    // skipped the normal-completion merge (put-to-end give-up, skip of a not-running last entry, or a
    // backend timing variant that never hit transferInodeIsClosed's merge). With the transfer list empty
    // they would otherwise never finalize and the copy never reaches Idle -- the end-of-copy HANG copying
    // un-creatable symlinks (Windows 1314). Same merge + barrier the normal-completion path uses
    // (transfer list empty => every child already closed), so folder dates stay correctly ordered.
    if(actionToDoListTransferEmpty() && !actionToDoListInode_afterTheTransfer.empty())
    {
        actionToDoListInode.insert(actionToDoListInode.cbegin(),actionToDoListInode_afterTheTransfer.cbegin(),actionToDoListInode_afterTheTransfer.cend());
        actionToDoListInode_afterTheTransfer.clear();
    }
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Critical,"safety re-pump: sustained stall -> full scheduler reset + reschedule");
    doNewActions_inode_manipulation();
    doNewActions_start_transfer();
}

void ListThread::getNeedPutAtBottom(const INTERNALTYPEPATH &fileInfo, const std::string &errorString, TransferThreadImpl *thread,
                                    const ErrorType &errorType)
{
    (void)fileInfo;
    (void)errorString;
    (void)errorType;
    thread->putAtBottom();
}

/// \to create transfer thread
void ListThread::createTransferThread()
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"start");
    if(stopIt)
        return;
    if(transferThreadList.size()>=(unsigned int)inodeThreads)
        return;
    transferThreadList.push_back(new TransferThreadImpl());
    TransferThreadImpl * last=transferThreadList.back();
    last->transferId=0;
    last->transferSize=0;
    last->setRightTransfer(doRightTransfer);
    last->setKeepDate(keepDate);
    last->setCoalesceSourceStat(coalesceSourceStat);
    last->setOsSpecFlags(os_spec_flags);
    last->setNativeCopy(native_copy);
    last->setAlwaysFileExistsAction(alwaysDoThisActionForFileExists);
    #ifdef ULTRACOPIER_PLUGIN_SPEED_SUPPORT
    if(!last->setBlockSize(blockSizeAfterSpeedLimitation))
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Warning,"unable to set the block size: "+std::to_string(blockSizeAfterSpeedLimitation));
    #endif
    last->setDeletePartiallyTransferredFiles(deletePartiallyTransferredFiles);
    last->setBuffer(buffer);
    last->setChecksum(checksum);
    #ifdef ULTRACOPIER_PLUGIN_RSYNC
    last->setRsync(rsync);
    #endif

    #if defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)
    last->writeFileList=new QMultiHash<QString,TransferThreadPipelined *>();
    last->writeFileListMutex=new QMutex();
    #else
    last->writeThread.writeFileList=new QMultiHash<QString,WriteThread *>();
    last->writeThread.writeFileListMutex=new QMutex();
    #endif

    #ifdef ULTRACOPIER_PLUGIN_DEBUG
    if(!connect(last,&TransferThread::debugInformation,             this,&ListThread::debugInformation,             Qt::QueuedConnection))
        abort();
    #endif // ULTRACOPIER_PLUGIN_DEBUG
    if(!connect(last,&TransferThread::errorOnFile,                  this,&ListThread::errorOnFile,                  Qt::QueuedConnection))
        abort();
    if(!connect(last,&TransferThread::fileAlreadyExists,            this,&ListThread::fileAlreadyExists,            Qt::QueuedConnection))
        abort();
    if(!connect(last,&TransferThread::tryPutAtBottom,               this,&ListThread::transferPutAtBottom,          Qt::QueuedConnection))
        abort();
    if(!connect(last,&TransferThread::readStopped,                  this,&ListThread::doNewActions_start_transfer,  Qt::QueuedConnection))
        abort();
    if(!connect(last,&TransferThread::preOperationStopped,          this,&ListThread::doNewActions_start_transfer,	Qt::QueuedConnection))
        abort();
    if(!connect(last,&TransferThread::postOperationStopped,         this,&ListThread::transferInodeIsClosed,        Qt::QueuedConnection))
        abort();
    if(!connect(last,&TransferThread::checkIfItCanBeResumed,		this,&ListThread::restartTransferIfItCan,       Qt::QueuedConnection))
        abort();
    if(!connect(last,&TransferThread::pushStat,                     this,&ListThread::newTransferStat,              Qt::QueuedConnection))
        abort();

    if(!connect(this,&ListThread::send_sendNewRenamingRules,		last,&TransferThread::setRenamingRules,         Qt::QueuedConnection))
        abort();

    if(!connect(this,&ListThread::send_updateMount,                 last,&TransferThread::set_updateMount,          Qt::QueuedConnection))
        abort();
    #ifdef ULTRACOPIER_PLUGIN_SPEED_SUPPORT
    last->setMultiForBigSpeed(multiForBigSpeed);
    //speed limitation
    connect(clockForTheCopySpeed,	&QTimer::timeout,			last,	&TransferThreadImpl::timeOfTheBlockCopyFinished,		Qt::QueuedConnection);
    #endif

    last->setObjectName(QStringLiteral("transfer %1").arg(transferThreadList.size()-1));
    #if !defined(ULTRACOPIER_PLUGIN_IO_URING) && !defined(ULTRACOPIER_PLUGIN_WINIOCP)
    last->readThread.setObjectName(QStringLiteral("read %1").arg(transferThreadList.size()-1));
    last->writeThread.setObjectName(QStringLiteral("write %1").arg(transferThreadList.size()-1));
    #endif
    last->setRenamingRules(firstRenamingRule,otherRenamingRule);
    #ifdef ULTRACOPIER_PLUGIN_DEBUG
    last->setId(transferThreadList.size()-1);
    #endif
    if(transferThreadList.size()>=(unsigned int)inodeThreads)
    {
        ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"create the last of the "+std::to_string(inodeThreads)+" transferThread");
        return;
    }
    if(stopIt)
        return;
    doNewActions_inode_manipulation();
    emit askNewTransferThread();
}

void ListThread::deleteTransferThread()
{
    ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"start");
    int loop_size=transferThreadList.size();
    if(loop_size>inodeThreads)
    {
        int index=0;
        while(index<loop_size && loop_size>inodeThreads)
        {
            ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"delete thread ["+std::to_string(index)+"]");
            if(transferThreadList.at(index)->getStat()==TransferStat_Idle && transferThreadList.at(index)->transferId==0)
            {
                transferThreadList.at(index)->stop();
                // Join (delete -> ~TransferThreadAsync waits the threads) BEFORE freeing the per-thread
                // write-collision list/mutex -- freeing before the join is a use-after-free (the
                // WriteThread may still lock writeFileListMutex in internalClose()->resumeNotStarted()).
                #if defined(ULTRACOPIER_PLUGIN_IO_URING) || defined(ULTRACOPIER_PLUGIN_WINIOCP)
                auto * const doomedWriteFileList=transferThreadList.at(index)->writeFileList;
                auto * const doomedWriteFileListMutex=transferThreadList.at(index)->writeFileListMutex;
                #else
                auto * const doomedWriteFileList=transferThreadList.at(index)->writeThread.writeFileList;
                auto * const doomedWriteFileListMutex=transferThreadList.at(index)->writeThread.writeFileListMutex;
                #endif
                delete transferThreadList.at(index);//->deleteLayer();
                delete doomedWriteFileList;
                delete doomedWriteFileListMutex;
                transferThreadList[index]=NULL;
                transferThreadList.erase(transferThreadList.cbegin()+index);
                loop_size--;
            }
            else
                index++;
        }
        if(loop_size==inodeThreads)
            ULTRACOPIER_DEBUGCONSOLE(Ultracopier::DebugLevel_Notice,"inodeThreads is lowered to the right value: "+std::to_string(inodeThreads));
    }
}

void ListThread::setMkFullPath(const bool mkFullPath)
{
    this->mkFullPath=mkFullPath;
    mkPathQueue.setMkFullPath(mkFullPath);
    unsigned int index=0;
    while(index<transferThreadList.size())
    {
        transferThreadList.at(index)->setMkFullPath(mkFullPath);
        index++;
    }
}
