/* virtualdirmode_copy.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2013-2023 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "virtualdirmode.hh"
#include "aguix/fieldlistviewdnd.h"
#include "aguix/textstorage.h"
#include "aguix/textview.h"
#include "aguix/button.h"
#include "worker_locale.h"
#include "worker.h"
#include "nwcentryselectionstate.hh"
#include "datei.h"
#include "copyopwin.hh"
#include "copyorder.hh"
#include "copycore.hh"
#include "nmcopyopdir.hh"
#include "nwc_path.hh"
#include "copystate.hh"
#include "nwc_os_makedirs.hh"

const static int BGCOPY_UPDATE_TIMEOUT ( 1000 / 25 );

void VirtualDirMode::copy( std::shared_ptr< struct copyorder > co )
{
    bool cancel = false;
    CopyOpWin *cowin;
    char *textstr, *buttonstr;
    std::list<const FileEntry*>::iterator itfe1;
    VirtualDirMode *updatevdm = NULL;
    bool insertElementsIntoVDM = false;

    if ( ! co ) return;

    if ( co->destdir.empty() ) return;

    if ( co->cowin == NULL ) return;

    if ( ! ce.get() ) return;

    finishsearchmode();
  
    if ( Datei::fileExistsExt( co->destdir.c_str() ) != Datei::D_FE_DIR ) {
        // no dir as destination
        textstr = (char*)_allocsafe( strlen( catalog.getLocale( 67 ) ) + strlen( co->destdir.c_str() ) + 1 );
        sprintf( textstr, catalog.getLocale( 67 ), co->destdir.c_str() );
        buttonstr = dupstring( catalog.getLocale(11) );
        Worker::getRequester()->request( catalog.getLocale( 125 ), textstr, buttonstr );
        _freesafe( textstr );
        _freesafe( buttonstr );
        return;
    }
  
    disableDirWatch();
  
    // clear the reclist so the slace doesn't block us because he is still reading
    // from disk
    ft_request_list_clear();
  
    if ( getCurrentDirectory() == co->destdir ) {
        if ( ! co->external_sources ) {
            co->do_rename = true;
        }
        updatevdm = this;
    } else {
        Lister *olister = parentlister->getWorker()->getOtherLister( parentlister );
        if ( olister != NULL ) {
            ListerMode *lm = olister->getActiveMode();
            if ( lm != NULL ) {
                if ( lm->getCurrentDirectory() == co->destdir ) {
                    updatevdm = dynamic_cast< VirtualDirMode * >( lm );
                }
            }
        }
    }

    if ( updatevdm != NULL && updatevdm != this ) {
        updatevdm->disableDirWatch();

        if ( ! updatevdm->currentDirIsReal() ) {
            insertElementsIntoVDM = true;
        }
    }
  
    // no sense for follow_symlinks when moving
    if ( co->move == true ) co->follow_symlinks = false;

    /* now presupposition checked
       next search for the files to copy
       therefore collect entries to copy in a list together with the LVC (when on root-level)
       then for each entry do recursive call to copy it
       after success deactivate LVC */

    /* first: create List contains NM_specialsource
       when source==COPY_SPECIAL then the given list but check for existing of the FEs
       else source==COPY_ONLYACTIVE => take the activefe (from ce) in this list
       else take all selected entries (except the "..")
    */

    cowin = co->cowin;

    cowin->open();
    cowin->setmessage( catalog.getLocale( 122 ), 0 );
    if ( cowin->redraw() & 1 ) cancel = true;

    std::shared_ptr< CopyCore > cc( new CopyCore( co ) );
    std::shared_ptr< CopyState > cs( new CopyState( cc ) );

    cs->m_updatevdm = updatevdm;
    cs->m_insert_elements_into_vdm = insertElementsIntoVDM;

    if ( cancel ) cc->setCancel( cancel );

    {
        std::list< NM_specialsourceExt > copylist;
        std::string source_base_dir;

        switch ( co->source ) {
            case copyorder::COPY_SPECIAL:
                copySourceList( co->sources,
                                copylist,
                                co->external_sources );
                break;
            case copyorder::COPY_ONLYACTIVE:
                getSelFiles( copylist, LM_GETFILES_ONLYACTIVE, false );
                if ( ! currentDirIsReal() ) {
                    source_base_dir = getCurrentDirectory();
                }
                break;
            default:  // all selected entries
                getSelFiles( copylist, LM_GETFILES_SELORACT, false );
                if ( ! currentDirIsReal() ) {
                    source_base_dir = getCurrentDirectory();
                }
                break;
        }
        for ( auto &it : copylist ) {
            if ( ! it.entry() ) continue;

            if ( check_for_bg_copy_collision( it.entry()->fullname, cs ) == false ) {
                std::vector< NWC::OS::deep_dir_info > file_base_segments;

                if ( co->vdir_preserve_dir_structure &&
                     ! source_base_dir.empty() ) {
                    std::string file_base;

                    file_base = NWC::Path::get_extended_basename( source_base_dir,
                                                                  NWC::Path::dirname( it.entry()->fullname ) );

                    int res = NWC::OS::prepare_deep_dir_info( source_base_dir,
                                                              file_base,
                                                              file_base_segments );

                    if ( res != 0 ) {
                        std::string buttons = catalog.getLocale( 8 );

                        std::string text = AGUIXUtils::formatStringToString( catalog.getLocale( 1211 ),
                                                                             it.entry()->fullname );

                        cowin->request( catalog.getLocale( 123 ), text.c_str(), buttons.c_str() );
                        cc->setCancel( true );
                        break;
                    }
                }

                cc->registerFEToCopy( *it.entry(), it.getID(),
                                      file_base_segments );
            } else {
                cc->setCancel( true );
            }
        }
    }

    if ( cc->buildCopyDatabase() != 0 ) cc->setCancel( true );

    bool skip_space_test = false;

    // this is an simple assumption to skip the space test if both
    // base directories are on the same device. Moving will probably
    // not taking any disk space. This is of course not accurate as
    // sub-dirs can be on different devices, or rename could still
    // fail on the same device, etc.
    if ( co->move ) {
        NWC::FSEntry source( getCurrentDirectory() );
        NWC::FSEntry dest( co->destdir );

        if ( dest.stat_dev() ==
             source.stat_dev() ) {
            skip_space_test = true;
        }
    }
    
    // this is just the value from the last update, it might not be correct
    if ( ! skip_space_test && parentlister->getWorker()->PS_readSpace( co->destdir.c_str() ) == 0 ) {
        loff_t total_space = parentlister->getWorker()->PS_getSpace();
        loff_t free_blocks = parentlister->getWorker()->PS_getFreeSpace();
        loff_t block_size = parentlister->getWorker()->PS_getBlocksize();

        if ( total_space > 0 && block_size > 0 && ( ( cc->getBytesToCopy() ) / block_size ) + 1 >= free_blocks ) {
            // files will possibly not fit on target, ask to continue or cancel

            std::string buttons = catalog.getLocale( 629 );
            buttons += "|";
            buttons += catalog.getLocale( 8 );

            std::string freespaceh = parentlister->getWorker()->PS_getFreeSpaceH();

            std::string text = AGUIXUtils::formatStringToString( catalog.getLocale( 992 ),
                                                                 co->destdir.c_str(),
                                                                 freespaceh.c_str() );

            int erg;

            erg = cowin->request( catalog.getLocale( 123 ), text.c_str(), buttons.c_str() );

            if ( erg != 0 ) {
                cc->setCancel( true );
            }
        }
    }

    // now database ready, can start copy-process

    int cs_id = cs->m_id;
    bool move = co->move;

    cc->setPreCopyCallback( [this,
                             cs_id]( loff_t size, int id, bool is_dir )
                            {
                                m_bg_copy_status.post_copy_cb_replies.mutex.lock();
                                m_bg_copy_status.post_copy_cb_replies.messages.push_back( CopyBGMessage( cs_id, size, id, is_dir ) );
                                m_bg_copy_status.post_copy_cb_replies.mutex.unlock();
                            } );

    cc->setPostCopyCallback( [this,
                              cs_id,
                              move]( const std::string &source_fullname, int id,
                                     CopyCore::nm_copy_t res,
                                     bool is_dir, unsigned long dir_error_counter,
                                     const std::string &target_fullname )
                             {
                                 m_bg_copy_status.post_copy_cb_replies.mutex.lock();
                                 m_bg_copy_status.post_copy_cb_replies.messages.push_back( CopyBGMessage( cs_id, source_fullname,
                                                                                                          id, res, is_dir,
                                                                                                          dir_error_counter,
                                                                                                          target_fullname ) );

                                 if ( res == CopyCore::NM_COPY_OK ||
                                      res == CopyCore::NM_COPY_NEED_DELETE ) {
                                     m_bg_copy_status.post_copy_cb_replies.pers_path_store_relocates.push_back( std::make_tuple( target_fullname,
                                                                                                                                 source_fullname,
                                                                                                                                 move ) );
                                 }
                                 
                                 m_bg_copy_status.post_copy_cb_replies.mutex.unlock();
                             } );

    if ( ! cc->getCancel() ) {
        m_bg_copy_status.active_copy_ops.push_back( cs );

        cc->executeCopy();
    }

    while ( ! cs->m_detached ) {
        handle_bg_copy_ops( false );

        if ( std::find( m_bg_copy_status.active_copy_ops.begin(),
                        m_bg_copy_status.active_copy_ops.end(),
                        cs ) == m_bg_copy_status.active_copy_ops.end() ) {
            break;
        }

        cs->m_cc->timed_wait_for_finished();
    }

    enableDirWatch();
    if ( updatevdm != NULL && updatevdm != this ) {
        updatevdm->enableDirWatch();
    }
}

void VirtualDirMode::handle_bg_copy_ops( AGMessage *msg )
{
    for ( auto it = m_bg_copy_status.active_copy_ops.begin();
          it != m_bg_copy_status.active_copy_ops.end();
          it++ ) {

        int res = (*it)->m_cc->getCopyOrder()->cowin->handleMessage( msg );

        if ( res & 1 ) {
            (*it)->m_cc->setAskToCancel();
        } else if ( res & 2 ) {
            (*it)->m_detached = true;

            if ( ! m_bg_copy_status.timeout_enabled ) {
                parentlister->getWorker()->registerTimeout( BGCOPY_UPDATE_TIMEOUT );
                m_bg_copy_status.timeout_enabled = true;
            }

            (*it)->m_cc->getCopyOrder()->cowin->hide_detach_button();

            if ( m_bg_copy_status.last_vismark_row >= 0 ) {
                m_lv->showRow( m_bg_copy_status.last_vismark_row, false );
                m_lv->setVisMark( m_bg_copy_status.last_vismark_row, false );
            }
        }
    }

    for ( auto it = m_bg_copy_status.finished_but_unclosed_wins.begin();
          it != m_bg_copy_status.finished_but_unclosed_wins.end();
          ) {

        CopyOpWin *cowin = *it;

        int res = cowin->handleMessage( msg );

        if ( res & 4 ) {
            auto next_it = it;

            next_it++;

            m_bg_copy_status.finished_but_unclosed_wins.erase( it );

            it = next_it;

            cowin->close();
            delete cowin;
        } else {
            it++;
        }
    }
}

void VirtualDirMode::handle_bg_copy_ops( bool skip_message_handling )
{
    // first redraw all windows without message handling
    for ( auto it = m_bg_copy_status.active_copy_ops.begin();
          it != m_bg_copy_status.active_copy_ops.end();
          it++ ) {
        (*it)->m_cc->getCopyOrder()->cowin->redraw( true );
    }

    // now handle window messages
    if ( ! skip_message_handling ) {
        AGMessage *msg;
        do {
            msg = aguix->GetMessage( NULL );

            handle_bg_copy_ops( msg );

            aguix->ReplyMessage(msg);
        } while ( msg != NULL );
    }

    // and finally thread messages and finalize status
    for ( auto it = m_bg_copy_status.active_copy_ops.begin();
          it != m_bg_copy_status.active_copy_ops.end();
          ) {

        process_bg_messages();

        (*it)->m_cc->getCopyOrder()->cowin->master_process_request();

        if ( (*it)->m_cc->finished() ) {
            (*it)->m_cc->join();

            // do it again to get all pending messages
            process_bg_messages();

            finalizeCopy( *it );

            auto next_it = it;
            next_it++;

            m_bg_copy_status.active_copy_ops.erase( it );

            it = next_it;

            if ( m_bg_copy_status.active_copy_ops.empty() ) {
                if ( m_bg_copy_status.timeout_enabled ) {
                    parentlister->getWorker()->unregisterTimeout( BGCOPY_UPDATE_TIMEOUT );
                    m_bg_copy_status.timeout_enabled = false;
                }
            }
        } else {
            it++;
        }
    }
}

void VirtualDirMode::showListOfIncompleteEntries( const std::vector< std::string > &entries )
{
    auto win = std::unique_ptr<AWindow>( new AWindow( aguix,
                                                      0, 0,
                                                      500, 400,
                                                      catalog.getLocale( 1457 ),
                                                      AWindow::AWINDOW_DIALOG  ) );
    win->setTransientForAWindow( aguix->getTransientWindow() );
    win->create();

    AContainer *cont0 = win->setContainer( new AContainer( win.get(), 1, 3 ), true );
    cont0->setMaxSpace( 5 );

    win->addMultiLineText( catalog.getLocale( 1458 ), *cont0, 0, 0, NULL, NULL );

    auto lv = dynamic_cast<FieldListView*>( cont0->add( new FieldListView( aguix, 0, 0, 
                                                                           500, 200, 0 ),
                                                        0, 1, AContainer::CO_MIN ) );
    lv->setNrOfFields( 1 );
    lv->setHBarState( 2 );
    lv->setVBarState( 2 );
    lv->setDefaultColorMode( FieldListView::PRECOLOR_NOTSELORACT);

    for ( const auto &e : entries ) {
        int row = lv->addRow();
        lv->setText( row, 0, e );
    }

    AContainer *cont2 = cont0->add( new AContainer( win.get(), 3, 1 ), 0, 2 );
    cont2->setBorderWidth( 0 );
    cont2->setMinSpace( 0 );
    cont2->setMaxSpace( -1 );

    auto closeb = dynamic_cast<Button*>( cont2->add( new Button( aguix, 0, 0,
                                                                 catalog.getLocale( 633 ),
                                                                 0 ),
                                                     1, 0, AContainer::CO_FIX ) );
   
    win->setDoTabCycling( true );
    win->contMaximize( true );
    win->centerScreen();
    win->show();

    closeb->takeFocus();

    AGMessage *msg;
    int endmode = -1;

    for(; endmode == -1; ) {
        msg = aguix->WaitMessage( win.get() );
        if ( msg != NULL ) {
            switch ( msg->type ) {
                case AG_CLOSEWINDOW:
                    if ( msg->closewindow.window == win->getWindow() ) {
                        endmode = 2;
                    }
                    break;
                case AG_BUTTONCLICKED:
                    if ( msg->button.button == closeb ) {
                        endmode = 0;
                    }
                    break;
                case AG_KEYPRESSED:
                    if ( win->isParent( msg->key.window, false ) == true ) {
                        switch ( msg->key.key ) {
                            case XK_KP_Enter:
                            case XK_Return:
                                if ( closeb->getHasFocus() == true ) {
                                    endmode = 0;
                                }
                                break;
                            case XK_Escape:
                                endmode = 2;
                                break;
                            case XK_F1:
                                endmode = 0;
                                break;
                        }
                    }
                    break;
            }
            aguix->ReplyMessage( msg );
        }
    }
}

void VirtualDirMode::finalizeCopy( std::shared_ptr< CopyState > cs )
{
    if ( m_lv ) {
        m_lv->redraw();
    }

    auto cc = cs->m_cc;

    auto co = cc->getCopyOrder();
  
    // now delete remaining files/dirs when moving
    if ( co->move == true ) {

        auto iti2 = cs->m_entries_to_delete.begin();
        cs->m_row_adjustment = 0;

        cc->deleteCopiedButNotMoved( [&iti2,
                                      &cs,
                                      this]( const std::pair< std::string, bool > &removed_entry )
                                     {
                                         const NWCEntrySelectionState *es = NULL;
                                         const NWC::FSEntry *fse = NULL;

                                         if ( cs->m_detached ) return;

                                         if ( iti2 != cs->m_entries_to_delete.end() ) {
                                             es = ce->getEntry( std::get< 0 >( *iti2 ) );

                                             if ( es ) {

                                                 fse = es->getNWCEntry();
                                             }
                                         }


                                         if ( fse && fse->getFullname() == removed_entry.first ) {
                                             m_lv->showRow( std::get< 1 > ( *iti2 ) - cs->m_row_adjustment );

                                             ce->temporarilyHideEntry( std::get< 0 > ( *iti2 ) );

                                             if ( !ce->isRealDir() ) {
                                                 cs->m_ids_to_remove.push_back( std::get< 0 > ( *iti2 ) );
                                             }

                                             m_lv->deleteRow( std::get< 1 >( *iti2 ) - cs->m_row_adjustment );
                                             m_lv->timeLimitedRedraw();

                                             showCacheState();

                                             cs->m_row_adjustment++;

                                             if ( ce->isRealDir() ) {
                                                 ce->updateActiveForRemoval( std::get< 0 > ( *iti2 ) );
                                             }

                                             iti2++;
                                         }
                                     } );
    }

    if ( ! cc->getCancel() && ! cc->incomplete_entries().empty() ) {
        showListOfIncompleteEntries( cc->incomplete_entries() );
    }
    
    if ( co->cowin != NULL ) {
        if ( co->cowin->getKeepWindow() &&
             ! cc->getCancel() ) {

            co->cowin->setFinished();
            m_bg_copy_status.finished_but_unclosed_wins.push_back( co->cowin );
            co->cowin = NULL;
        } else {
            co->cowin->close();
        }
    }

    if ( ! cs->m_detached ) {
        if ( co->move == true ) {
            if ( !ce->isRealDir() ) {
                ce->beginModification( DMCacheEntryNWC::MOD_REMOVAL );

                for ( auto id : cs->m_ids_to_remove ) {
                    // entry is removed from NWC::Dir, not from DMCacheEntryNWC
                    // this will be done at end
                    ce->removeEntry( id );
                }

                ce->endModification();
            } else {
                ce->reload();
            }

            // trigger all the refresh stuff
            setCurrentCE( ce );
        } else {
            update();
        }

        if ( cs->m_updatevdm && ! cs->m_inserts.empty() ) {
            cs->m_updatevdm->addEntries( cs->m_inserts );
        }
  
        if ( cs->m_updatevdm != NULL ) cs->m_updatevdm->update();

        m_lv->showActive();
        aguix->Flush();
    }
}

void VirtualDirMode::process_bg_messages()
{
    for (;;) {
        m_bg_copy_status.post_copy_cb_replies.mutex.lock();

        if ( m_bg_copy_status.post_copy_cb_replies.messages.empty() ) {
            m_bg_copy_status.post_copy_cb_replies.mutex.unlock();
            break;
        }

        CopyBGMessage msg = m_bg_copy_status.post_copy_cb_replies.messages.front();
        m_bg_copy_status.post_copy_cb_replies.messages.pop_front();

        time_t now = time( NULL );

        for ( auto &e : m_bg_copy_status.post_copy_cb_replies.pers_path_store_relocates ) {
            parentlister->getWorker()->relocateEntriesPers( std::get<0>( e ),
                                                            std::get<1>( e ),
                                                            now,
                                                            std::get<2>( e ) );
        }

        m_bg_copy_status.post_copy_cb_replies.pers_path_store_relocates.clear();
        
        m_bg_copy_status.post_copy_cb_replies.mutex.unlock();

        std::shared_ptr< CopyState > cs;

        for ( auto &tcs : m_bg_copy_status.active_copy_ops ) {
            if ( tcs->m_id == msg.m_copy_state_id ) {
                cs = tcs;
            }
        }

        if ( ! cs ) continue;

        if ( msg.m_type == CopyBGMessage::PRE_MESSAGE &&
             cs->m_detached == false &&
             cs->m_cc->getCopyOrder()->external_sources == false ) {
            int row = getRowForCEPos( msg.m_id, cs->m_cc->getCopyOrder()->move );
            int actual_row = row - cs->m_row_adjustment;
            if ( actual_row >= 0 ) {
                m_lv->setVisMark( actual_row, true );
                m_lv->showRow( actual_row, false );

                m_bg_copy_status.last_vismark_row = actual_row;

                // this is some optimization when copying a lot of small files
                // limit redrawing in this case
                if ( ! msg.m_is_dir ) {
                    if ( msg.m_size > 128 * 1024 ) {
                        m_lv->redraw();
                    } else {
                        m_lv->timeLimitedRedraw();
                    }
                } else {
                    m_lv->redraw();
                }
            }
        } else if ( msg.m_type == CopyBGMessage::POST_MESSAGE ) {
            if ( cs->m_detached == false &&
                 cs->m_cc->getCopyOrder()->external_sources == false ) {
                if ( cs->m_cc->getCopyOrder()->move == false ) {
                    // just copy

                    int row = getRowForCEPos( msg.m_id );

                    if ( row >= 0 ) {
                        m_lv->showRow( row, false );
                        m_lv->setVisMark( row, false );
                    }

                    if ( ! cs->m_cc->getCancel() &&
                         ( msg.m_res == CopyCore::NM_COPY_OK || msg.m_res == CopyCore::NM_COPY_NEED_DELETE ) &&
                         ( msg.m_is_dir == false || msg.m_dir_error_counter == 0 ) ) {
                        // success

                        auto es = ce->getEntry( msg.m_id );

                        if ( es ) {

                            auto fse = es->getNWCEntry();

                            if ( fse && fse->getFullname() == msg.m_source_fullname ) {
                                setEntrySelectionState( msg.m_id, false );

                                if ( cs->m_insert_elements_into_vdm && ! msg.m_target_fullname.empty() ) {
                                    cs->m_inserts.push_back( msg.m_target_fullname );
                                }
                            }
                        }
                    }
                } else {
                    // move so remove entry if successful
                    int row = getRowForCEPos( msg.m_id, true );

                    int actual_row = row - cs->m_row_adjustment;

                    auto es = ce->getEntry( msg.m_id );

                    if ( es ) {

                        auto fse = es->getNWCEntry();

                        m_lv->setVisMark( actual_row, false );

                        if ( fse && fse->getFullname() == msg.m_source_fullname ) {
                            if ( msg.m_res == CopyCore::NM_COPY_OK &&
                                 ( msg.m_is_dir == false || msg.m_dir_error_counter == 0 ) ) {
                                // success so remove entry
                                ce->temporarilyHideEntry( msg.m_id );

                                if ( !ce->isRealDir() ) {
                                    cs->m_ids_to_remove.push_back( msg.m_id );
                                }

                                m_lv->deleteRow( actual_row );
                                m_lv->timeLimitedRedraw();

                                showCacheState();

                                cs->m_row_adjustment++;

                                if ( ce->isRealDir() ) {
                                    ce->updateActiveForRemoval( msg.m_id );
                                }

                                if ( cs->m_insert_elements_into_vdm && ! msg.m_target_fullname.empty() ) {
                                    cs->m_inserts.push_back( msg.m_target_fullname );
                                }
                            } else if ( msg.m_res == CopyCore::NM_COPY_NEED_DELETE ) {
                                if ( actual_row >= 0 ) {
                                    cs->m_entries_to_delete.push_back( std::make_tuple( msg.m_id, actual_row, msg.m_source_fullname ) );
                                }

                                if ( cs->m_insert_elements_into_vdm && ! msg.m_target_fullname.empty() ) {
                                    cs->m_inserts.push_back( msg.m_target_fullname );
                                }
                            } else if ( msg.m_res == CopyCore::NM_COPY_CANCEL ) {
                                cs->m_cc->setCancel( true );
                            }
                        }
                    }
                }
            } else if ( cs->m_cc->getCopyOrder()->external_sources == false ) {
                if ( cs->m_cc->getCopyOrder()->move == false ) {
                    cs->m_entries_to_deselect.push_back( msg.m_source_fullname );
                } else {
                    if ( ( msg.m_res == CopyCore::NM_COPY_OK &&
                           ( msg.m_is_dir == false || msg.m_dir_error_counter == 0 ) ) ||
                         msg.m_res == CopyCore::NM_COPY_NEED_DELETE ) {
                        // success so remove entry

                        cs->m_entries_to_deselect.push_back( msg.m_source_fullname );

                        if ( cs->m_insert_elements_into_vdm && ! msg.m_target_fullname.empty() ) {
                            cs->m_inserts.push_back( msg.m_target_fullname );
                        }
                    } else if ( msg.m_res == CopyCore::NM_COPY_CANCEL ) {
                        cs->m_cc->setCancel( true );
                    }
                }
            } else {
                if ( msg.m_res == CopyCore::NM_COPY_CANCEL ) {
                    cs->m_cc->setCancel( true );
                }
            }
        }
    }
}

void VirtualDirMode::wait_for_bg_copies()
{
    while ( ! m_bg_copy_status.active_copy_ops.empty() ) {
        handle_bg_copy_ops( false );

        usleep( 1000000 / 25 );
    }
}

bool VirtualDirMode::check_for_bg_copy_collision( const std::string &fullname,
                                                  std::shared_ptr< CopyState > cs )
{
    for ( auto it = m_bg_copy_status.active_copy_ops.begin();
          it != m_bg_copy_status.active_copy_ops.end();
          it++ ) {
        if ( NWC::Path::is_prefix_dir( fullname,
                                       (*it)->m_cc->getCopyOrder()->destdir ) ||
             NWC::Path::is_prefix_dir( (*it)->m_cc->getCopyOrder()->destdir,
                                       fullname ) ) {
            // collision
            std::string buttons = catalog.getLocale( 629 );
            buttons += "|";
            buttons += catalog.getLocale( 8 );

            std::string text = AGUIXUtils::formatStringToString( catalog.getLocale( 1040 ),
                                                                 fullname.c_str() );

            int erg;

            erg = cs->m_cc->getCopyOrder()->cowin->request( catalog.getLocale( 123 ), text.c_str(), buttons.c_str() );

            if ( erg != 0 ) {
                return true;
            }
        }
    }

    return false;
}
