//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2018 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  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.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function:file handling on disk (copy,move,rename,delete)

//#define MPXPLAY_USE_DEBUGF 1
#define MPXPLAY_DEBUG_OUTPUT NULL //stdout
#define MPXPLAY_DEBUGOUT_BITRATE stdout

#include "mpxplay.h"
#include "mpxinbuf.h"
#include "au_mixer\au_mixer.h"
#include "display\display.h"
#include "control\cntfuncs.h"
#include "decoders\decoders.h"
#include "diskdriv\diskdriv.h"
#include <malloc.h>
#include <dos.h>

#define DISKFILE_FILECOPY_BLOCKSIZE_MIN 65536
#define DISKFILE_FILECOPY_BUFFER_BLOCKS 4
#define DISKFILE_DEFAULT_TIMEOUTMS_WAITDISP 2000 // in ms
#define DISKFILE_DEFAULT_TIMEOUTMS_READ     5000 // in ms

#ifdef MPXPLAY_GUI_CONSOLE
 #define DISKFILE_GUI_TAB_CHAR " "
#else
 #define DISKFILE_GUI_TAB_CHAR "\t"
#endif

#define DISKFILE_CPYTYPE_COPY         (1<< 0)
#define DISKFILE_CPYTYPE_MOVE         (1<< 1)
#define DISKFILE_CPYTYPE_RENAME       (1<< 2) // no move
#define DISKFILE_CPYTYPE_DEL          (1<< 3) // some functions are common
#define DISKFILE_CPYTYPE_INFO_SIZE    (1<< 4) // for playlist_diskfile_show_multifileinfos
#define DISKFILE_CPYTYPE_INFO_TIME    (1<< 5) // phase 2
#define DISKFILE_CPYTYPE_RENBYID3     (1<< 6) // rename by ID3
#define DISKFILE_CPYTYPE_OUTTIMESTAMP (1<< 7) // put date/time stamp into output filename
#define DISKFILE_CPYTYPE_NEWOUTBYID3  (1<< 8) // open new output file at metadata change (live stream)
#define DISKFILE_CPYTYPE_OUTCUEBYID3  (1<< 9) // create CUE file with metadata changes (live stream) (disables NEWOUTBYID3)
#define DISKFILE_CPYTYPE_IGNOREINDEX  (1<<10) // igonre index, copy whole file

#define DISKFILE_CPYCTRL_MULTIFILE    (1<< 0) // more files are selected
#define DISKFILE_CPYCTRL_NOEXTCHK     (1<< 1) // don't check file extension (copy all files)

#define DISKFILE_CPYCTRL_EMPTYDIRCOPY (1<< 2) // copy empty dirs too
#define DISKFILE_CPYCTRL_SUBONECOPY   (1<< 3) // special copy at one directory if the output dir doesn't exist (don't copy the highest dirname)(for other commander compatibility)
#define DISKFILE_CPYCTRL_WITHSUBDIR   (1<< 4) // copy from playlist to dirbrowser, create one subdir level

#define DISKFILE_CPYCTRL_CREATENOEXT  (1<< 5) // create file without extension
#define DISKFILE_CPYCTRL_MAKEDIR      (1<< 6) // make dir if it doesn't exists
#define DISKFILE_CPYCTRL_OVERWRITE    (1<< 7) // overwrite existent file
#define DISKFILE_CPYCTRL_SKIPFILE     (1<< 8) // skip existent file

#define DISKFILE_CPYCTRL_DELFILE      (1<< 9) //
#define DISKFILE_CPYCTRL_DELSUBDIR    (1<<10) // in delete function only
#define DISKFILE_CPYCTRL_ALLFILE      (1<<11) // for overwrite/skipfile/delfile
#define DISKFILE_CPYCTRL_ALLSUBDIR    (1<<12) // for delsubdir

#define DISKFILE_CPYCTRL_IGNALLERR      (1<<13) // ignore all errors
#define DISKFILE_CPYCTRL_SUBFINDNEXT    (1<<14) // call subdirscan_findnext
#define DISKFILE_CPYCTRL_REOPEN         (1<<15) // reopen currently playing file (ie: after rename)
#define DISKFILE_CPYCTRL_REOPENAVFILE   (1<<16) // reopen by avfile_open
#define DISKFILE_CPYCTRL_STARTAVFILE    (1<<17) // start infile after reopen
#define DISKFILE_CPYCTRL_SKIPWAITHPP    (1<<18) // skip one (del/first) waiting
#define DISKFILE_CPYCTRL_DONTWAITHPP    (1<<19) // don't wait for (the end of) high priority processes
#define DISKFILE_CPYCTRL_SHOWMULTI      (1<<20) // show (F3) type is multi
#define DISKFILE_CPYCTRL_STREAMSRC      (1<<21) // stream source copy (show finish button)
#define DISKFILE_CPYCTRL_PAUSED         (1<<22) // copy is paused
#define DISKFILE_CPYCTRL_OUTPAUSED      (1<<23) // metadata change begin sign (from low level callback) -> don't read input (pause till the metadata end)
#define DISKFILE_CPYCTRL_OUTFNRECREAT   (1<<24) // re-create output filename (ie. at DISKFILE_CPYTYPE_OUTTIMESTAMP)
#define DISKFILE_CPYCTRL_METADATACHG    (1<<25) // metadata->filename change sign (from low level callback)
#define DISKFILE_CPYCTRL_INBACKGROUND   (1<<30) // copy in background // TODO

#define DISKFILE_COUNTCTRL_NEXTFILE   1
#define DISKFILE_COUNTCTRL_NEWSUBDIR  2 // for !EMPTYDIRCOPY
#define DISKFILE_COUNTCTRL_COMPLETE   4

#ifdef MPXPLAY_LINK_CREDENTIALS
// credentials control
#define DISKFILE_CRDCTRL_IMPERSED     (1<< 0) // impresonate(d) loggedon user
#define DISKFILE_CRDCTRL_ALTERUSER    (1<< 1) // use alternative user/credentials
#define DISKFILE_CRDCTRL_ALLALTERUSER (1<< 2) // for all files
#define DISKFILE_CRDCTRL_IGNORED      (1<< 3) // last was ignored
#define DISKFILE_CRDCTRL_IGNALLERR    (1<< 4) // ignore all credentials error
#endif

enum {DISKFILE_STATWINTYPE_NONE = 0, DISKFILE_STATWINTYPE_WAITHIGHPRIO, DISKFILE_STATWINTYPE_COPYMOVE}; // for window changes (clear at change)

typedef struct filecopy_t{
 struct mainvars *mvp;
 struct playlist_side_info *psi_src;       // source tab infos (linked to playlist editor directly!)
 struct playlist_side_info *psi_dest;      // destination tab infos (linked to playlist editor directly!)
// struct playlist_side_info *psi_list_src;  // for playlist editor independent copy (copy of all psi_src infos, not direct link to playlist editor)
 struct playlist_side_info *psi_list_dest; // currently used for CUE indexes
 struct playlist_entry_info *pei_selected; // current subdir or file
 struct playlist_entry_info *pei_curr;     // current file (it's not in subdir)
 struct playlist_entry_info *pei_last;
// struct playlist_entry_info *pei_list_src; //

 unsigned long cpy_type,cpy_ctrl;
 unsigned long directories_selected;
 unsigned long entries_selected;
 unsigned long filenum_selcount;
 unsigned long filenum_curr;
 mpxp_int64_t filebytes_copied;
 mpxp_int64_t outfnrecreat_timeout; // timeout at DISKFILE_CPYCTRL_OUTFNRECREAT waiting for DISKFILE_CPYCTRL_METADATACHG
 int stream_bitrate;

 unsigned int count_ctrl;
 struct playlist_entry_info *count_pei;
 void *count_tw;
 int count_itemnum;
 unsigned int counted_directories;
 unsigned int counted_filenum_all;
 unsigned int counted_filenum_media,counted_filenum_mtime;
 unsigned int counted_filenum_list;
 unsigned int counted_filenum_other;
 mpxp_int64_t counted_filesizes_all;
 mpxp_int64_t counted_filesizes_media;
 mpxp_int64_t counted_filesizes_list;
 mpxp_int64_t counted_timemsec_media;
 mpxp_int64_t counted_copyprocess_starttime_ms;

 void *statwin_tw; // static count/rename/move/del/copy status window
 unsigned int statwin_type; // DISKFILE_STATWINTYPE_
 mpxp_int64_t statwin_begintime_waitdisp; // begin time(out) for displaying "waiting for high priority" window

 int retcode,last_error;
 mpxp_filesize_t filelen_in, filelen_out;
 mpxp_filesize_t filepos_in, filepos_out;
 char *buffer;
 int buf_size, buf_blocksize;
 unsigned int buf_stored_bytes;
 struct mpxplay_diskdrive_data_s *mdds_src,*mdds_dest;
 void *filehand_data_src,*filehand_data_dest;
 struct mpxpframe_s frp_src;
 //struct mpxpframe_s frp_dest;
 char *selected_filename;            // pei->filename or psi->currdir (type==DFT_UPDIR)
 char infilename[MAX_PATHNAMELEN];
 char infilenam_bitrate[MAX_PATHNAMELEN]; // stream_bitrate calculated on this filename
 char outfilename[MAX_PATHNAMELEN];
 char outfilenam_notimestamp[MAX_PATHNAMELEN]; // outfilename without timestamp
 char lastfilename[MAX_PATHNAMELEN]; // last (out)filename (at error)
 char path_src[MAX_PATHNAMELEN];
 char path_dest[MAX_PATHNAMELEN];
 char outpath_argument[MAX_PATHNAMELEN]; // given outpath
 char outpath_root[MAX_PATHNAMELEN]; // starting outpath
 char outpath_newsubdir[MAX_PATHNAMELEN]; // save of the built/renamed outdir
 char outpath_curr[MAX_PATHNAMELEN]; // current outpath (subdir)
 char headtext[48];
 char buttontext[48];
 char source_filtermask[48];
 struct pds_subdirscan_t dsi;
#ifdef MPXPLAY_LINK_CREDENTIALS
 struct mpxp_credentials_s cri;
#endif
}filecopy_t;

static void diskfile_count_files_stop(struct filecopy_t *fc);
static int  diskfile_filecopymove_make_subdir(struct filecopy_t *fc,char *outdirname);
static void diskfile_filecopymove_makefilenames(struct filecopy_t *fc);
static void diskfile_filecopymove_checkfilenames(struct filecopy_t *fc);
static void diskfile_filecopy_get_source_bitrate(struct filecopy_t *fc, mpxp_bool_t live_stream);
static void diskfile_filecopy_closefiles(struct filecopy_t *fc);
static void diskfile_filecopy_do_copy(struct filecopy_t *fc);
static unsigned int diskfile_filemove_check_samedrive(struct filecopy_t *fc);
static void diskfile_filecopyrename_postprocess(struct filecopy_t *fc);
static void diskfile_filecopymove_postprocess(struct filecopy_t *fc);
static void diskfile_filecopymove_select(struct filecopy_t *fc);
static void diskfile_filecopymove_loop(struct filecopy_t *fc);
static void playlist_diskfile_delete_do(struct filecopy_t *fc);
static void diskfile_show_multifileinfos_window(struct filecopy_t *fc);
static unsigned int diskfile_renamebyid3_createoutfilename(struct filecopy_t *fc,struct playlist_side_info *psi, struct playlist_entry_info *pei,char *outbuf,unsigned int bufsize,unsigned int cpytype_ctrl);

extern unsigned int refdisp,displaymode,desktopmode,loadid3tag,playcontrol;
#ifdef __DOS__
extern unsigned int is_lfn_support,uselfn;
#endif

static char source_default_filter_copy[48]=PDS_DIRECTORY_ALLFILES_STR; // !!! all files (not only the supported)
static char source_default_filter_del[48]=PDS_DIRECTORY_ALLFILES_STR; // all files (not only the supported)
static char source_default_filter_info[48]=PDS_DIRECTORY_ALLFILE_STR; // no diff at info

#ifdef MPXPLAY_LINK_CREDENTIALS
#ifdef MPXPLAY_WIN32
#ifndef LOGON32_LOGON_NEW_CREDENTIALS
 #define LOGON32_LOGON_NEW_CREDENTIALS   9L
#endif
#ifndef LOGON32_PROVIDER_WINNT50
#define LOGON32_PROVIDER_WINNT50    3L
#endif

static char diskfile_alternative_username[64];
static char diskfile_alternative_password[64];
//static char diskfile_alternative_domain[128];
#endif
#endif

static void diskfile_statwin_close(struct filecopy_t *fc)
{
 if(fc && fc->statwin_tw){
  display_textwin_closewindow_buttons(fc->statwin_tw);
  fc->statwin_tw=NULL;
 }
 fc->count_itemnum=-1;
 fc->statwin_type = DISKFILE_STATWINTYPE_NONE;
}

static struct filecopy_t *diskfile_filecopy_alloc(struct mainvars *mvp)
{
 struct filecopy_t *fc;

 fc=pds_calloc(1,sizeof(struct filecopy_t));
 if(!fc)
  return fc;

 fc->mvp=mvp;
 fc->psi_src=mvp->psie;
 fc->filenum_curr=1;
 fc->count_itemnum=-1;

 return fc;
}

static void diskfile_filecopy_dealloc(struct filecopy_t *fc)
{
#ifdef __DOS__
 void *tw;
#endif

 diskfile_statwin_close(fc);
#ifdef MPXPLAY_LINK_CREDENTIALS
 playlist_diskfile_credentials_logoff(&fc->cri);
#endif
#ifdef __DOS__
 tw=display_textwin_openwindow_message(NULL,NULL,"Flushing disk caches ...");
 pds_drives_flush();
 display_textwin_closewindow_message(tw);
#endif

 if(fc){
  if(fc->buffer){
   pds_free(fc->buffer);
   fc->buffer=NULL;
  }
  if(fc->psi_list_dest){
   playlist_editlist_tab_close(fc->psi_list_dest);
   pds_free(fc->psi_list_dest);
   fc->psi_list_dest=NULL;
  }
  mpxplay_infile_frame_free(&fc->frp_src);
  pds_free(fc);
 }
 refdisp|=RDT_RESET_EDIT|RDT_EDITOR;
}

//-------------------------------------------------------------------------
static unsigned int diskfile_count_files_chkentry(struct filecopy_t *fc,char *filename,mpxp_filesize_t filesize)
{
 if(fc->cpy_type&DISKFILE_CPYTYPE_INFO_TIME){
  if(mpxplay_infile_check_extension(filename,fc->mdds_src)){
   if(mpxplay_infile_get_header_by_ext(&fc->frp_src,fc->mdds_src,filename,MPXPLAY_INFILE_OPENMODE_CHECK) == MPXPLAY_ERROR_OK)
    fc->counted_timemsec_media+=fc->frp_src.infile_infos->timemsec;
   mpxplay_infile_close(&fc->frp_src);
   fc->counted_filenum_mtime++;
  }
  return 1;
 }

 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_NOEXTCHK)){
  if(mpxplay_infile_check_extension(filename,fc->mdds_src)){
   fc->counted_filenum_media++;
   fc->counted_filesizes_media+=filesize;
  }else if(playlist_loadlist_check_extension(filename)){
   fc->counted_filenum_list++;
   fc->counted_filesizes_list+=filesize;
  }else if(fc->cpy_type&DISKFILE_CPYTYPE_INFO_SIZE){
   fc->counted_filenum_other++;
  }else
   return 0;
 }

 fc->counted_filesizes_all+=filesize;

 return 1;
}

static void diskfile_count_files_updatemsg(struct filecopy_t *fc)
{
 char sout[MAX_PATHNAMELEN+100];
 if(fc->cpy_type&(DISKFILE_CPYTYPE_INFO_SIZE|DISKFILE_CPYTYPE_INFO_TIME))
  diskfile_show_multifileinfos_window(fc);
 else if(fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME|DISKFILE_CPYTYPE_DEL)){
  if(fc->cpy_type&DISKFILE_CPYTYPE_DEL)
   sprintf(sout,"(total %d file%s and %d subdir%s)",fc->counted_filenum_all,((fc->counted_filenum_all>1)? "s":""),fc->counted_directories,((fc->counted_directories>1)? "s":""));
  else if(fc->psi_src->selected_files){
   sprintf(sout," %s %d file%s (total %d file%s with %d subdir%s) to",
     ((fc->cpy_type&DISKFILE_CPYTYPE_COPY)? "Copy":"Move"),
     fc->psi_src->selected_files,((fc->psi_src->selected_files>1)? "s":""),
     fc->counted_filenum_all,((fc->counted_filenum_all>1)? "s":""),
     fc->counted_directories,((fc->counted_directories>1)? "s":""));
  }else{
   snprintf(sout,sizeof(sout)," %s \"%s\" (total %d file%s with %d subdir%s) to",
     ((fc->cpy_type&DISKFILE_CPYTYPE_COPY)? "Copy":"Move"),
     pds_getfilename_from_fullname((fc->pei_selected->entrytype==DFT_UPDIR)? fc->psi_src->currdir:fc->pei_selected->filename),
     fc->counted_filenum_all,((fc->counted_filenum_all>1)? "s":""),
     fc->counted_directories,((fc->counted_directories>1)? "s":""));
  }
  display_textwin_update_msg(fc->count_tw,fc->count_itemnum,sout);
 }
}

static void diskfile_count_files_in_subdirs(struct filecopy_t *fc)
{
 struct playlist_entry_info *pei=fc->count_pei;
 struct pds_subdirscan_t *dsi;
 char searchpath[MAX_PATHNAMELEN];

 if((pei>fc->psi_src->lastentry) || (pei>fc->pei_last))
  goto err_out_finish;

 if((fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) && !(pei->infobits&PEIF_SELECTED))
  goto err_out_skip;

 if(pei->entrytype==DFT_UPLIST)
  goto err_out_skip;

 if((fc->cpy_type&DISKFILE_CPYTYPE_INFO_TIME) && ((pei->entrytype>=DFT_AUDIOFILE) || (pei->entrytype==DFT_NOTCHECKED)) && pds_utf8_filename_wildcard_cmp(pei->filename,fc->source_filtermask)){
  unsigned long timemsec=playlist_entry_get_timemsec(pei);
  if(timemsec)
   fc->counted_timemsec_media+=timemsec;
  else{
   if(mpxplay_infile_get_header_by_ext(&fc->frp_src,fc->mdds_src,pei->filename,MPXPLAY_INFILE_OPENMODE_CHECK) == MPXPLAY_ERROR_OK)
    fc->counted_timemsec_media+=fc->frp_src.infile_infos->timemsec;
   mpxplay_infile_close(&fc->frp_src);
  }
  fc->counted_filenum_mtime++;
 }
 if((pei->entrytype==DFT_DRIVE) || (pei->entrytype==DFT_UPDIR) || (pei->entrytype==DFT_SUBDIR)){
  dsi=&fc->dsi;
  if(!(fc->count_ctrl&DISKFILE_COUNTCTRL_NEXTFILE)){
   unsigned int len;
   fc->mdds_src=pei->mdds;
   switch(pei->entrytype){
    case DFT_DRIVE:len=pds_strncpy(searchpath,pei->filename,sizeof(PDS_DIRECTORY_DRIVE_STR)-1);break;
    case DFT_UPDIR:len=pds_strcpy(searchpath,fc->psi_src->currdir);break;
    default:len=pds_strcpy(searchpath,pei->filename);
   }
   len+=pds_strcpy(&searchpath[len],PDS_DIRECTORY_SEPARATOR_STR);
   len+=pds_strcpy(&searchpath[len],PDS_DIRECTORY_ALLDIR_STR);
   len+=pds_strcpy(&searchpath[len],PDS_DIRECTORY_SEPARATOR_STR);
   pds_strcpy(&searchpath[len],fc->source_filtermask);
   if(!mpxplay_diskdrive_subdirscan_open(fc->mdds_src,searchpath,_A_NORMAL,dsi))
    funcbit_enable(fc->count_ctrl,DISKFILE_COUNTCTRL_NEXTFILE);
   if((fc->cpy_ctrl&DISKFILE_CPYCTRL_EMPTYDIRCOPY) && (fc->cpy_type!=DISKFILE_CPYTYPE_INFO_TIME))
    fc->counted_directories++;
  }
  if(fc->count_ctrl&DISKFILE_COUNTCTRL_NEXTFILE){
   int fferror=mpxplay_diskdrive_subdirscan_findnextfile(fc->mdds_src,dsi);
   if(fferror<0){
    funcbit_disable(fc->count_ctrl,DISKFILE_COUNTCTRL_NEXTFILE);
    mpxplay_diskdrive_subdirscan_close(fc->mdds_src,dsi);
   }else{
    if(dsi->flags&SUBDIRSCAN_FLAG_SUBDIR){
     if(fc->cpy_ctrl&DISKFILE_CPYCTRL_EMPTYDIRCOPY){
      if(fc->cpy_type!=DISKFILE_CPYTYPE_INFO_TIME)
       fc->counted_directories++;
     }else
      funcbit_enable(fc->count_ctrl,DISKFILE_COUNTCTRL_NEWSUBDIR);
    }
    if((fferror==0) && !(dsi->ff->attrib&(_A_SUBDIR|_A_VOLID))){
     if(diskfile_count_files_chkentry(fc,dsi->fullname,dsi->ff->size)){
      if(fc->cpy_type!=DISKFILE_CPYTYPE_INFO_TIME)
       fc->counted_filenum_all++;
      if(fc->count_ctrl&DISKFILE_COUNTCTRL_NEWSUBDIR){
       if(fc->cpy_type!=DISKFILE_CPYTYPE_INFO_TIME)
        fc->counted_directories++;
       funcbit_disable(fc->count_ctrl,DISKFILE_COUNTCTRL_NEWSUBDIR);
      }
     }
    }
    return;
   }
  }
 }else{
  if(fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_INFO_SIZE))
   if(pds_utf8_filename_wildcard_cmp(pei->filename,fc->source_filtermask))
    diskfile_count_files_chkentry(fc,pei->filename,((pei->filesize)? pei->filesize:pds_getfilesize(pei->filename)));
 }

err_out_skip:
 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE)) // no more file
  goto err_out_finish;
 pei++;
 fc->count_pei=pei;
 return;

err_out_finish:
 if((fc->cpy_type&DISKFILE_CPYTYPE_INFO_SIZE) && fc->counted_filenum_media){
  fc->cpy_type=DISKFILE_CPYTYPE_INFO_TIME;
  if(fc->psi_src->selected_files){
   fc->count_pei=fc->pei_selected=fc->psi_src->firstentry;
   fc->pei_last=fc->psi_src->lastentry;
  }else
   fc->count_pei=fc->pei_selected=fc->pei_last=fc->psi_src->editorhighline;
 }else{
  funcbit_enable(fc->count_ctrl,DISKFILE_COUNTCTRL_COMPLETE);
  diskfile_count_files_stop(fc);
 }
}

static void diskfile_count_files_reset(struct filecopy_t *fc)
{
 fc->count_ctrl=0;
 fc->directories_selected=0;
 fc->counted_directories=0;
 fc->counted_filenum_all=0;
 fc->counted_filenum_media=0;
 fc->counted_filenum_mtime=0;
 fc->counted_filenum_list=0;
 fc->counted_filenum_other=0;
 fc->counted_filesizes_all=0;
 fc->counted_filesizes_media=0;
 fc->counted_filesizes_list=0;
 fc->counted_timemsec_media=0;
}

static void diskfile_count_files_start(struct filecopy_t *fc)
{
 struct playlist_entry_info *pei;
 unsigned int filenum,dirnum;

 diskfile_count_files_reset(fc);

 if(fc->psi_src->selected_files){
  pei=fc->psi_src->firstentry;
  filenum=dirnum=0;
  do{
   if(pei->infobits&PEIF_SELECTED){
    if((pei->entrytype==DFT_DRIVE) || (pei->entrytype==DFT_UPDIR) || (pei->entrytype==DFT_SUBDIR))
     dirnum++;
    else if(pds_utf8_filename_wildcard_cmp(pei->filename,fc->source_filtermask))
     filenum++;
   }
   pei++;
  }while(pei<=fc->psi_src->lastentry);
  fc->counted_filenum_all=filenum;
  fc->directories_selected=dirnum;
  fc->entries_selected=dirnum+filenum;
  if(dirnum || (fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_INFO_SIZE)))
   fc->count_pei=fc->psi_src->firstentry;
  else
   funcbit_enable(fc->count_ctrl,DISKFILE_COUNTCTRL_COMPLETE);
 }else{
  pei=fc->psi_src->editorhighline;
  fc->entries_selected=1;
  if((pei->entrytype==DFT_DRIVE) || (pei->entrytype==DFT_UPDIR) || (pei->entrytype==DFT_SUBDIR)){
   fc->count_pei=pei;
   fc->directories_selected=1;
  }else{
   fc->counted_filenum_all=1;
   if(fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_INFO_SIZE)){
    fc->counted_filesizes_all=(pei->filesize)? pei->filesize:pds_getfilesize(pei->filename);
    fc->counted_timemsec_media=playlist_entry_get_timemsec(pei);
   }
   funcbit_enable(fc->count_ctrl,DISKFILE_COUNTCTRL_COMPLETE);
  }
 }

 if(!funcbit_test(fc->count_ctrl,DISKFILE_COUNTCTRL_COMPLETE)){
  mpxplay_timer_addfunc(diskfile_count_files_in_subdirs,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_LOWPRIOR,0);
  mpxplay_timer_addfunc(diskfile_count_files_updatemsg,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_LOWPRIOR,mpxplay_timer_secs_to_counternum(1)/20);
 }
}

static void diskfile_count_files_stop(struct filecopy_t *fc)
{
 if(fc->count_ctrl&DISKFILE_COUNTCTRL_NEXTFILE){
  funcbit_disable(fc->count_ctrl,DISKFILE_COUNTCTRL_NEXTFILE);
  mpxplay_diskdrive_subdirscan_close(fc->mdds_src,&fc->dsi);
 }
 mpxplay_timer_deletefunc(diskfile_count_files_in_subdirs,fc);
 mpxplay_timer_deletefunc(diskfile_count_files_updatemsg,fc);
 diskfile_count_files_updatemsg(fc);
}

//-------------------------------------------------------------------------
static void diskfile_filecopymove_correct_fcpei_add(struct filecopy_t *fc,struct playlist_entry_info *pei)
{
 if(fc->pei_curr && pei<=fc->pei_curr)
  fc->pei_curr++;
 if(fc->pei_selected && pei<=fc->pei_selected)
  fc->pei_selected++;
 fc->pei_last++;
}

static void diskfile_filecopymove_correct_fcpei_del(struct filecopy_t *fc,struct playlist_entry_info *pei)
{
 if(pei==fc->pei_curr)
  fc->pei_curr=NULL;
 if(fc->pei_selected && pei>fc->pei_selected) // ???
  fc->pei_selected--;
}

static void diskfile_filecopymove_correct_fcpei_move(struct filecopy_t *fc,struct playlist_entry_info *pei_dest,struct playlist_entry_info *pei_src)
{
 if(pei_src<fc->pei_selected && pei_dest>fc->pei_selected)
  fc->pei_selected--;
 if(pei_src>fc->pei_selected && pei_dest<fc->pei_selected)
  fc->pei_selected++;
 if(fc->pei_curr==pei_src){
  if(pei_dest && (pei_dest->infobits&PEIF_SELECTED)){
   funcbit_disable(pei_dest->infobits,PEIF_SELECTED);
   if(fc->psi_src->selected_files)
    fc->psi_src->selected_files--;
   refdisp|=RDT_EDITOR;
  }
  fc->pei_curr=NULL;
 }
}

//-------------------------------------------------------------------------
static unsigned int diskfile_filecopymove_build_subdirpath(struct filecopy_t *fc,char *path,char *dirname)
{
 unsigned int len=pds_strcpy(path,fc->outpath_root);
 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_SUBONECOPY)){
  if(path[len-1]!=PDS_DIRECTORY_SEPARATOR_CHAR)
   len+=pds_strcpy(&path[len],PDS_DIRECTORY_SEPARATOR_STR);
  len+=pds_strcpy(&path[len],pds_getfilename_from_fullname(dirname));
 }
 return len;
}

static void diskfile_filecopymove_build_outpath(struct filecopy_t *fc,char *dirname)
{
 struct pds_subdirscan_t *dsi;
 unsigned int i,len;
 char path[MAX_PATHNAMELEN];

 if(fc->cpy_type&DISKFILE_CPYTYPE_DEL)
  return;

 len=diskfile_filecopymove_build_subdirpath(fc,path,dirname);

 dsi=&fc->dsi;
 i=pds_strlen(dsi->startdir);
 if((path[len-1]!=PDS_DIRECTORY_SEPARATOR_CHAR) && (dsi->currdir[i]!=PDS_DIRECTORY_SEPARATOR_CHAR))
  len+=pds_strcpy(&path[len],PDS_DIRECTORY_SEPARATOR_STR);
 len+=pds_strcpy(&path[len],&dsi->currdir[i]);
 pds_strcpy(fc->outpath_curr,path);
}

static void diskfile_filecopymove_rebuild_outpathargument(struct filecopy_t *fc,char *path)
{
 pds_strcpy(fc->outpath_root,path);
 pds_filename_assemble_fullname(fc->outpath_argument,path,PDS_DIRECTORY_ALLFILES_STR);
}

//--------------------------------------------------------------------------
static void diskfile_filecopymove_update_clfn(struct filecopy_t *fc, struct playlist_side_info *psi, struct playlist_entry_info *pei_src, struct playlist_entry_info *pei_dest)
{
 if((pei_src->infobits&PEIF_ID3FILENAME) || !(pei_src->infobits&PEIF_ID3EXIST)){
  playlist_editlist_del_id3_one(psi, pei_dest, I3I_ARTIST);
  playlist_editlist_del_id3_one(psi, pei_dest, I3I_TITLE);
  funcbit_disable(pei_src->infobits,PEIF_ID3EXIST);
  playlist_chkentry_create_id3infos_from_filename(psi, pei_dest, (loadid3tag|ID3LOADMODE_CLFN));
 }
}

static void diskfile_filecopymove_update_sides(struct filecopy_t *fc,char *infilename,char *outfilename,struct playlist_entry_info *pei_src)
{
 struct playlist_side_info *psi=fc->mvp->psi0;
 unsigned int tabnum,modified_side,directory,renmov_delsel;
 struct playlist_entry_info *pei,*new_pei,pei_tmp,pei_temp;
 char inpath[MAX_PATHNAMELEN],strtmp[MAX_PATHNAMELEN];
 inpath[0]=0;

 if(infilename && (fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME|DISKFILE_CPYTYPE_DEL)) && (fc->retcode==MPXPLAY_ERROR_FILEHAND_OK))
  pds_getpath_from_fullname(inpath,infilename);

 if(outfilename){
  if(!pei_src || (fc->cpy_ctrl&DISKFILE_CPYCTRL_STREAMSRC) || ((pei_src->infobits & PEIF_INDEXED) && !(fc->cpy_type & DISKFILE_CPYTYPE_IGNOREINDEX))){ // stream source, indexed input or something else (infos of target doesn't exist in the playlist, load it from the disk)
   pds_memset(&pei_tmp, 0, sizeof(struct playlist_entry_info));
   pei_tmp.mdds = fc->mdds_dest;
   funcbit_enable(pei_tmp.infobits, PEIF_ALLOCATED);
   playlist_editlist_add_filename(NULL, &pei_tmp, outfilename);
   fc->frp_src.pei = &pei_tmp;
   playlist_chkentry_get_onefileinfos_from_file(fc->psi_dest, &pei_tmp, &fc->frp_src,loadid3tag, 0, MPXPLAY_INFILE_OPENMODE_CHECK);
   mpxplay_infile_close(&fc->frp_src);
  }else{ // simple file copy
   pds_memcpy(&pei_tmp, pei_src, sizeof(struct playlist_entry_info));
   pei_tmp.filename = outfilename;
   pei_tmp.mdds = fc->mdds_dest;
  }
 }

 renmov_delsel=((fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME)) && (fc->cpy_ctrl&DISKFILE_CPYCTRL_OVERWRITE) && (pds_utf8_stricmp(inpath,fc->outpath_curr)==0))? 1:0;

 if(outfilename && ((fc->cpy_type&DISKFILE_CPYTYPE_COPY) || renmov_delsel || (pds_utf8_stricmp(inpath,fc->outpath_curr)!=0))){
  for(tabnum=0;tabnum<fc->mvp->editorside_all_tabs;tabnum++,psi++){
   if(!(psi->editsidetype&PLT_ENABLED) || !(psi->editsidetype&PLT_DIRECTORY) || psi->sublistlevel)
    continue;
   if(pds_utf8_stricmp(fc->outpath_curr,psi->currdir)!=0)
    continue;
   modified_side=0;
   pei=playlist_search_filename(psi,outfilename,-1,NULL,0);
   if(!renmov_delsel && ((fc->retcode==MPXPLAY_ERROR_FILEHAND_OK) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_DELETE))){
    if(pei){ // update an existent playlist entry (with the datas from infile)
     do{
      playlist_editlist_delfile_one(psi,pei,EDITLIST_MODE_HEAD|EDITLIST_MODE_ID3);
      if(playlist_editlist_addfile_one(fc->psi_src,psi,&pei_tmp,pei,EDITLIST_MODE_HEAD|EDITLIST_MODE_ID3)){
       diskfile_filecopymove_update_clfn(fc,psi,&pei_tmp,pei);
       modified_side=PLL_CHG_LEN|PLL_CHG_ID3;
      }
      new_pei=playlist_search_filename(psi,outfilename,-1,pei+1,0);
      if(!new_pei)
       break;
      pei=new_pei;
     }while(1); // maybe the same file is more times in the playlist
    }else{   // create a new playlist entry (copy datas from infile)
     if(playlist_editlist_addfile_one(fc->psi_src,psi,&pei_tmp,NULL,(EDITLIST_MODE_ALL&(~EDITLIST_MODE_INDEX)))){
      pei=playlist_editlist_addfileone_postproc(psi,psi->lastentry);
      diskfile_filecopymove_update_clfn(fc,psi,&pei_tmp,pei);
      modified_side=PLL_CHG_ENTRY;
      if(psi==fc->psi_src)
       diskfile_filecopymove_correct_fcpei_add(fc,pei);
     }
    }
   }else{
    if(pei){ // delete entry (output file) at error or at overwrite
     playlist_editlist_delfile_one(psi,pei,EDITLIST_MODE_ALL);
     modified_side=PLL_CHG_ENTRY;
     if(psi==fc->psi_src)
      diskfile_filecopymove_correct_fcpei_del(fc,pei);
    }
   }

   if(modified_side){
    funcbit_enable(psi->editloadtype,modified_side);
    if((psi!=fc->mvp->psie) && !(fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) && !fc->directories_selected)
     playlist_editorhighline_set(psi,pei);
    if(psi==fc->mvp->psip)
     refdisp|=RDT_EDITOR|RDT_BROWSER;
    else
     refdisp|=RDT_EDITOR;
   }
  }
 }

 if(infilename && (fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME|DISKFILE_CPYTYPE_DEL)) && (fc->retcode==MPXPLAY_ERROR_FILEHAND_OK)){
  psi=fc->mvp->psi0;
  pds_getpath_from_fullname(inpath,infilename);
  for(tabnum=0;tabnum<fc->mvp->editorside_all_tabs;tabnum++,psi++){
   if(!(psi->editsidetype&PLT_ENABLED))
    continue;
   directory=((psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel);
   if(directory && fc->directories_selected && !(fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) && (fc->cpy_type&DISKFILE_CPYTYPE_RENAME) && pds_strnicmp(psi->currdir,infilename,pds_strlen(infilename))==0){ // (up)directory rename
    unsigned int inflen=pds_strlen(infilename);
    unsigned int i=pds_strcpy(strtmp,outfilename);
    pds_strcpy(&strtmp[i],&psi->currdir[inflen]);
    pds_strcpy(psi->currdir,strtmp);
    for(pei=psi->firstentry;pei<=psi->lastentry;pei++){
     if(pds_strnicmp(pei->filename,infilename,inflen)==0){
      unsigned int pos=pds_strcpy(strtmp,outfilename);
      pds_strcpy(&strtmp[pos],&pei->filename[inflen]);
      pds_memcpy(&pei_temp,pei,sizeof(struct playlist_entry_info));
      pei_temp.filename=strtmp;
      pei_temp.mdds=fc->mdds_dest;
      playlist_editlist_delfile_one(psi,pei,EDITLIST_MODE_FILENAME);
      playlist_editlist_addfile_one(fc->psi_src,psi,&pei_temp,pei,EDITLIST_MODE_FILENAME);
      funcbit_disable(pei->infobits,PEIF_SORTED); // ??? after manual sort?
      funcbit_enable(psi->editloadtype,PLL_CHG_ENTRY);
      refdisp|=RDT_EDITOR;
     }
    }
   }else if(!directory || pds_utf8_stricmp(inpath,psi->currdir)==0){
    pei=playlist_search_filename(psi,infilename,-1,NULL,0);
    if(pei){
     if((!directory && (fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME))) || (pds_utf8_stricmp(inpath,fc->outpath_curr)==0)){ // rename (or rename in playlist by move)
      if(outfilename){ // ???
       do{
        playlist_editlist_delfile_one(psi,pei,EDITLIST_MODE_FILENAME);
        playlist_editlist_addfile_one(fc->psi_src,psi,&pei_tmp,pei,EDITLIST_MODE_FILENAME);
        diskfile_filecopymove_update_clfn(fc,psi,pei,pei);
        funcbit_disable(pei->infobits,PEIF_SORTED); // ??? after manual sort?
        new_pei=playlist_order_entry(psi,pei);
        if(new_pei && (psi==fc->psi_src)){
         if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) && (pei==fc->pei_curr)) // !!!
          playlist_editorhighline_set(psi,new_pei);
         diskfile_filecopymove_correct_fcpei_move(fc,new_pei,pei);
        }
        pei=playlist_search_filename(psi,infilename,-1,pei+1,0);
       }while(pei); // maybe the same file is more times in the playlist
       funcbit_enable(psi->editloadtype,PLL_CHG_ENTRY);
       refdisp|=RDT_EDITOR;
      }
     }else{                                       // move or del
      if(directory || (psi==fc->psi_src)){
       playlist_editlist_delfile_one(psi,pei,EDITLIST_MODE_ALL);
       funcbit_enable(psi->editloadtype,PLL_CHG_ENTRY);
       if(psi==fc->psi_src)
        diskfile_filecopymove_correct_fcpei_del(fc,pei);
       if(psi==fc->mvp->psip)
        refdisp|=RDT_EDITOR|RDT_BROWSER;
       else
        refdisp|=RDT_EDITOR;
      }
     }
    }
   }
  }
 }

 if(outfilename && (!pei_src || (fc->cpy_ctrl&DISKFILE_CPYCTRL_STREAMSRC))){
  playlist_editlist_del_filename(NULL,&pei_tmp);
  playlist_editlist_del_id3_all(NULL,&pei_tmp);
 }
}

#ifdef MPXPLAY_UTF8
void playlist_diskfile_filecopy_makebar(char *barstr,unsigned int percent)
{
 int i,b=percent*DISKFILE_FILECOPY_BARLEN/100;
 const mpxp_wchar_t fullbox=pds_cvchar_CP437_to_UTF16LE(219); // 'Û'
 const mpxp_wchar_t halfbox=pds_cvchar_CP437_to_UTF16LE(176); // '°'
 mpxp_wchar_t u16buf[DISKFILE_FILECOPY_BARLEN+1]; 

 for(i=0;i<DISKFILE_FILECOPY_BARLEN;i++){
  if(i<b)
   u16buf[i]=fullbox;
  else
   u16buf[i]=halfbox;
 }
 u16buf[i]=0;
 pds_str_UTF16LE_to_UTF8(barstr,u16buf,DISKFILE_FILECOPY_BARBUFLEN);
}
#else
void playlist_diskfile_filecopy_makebar(char *barstr,unsigned int percent)
{
 int i,b=percent*DISKFILE_FILECOPY_BARLEN/100;

 for(i=0;i<DISKFILE_FILECOPY_BARLEN;i++){
  if(i<b)
   barstr[i]='Û'; // 219
  else
   barstr[i]='°'; // 176
 }
 barstr[i]=0;
}
#endif

static display_textwin_button_t buttons_errorhand_select[]={
 {" Ignore "    ,0x1769}, // 'i'
 {""            ,0x1749}, // 'I'
 {" Ignore All ",0x1e61}, // 'a'
 {""            ,0x1e41}, // 'A'
 {" Cancel "    ,0x2e63}, // 'c'
 {""            ,0x2e43}, // 'C'
 {""            ,KEY_ESC},// ESC
 {NULL,0}
};

static display_textwin_button_t buttons_errorhand_ok[]={
 {"[ Ok ]"    ,KEY_ESC}, //
 {NULL,0}
};

static void diskfile_filecopy_errorhandler(struct filecopy_t *fc,unsigned int extkey)
{
 switch(extkey){
  case 0x1e61:
  case 0x1e41:funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_IGNALLERR);
  case 0x1769:
  case 0x1749:mpxplay_timer_addfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
              break;
  default:mpxplay_timer_addfunc(diskfile_filecopy_dealloc,fc,MPXPLAY_TIMERFLAG_INDOS,0);
 }
}

static void diskfile_filecopy_errormessage(struct filecopy_t *fc,char *msg,char *filename)
{
 const unsigned int flags=(TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN);
 void *tw;

 diskfile_statwin_close(fc);

 tw=display_textwin_allocwindow_items(NULL,flags," Error ",diskfile_filecopy_errorhandler,fc);
 display_textwin_additem_msg_alloc(tw,flags,0,-1,msg);
 display_textwin_additem_msg_alloc(tw,flags,0,-1,filename);
 if((fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) || (fc->cpy_ctrl&DISKFILE_CPYCTRL_SUBFINDNEXT)){
  display_textwin_additem_msg_static(tw,flags,0,-1,"");
  display_textwin_additem_buttons(tw,flags,0,-1,buttons_errorhand_select,NULL);
 }else
  display_textwin_additem_buttons(tw,flags,0,-1,buttons_errorhand_ok,NULL);
 display_textwin_openwindow_items(tw,0,0,0);
}

static void playlist_diskfile_show_errmsg(struct filecopy_t *fc)
{
 switch(fc->last_error){
  case MPXPLAY_ERROR_FILEHAND_OK:break;
  case MPXPLAY_ERROR_FILEHAND_USERABORT:diskfile_filecopy_errormessage(fc,"User abort at",fc->infilename);break;
  case MPXPLAY_ERROR_FILEHAND_MEMORY   :diskfile_filecopy_errormessage(fc,"Memory allocation error at",fc->infilename);break;
  case MPXPLAY_ERROR_FILEHAND_CANTOPEN :diskfile_filecopy_errormessage(fc,"Cannot open for reading at",fc->infilename);break;
  case MPXPLAY_ERROR_FILEHAND_CANTCREATE:diskfile_filecopy_errormessage(fc,"Cannot create",fc->lastfilename);break;
  case MPXPLAY_ERROR_FILEHAND_CANTREAD :diskfile_filecopy_errormessage(fc,"Cannot read file (Where is the disk?)",fc->infilename);break;
  case MPXPLAY_ERROR_FILEHAND_CANTWRITE:diskfile_filecopy_errormessage(fc,"Cannot write file (Disk full?)",fc->lastfilename);break;
  case MPXPLAY_ERROR_FILEHAND_READONLY :diskfile_filecopy_errormessage(fc,"Couldn't delete/rename/move file (it's read only)",fc->infilename);break;
  case MPXPLAY_ERROR_FILEHAND_DELETE   :diskfile_filecopy_errormessage(fc,"Couldn't delete file",fc->infilename);break;
  case MPXPLAY_ERROR_FILEHAND_RENAME   :diskfile_filecopy_errormessage(fc,"Couldn't rename/move file",fc->infilename);break;
  case MPXPLAY_ERROR_FILEHAND_REMOVEDIR:diskfile_filecopy_errormessage(fc,"Couldn't delete directory",fc->lastfilename);break;
  case MPXPLAY_ERROR_FILEHAND_CHANGEATTR:diskfile_filecopy_errormessage(fc,"Couldn't modify the attribs of",fc->infilename);break;
  //case MPXPLAY_ERROR_FILEHAND_CANTPERFORM:diskfile_filecopy_errormessage(fc,"Cannot perform operation on this filetype",fc->infilename);break;
  case MPXPLAY_ERROR_FILEHAND_CANTCOPY :diskfile_filecopy_errormessage(fc,"Cannot copy/move (probably a correct fullpath is missing)!",fc->lastfilename);break;
  case MPXPLAY_ERROR_FILEHAND_COPYDIR  :diskfile_filecopy_errormessage(fc,"Cannot move/del drive/updir/uplist",fc->infilename);break;
  case MPXPLAY_ERROR_FILEHAND_SAMEDIR  :diskfile_filecopy_errormessage(fc,"Cannot copy/move a directory to itself!",fc->lastfilename);break;
  case MPXPLAY_ERROR_FILEHAND_MULTITO1 :diskfile_filecopy_errormessage(fc,"Cannot copy/move multiply files to one!\nDestination must be an existent path:",fc->outpath_argument);break;
  case MPXPLAY_ERROR_FILEHAND_SKIPFILE :break;
  default:diskfile_filecopy_errormessage(fc,"Unknown error in copy at",fc->infilename);break;
 }
}

static display_textwin_button_t buttons_error_existfile[]={
 {" Overwrite ",0x186f}, // 'o'
 {""           ,0x184f}, // 'O'
 {" All "      ,0x1e61}, // 'a'
 {""           ,0x1e41}, // 'A'
 {" Skip "     ,0x1f73}, // 's'
 {""           ,0x1f53}, // 'S'
 {" Skipall "  ,0x256b}, // 'k'
 {""           ,0x254b}, // 'K'
 {" Cancel "   ,0x2e63}, // 'c'
 {""           ,0x2e43}, // 'C'
 {""           ,KEY_ESC},// ESC
 {NULL,0}
};

static display_textwin_button_t buttons_error_samefile[]={
 {"[Rename/cont]",0x1372}, // 'r'
 {""           ,0x1352}, // 'R'
 {"[Skip file]",0x1f73}, // 's'
 {""           ,0x1f53}, // 'S'
 {"[Cancel]"   ,0x2e63}, // 'c'
 {""           ,0x2e43}, // 'C'
 {""           ,KEY_ESC},// ESC
 {NULL,0}
};

static display_textwin_button_t buttons_error_noext[]={
 {"[Rename/cont]",0x1372}, // 'r'
 {""           ,0x1352}, // 'R'
 {"[Make dir]" ,0x326d}, // 'm'
 {""           ,0x324d}, // 'M'
 {"[Skip file]",0x1f73}, // 's'
 {""           ,0x1f53}, // 'S'
 {"[Cancel]"   ,0x2e63}, // 'c'
 {""           ,0x2e43}, // 'C'
 {""           ,KEY_ESC},// ESC
 {NULL,0}
};

static void diskfile_filecopymove_existfile_keyhand(struct filecopy_t *fc,unsigned int extkey)
{
 switch(extkey){
  case 0x1372: // rename
  case 0x1352:
  case 0x186f: // overwrite
  case 0x184f:if(pds_utf8_stricmp(fc->outfilename,fc->lastfilename)==0){ // if the filename is not modified manually
               funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_OVERWRITE); // overwrite it
               funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_CREATENOEXT);
               mpxplay_timer_addfunc(diskfile_filecopymove_checkfilenames,fc,MPXPLAY_TIMERFLAG_INDOS,0); // ???
              }else
               mpxplay_timer_addfunc(diskfile_filecopymove_makefilenames,fc,MPXPLAY_TIMERFLAG_INDOS,0);
              return;
  case 0x326d: // make dir
  case 0x324d:if(diskfile_filecopymove_make_subdir(fc,fc->outfilename)==MPXPLAY_ERROR_FILEHAND_OK)
               mpxplay_timer_addfunc(diskfile_filecopymove_makefilenames,fc,MPXPLAY_TIMERFLAG_INDOS,0);
              else
               diskfile_filecopy_dealloc(fc);
              return;
  case 0x1e61: // overwrite all
  case 0x1e41:if(pds_utf8_stricmp(fc->outfilename,fc->lastfilename)==0){
               funcbit_enable(fc->cpy_ctrl,(DISKFILE_CPYCTRL_OVERWRITE|DISKFILE_CPYCTRL_ALLFILE));
               mpxplay_timer_addfunc(diskfile_filecopymove_checkfilenames,fc,MPXPLAY_TIMERFLAG_INDOS,0); // ???
              }else
               mpxplay_timer_addfunc(diskfile_filecopymove_makefilenames,fc,MPXPLAY_TIMERFLAG_INDOS,0);
              return;
  case 0x1f73: // skip
  case 0x1f53:funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SKIPFILE);
              fc->retcode=MPXPLAY_ERROR_FILEHAND_SKIPFILE;
              break;
  case 0x256b: // skip all
  case 0x254b:funcbit_enable(fc->cpy_ctrl,(DISKFILE_CPYCTRL_SKIPFILE|DISKFILE_CPYCTRL_ALLFILE));
              fc->retcode=MPXPLAY_ERROR_FILEHAND_SKIPFILE;
              break;
  case 0x2e63: // cancel
  case 0x2e43:
  case KEY_ESC:fc->retcode=MPXPLAY_ERROR_FILEHAND_USERABORT;
               diskfile_filecopy_dealloc(fc);
               return;
 }
 diskfile_filecopymove_select(fc);
}

static display_textwin_button_t buttons_error_delsubdir[]={
 {" Delete "   ,0x2064}, // 'd'
 {""           ,0x2044}, // 'D'
 {" All "      ,0x1e61}, // 'a'
 {""           ,0x1e41}, // 'A'
 {" Skip "     ,0x1f73}, // 's'
 {""           ,0x1f53}, // 'S'
 //{" Skipall "  ,0x256b}, // 'k'
 //{""           ,0x254b}, // 'K'
 {" Cancel "   ,0x2e63}, // 'c'
 {""           ,0x2e43}, // 'C'
 {""           ,KEY_ESC},// ESC
 {NULL,0}
};

static void diskfile_filemovedel_delsubdir_keyhand(struct filecopy_t *fc,unsigned int extkey)
{
 switch(extkey){
  case 0x2064:
  case 0x2044:funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_DELSUBDIR);
              break;
  case 0x1e61:
  case 0x1e41:funcbit_enable(fc->cpy_ctrl,(DISKFILE_CPYCTRL_DELSUBDIR|DISKFILE_CPYCTRL_ALLSUBDIR));
              break;
  case 0x1f73:
  case 0x1f53:fc->pei_selected++;
              break;
  //case 0x256b:
  //case 0x254b:fc->pei_selected++;
  //
  //            break;
  case 0x2e63:
  case 0x2e43:
  case KEY_ESC:fc->retcode=MPXPLAY_ERROR_FILEHAND_USERABORT;
               diskfile_filecopy_dealloc(fc);
               return;
 }
 mpxplay_timer_addfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
}

//-----------------------------------------------------------------------
// rename/move/del with different user/credentials

#ifdef MPXPLAY_LINK_CREDENTIALS

#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>

static display_textwin_button_t diskfile_buttons_error_credentials[]={
 {"[ Retry ]"   ,0x1372}, // 'r'
 {""            ,0x1352}, // 'R'
 {"[ RetryAll ]",0x1e61}, // 'a'
 {""            ,0x1e41}, // 'A'
 {"[ Ignore ]"  ,0x1769}, // 'i'
 {""            ,0x1749}, // 'I'
 {"[ IgnoreAll ]",0x2267}, // 'g'
 {""            ,0x2247}, // 'G'
 {"[ Cancel ]"  ,0x2e63}, // 'c'
 {""            ,0x2e43}, // 'C'
 {""            ,KEY_ESC},// ESC
 {NULL,0}
};

static unsigned int diskfile_credentials_logon(struct mpxp_credentials_s *cri)
{
 if(!cri->credentials_hToken || pds_strcmp(cri->credentials_username,diskfile_alternative_username)!=0
  || pds_strcmp(cri->credentials_password,diskfile_alternative_password)!=0
  //|| pds_strcmp(cri->credentials_domain,diskfile_alternative_domain)!=0
  ){
  if(!playlist_diskfile_credentials_logoff(cri))
   goto err_out_dcl;
  //if(!LogonUser(diskfile_alternative_username, ((diskfile_alternative_domain[0])? diskfile_alternative_domain:"."),
  //          diskfile_alternative_password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &fc->credentials_hToken))
  if(!LogonUser(diskfile_alternative_username,".",diskfile_alternative_password, LOGON32_LOGON_INTERACTIVE,LOGON32_PROVIDER_DEFAULT,(HANDLE *)&cri->credentials_hToken))
   goto err_out_dcl;
 }
 if(cri->credentials_control&DISKFILE_CRDCTRL_IMPERSED)
  return 1;

 if(!ImpersonateLoggedOnUser(cri->credentials_hToken)){
  playlist_diskfile_credentials_logoff(cri);
  goto err_out_dcl;
 }
 funcbit_enable(cri->credentials_control,DISKFILE_CRDCTRL_IMPERSED);

 pds_strcpy(cri->credentials_username,diskfile_alternative_username);
 pds_strcpy(cri->credentials_password,diskfile_alternative_password);
 //pds_strcpy(cri->credentials_domain,diskfile_alternative_domain);

 return 1;

err_out_dcl:
 funcbit_disable(cri->credentials_control,(DISKFILE_CRDCTRL_IMPERSED|DISKFILE_CRDCTRL_ALLALTERUSER));
 return 0;
}

static unsigned int diskfile_credentials_revert(struct mpxp_credentials_s *cri)
{
 if(cri->credentials_hToken && (cri->credentials_control&DISKFILE_CRDCTRL_IMPERSED))
  if(!RevertToSelf())
   return 0;
 funcbit_disable(cri->credentials_control,DISKFILE_CRDCTRL_IMPERSED);
 return 1;
}

unsigned int playlist_diskfile_credentials_logoff(struct mpxp_credentials_s *cri)
{
 if(cri->credentials_hToken){
  if(!diskfile_credentials_revert(cri))
   return 0;
  CloseHandle(cri->credentials_hToken);
  cri->credentials_hToken=NULL;
 }
 return 1;
}

static void diskfile_credentials_passcall_retry(struct mpxp_credentials_s *cri)
{
 if(cri->passfunc_retry)
  cri->passfunc_retry(cri->passdata);
}

static void diskfile_credentials_passcall_skip(struct mpxp_credentials_s *cri)
{
 if(cri->passfunc_skip)
  cri->passfunc_skip(cri->passdata);
}

static void diskfile_credentials_keyhand(struct mpxp_credentials_s *cri,unsigned int extkey)
{
 switch(extkey){
  case 0x1e61:
  case 0x1e41:funcbit_enable(cri->credentials_control,DISKFILE_CRDCTRL_ALLALTERUSER);
  case 0x1372:
  case 0x1352:funcbit_enable(cri->credentials_control,DISKFILE_CRDCTRL_ALTERUSER);
              funcbit_disable(cri->credentials_control,DISKFILE_CRDCTRL_IGNORED);
              diskfile_credentials_logon(cri);
              *(cri->retcodep)=MPXPLAY_ERROR_FILEHAND_OK; // !!!
              mpxplay_timer_addfunc(diskfile_credentials_passcall_retry,cri,MPXPLAY_TIMERFLAG_INDOS,0);
              break;
  case 0x2267:
  case 0x2247:funcbit_enable(cri->credentials_control,DISKFILE_CRDCTRL_IGNALLERR);
  case 0x1769:
  case 0x1749:funcbit_disable(cri->credentials_control,(DISKFILE_CRDCTRL_ALTERUSER|DISKFILE_CRDCTRL_ALLALTERUSER));
              funcbit_enable(cri->credentials_control,DISKFILE_CRDCTRL_IGNORED);
              mpxplay_timer_addfunc(diskfile_credentials_passcall_skip,cri,MPXPLAY_TIMERFLAG_INDOS,0);
              break;
  case 0x2e63:
  case 0x2e43:
  case KEY_ESC:*(cri->retcodep)=MPXPLAY_ERROR_FILEHAND_USERABORT;
               if(cri->passfunc_dealloc)
                cri->passfunc_dealloc(cri->passdata);
               return;
 }
}

unsigned int playlist_diskfile_credentials_errorhand(struct mpxp_credentials_s *cri,unsigned int do_cr)
{
 if(*(cri->retcodep)==MPXPLAY_ERROR_FILEHAND_USERABORT) // aborted by user
  return 0;
 if(*(cri->retcodep)==MPXPLAY_ERROR_FILEHAND_OK){
  if(cri->credentials_control&DISKFILE_CRDCTRL_ALLALTERUSER)
   funcbit_enable(cri->credentials_control,DISKFILE_CRDCTRL_ALTERUSER);
  else
   funcbit_disable(cri->credentials_control,DISKFILE_CRDCTRL_ALTERUSER);
  diskfile_credentials_revert(cri);
  return 0;
 }
 if(!do_cr)
  return 0;
 if(cri->credentials_control&DISKFILE_CRDCTRL_IGNALLERR){
  if(!cri->passfunc_skip)
   return 0;
  mpxplay_timer_addfunc(diskfile_credentials_passcall_skip,cri,MPXPLAY_TIMERFLAG_INDOS,0);
  return 1;
 }
 if((cri->credentials_control&DISKFILE_CRDCTRL_ALLALTERUSER) && (cri->credentials_control&DISKFILE_CRDCTRL_ALTERUSER) && cri->passfunc_retry){
  funcbit_disable(cri->credentials_control,DISKFILE_CRDCTRL_ALTERUSER);
  if(!diskfile_credentials_logon(cri))
   return 0;
  *(cri->retcodep)=MPXPLAY_ERROR_FILEHAND_OK; // !!!
  mpxplay_timer_addfunc(diskfile_credentials_passcall_retry,cri,MPXPLAY_TIMERFLAG_INDOS,0);
 }else{
  const unsigned int flags=(TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN);
  display_textwin_button_t *selbt=NULL;
  void *tw;

  funcbit_disable(cri->credentials_control,(DISKFILE_CRDCTRL_ALTERUSER|DISKFILE_CRDCTRL_ALLALTERUSER));
  if(cri->passfunc_statwinclose)
   cri->passfunc_statwinclose(cri->passdata);

  tw=display_textwin_allocwindow_items(NULL,flags," Error ",diskfile_credentials_keyhand,cri);
  display_textwin_additem_msg_static(tw,flags,0,0,"Access denied on");
  display_textwin_additem_msg_alloc(tw,flags,0,1,cri->filename);
  display_textwin_additem_msg_static(tw,flags,0,2,"Try with different credentials/user");
  display_textwin_additem_msg_static(tw,flags,0,4,"Username:");
  display_textwin_additem_editline(tw,flags,0,10,4,30,&diskfile_alternative_username[0],sizeof(diskfile_alternative_username)-1);
  display_textwin_additem_msg_static(tw,flags,0,6,"Password:");
  display_textwin_additem_editline(tw,flags,TEXTWIN_EDITFLAG_HIDDENTYPE,10,6,30,&diskfile_alternative_password[0],sizeof(diskfile_alternative_password)-1);
  //display_textwin_additem_msg_static(tw,flags,0,8,"Domain:");
  //display_textwin_additem_editline(tw,flags,0,10,8,30,&diskfile_alternative_domain[0],sizeof(diskfile_alternative_domain)-1);
  display_textwin_additem_separatorline(tw,7);
  if(cri->credentials_control&DISKFILE_CRDCTRL_IGNORED)
   selbt=&diskfile_buttons_error_credentials[4];
  else if(diskfile_alternative_username[0])
   selbt=&diskfile_buttons_error_credentials[0];

  display_textwin_additem_buttons(tw,flags,0,8,diskfile_buttons_error_credentials,selbt);
  display_textwin_openwindow_items(tw,0,0,0);
 }
 return 1;
}

//-------------------------------------------------------------------------
static void diskfile_filecopymove_credentials_skipfile(struct filecopy_t *fc)
{
 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_ALLFILE))
  funcbit_disable(fc->cpy_ctrl,(DISKFILE_CPYCTRL_CREATENOEXT|DISKFILE_CPYCTRL_OVERWRITE|DISKFILE_CPYCTRL_SKIPFILE));
 if(fc->retcode!=MPXPLAY_ERROR_FILEHAND_OK)
  pds_strcpy(fc->lastfilename,fc->outfilename);
 fc->outfilename[0]=0;
 diskfile_filecopyrename_postprocess(fc);
 if(fc->retcode==MPXPLAY_ERROR_FILEHAND_SKIPFILE)
  fc->retcode=MPXPLAY_ERROR_FILEHAND_OK;
 fc->last_error=fc->retcode;
 mpxplay_timer_addfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
}

static unsigned int diskfile_filecopymove_credentials_init(struct filecopy_t *fc,void *retry_func)
{
 struct mpxp_credentials_s *cri=&fc->cri;
 unsigned int do_cr;
 cri->retcodep=&fc->retcode;
 cri->filename=(fc->lastfilename[0])? fc->lastfilename:fc->infilename;
 cri->passdata=fc;
 cri->passfunc_retry=retry_func;
 cri->passfunc_skip=(void *)diskfile_filecopymove_credentials_skipfile;
 cri->passfunc_statwinclose=(void *)diskfile_statwin_close;
 do_cr=((fc->retcode==MPXPLAY_ERROR_FILEHAND_DELETE) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_RENAME) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_CANTCREATE) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_REMOVEDIR) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_CHANGEATTR))? 1:0;
 return playlist_diskfile_credentials_errorhand(cri,do_cr);
}

#endif // MPXPLAY_LINK_CREDENTIALS

//-------------------------------------------------------------------------
static int diskfile_filecopymove_make_dirtree(struct filecopy_t *fc,unsigned int tree)
{
 if(tree){ // create all subdirectories step by step
  struct pds_subdirscan_t *dsi=&fc->dsi;
  unsigned int i,len;
  char *path=&fc->outpath_curr[0];
  len=diskfile_filecopymove_build_subdirpath(fc,path,fc->outpath_newsubdir);
  if(!mpxplay_diskdrive_checkdir(fc->mdds_dest,path))
   if(mpxplay_diskdrive_mkdir(fc->mdds_dest,path)<0)
    goto err_out_co;
  for(i=0;i<dsi->subdir_level;i++){
   len+=pds_strcpy(&path[len],PDS_DIRECTORY_SEPARATOR_STR);
   len+=pds_strcpy(&path[len],dsi->subdir_names[i]);
   if(!mpxplay_diskdrive_checkdir(fc->mdds_dest,path))
    if(mpxplay_diskdrive_mkdir(fc->mdds_dest,path)<0)
     goto err_out_co;
  }
  return 0;
 }

 diskfile_filecopymove_build_outpath(fc,fc->outpath_newsubdir);

 if(mpxplay_diskdrive_checkdir(fc->mdds_dest,fc->outpath_curr))
  return 0;
 if(mpxplay_diskdrive_mkdir(fc->mdds_dest,fc->outpath_curr)==MPXPLAY_ERROR_FILEHAND_OK)
  return 0;

err_out_co:
 fc->retcode=MPXPLAY_ERROR_FILEHAND_CANTCREATE;
 pds_strcpy(fc->lastfilename,fc->outpath_curr);
 return -1;
}

static int diskfile_filecopymove_make_subdir(struct filecopy_t *fc,char *outdirname)
{
 struct playlist_entry_info pei_dir;
 if(!mpxplay_diskdrive_checkdir(fc->mdds_dest,outdirname)){  //
  if(mpxplay_diskdrive_mkdir(fc->mdds_dest,outdirname)<0){ // create the dir if it doesn't exist
   fc->retcode=MPXPLAY_ERROR_FILEHAND_CANTCREATE;
   pds_strcpy(fc->lastfilename,outdirname);
   return -1;
  }
  pds_memset(&pei_dir,0,sizeof(pei_dir));
  pei_dir.entrytype=DFT_SUBDIR;
  pei_dir.infobits=PEIF_ID3EXIST;
  pei_dir.id3info[I3I_DFT_STORE]=DFTSTR_SUBDIR;
  diskfile_filecopymove_update_sides(fc,NULL,outdirname,&pei_dir);
 }
 return 0;
}

static unsigned int diskfile_filecopymove_check_samerootdir(char *rootdir,char *subdir)
{
 unsigned int rootlen=pds_strlen(rootdir),sublen=pds_strlen(subdir);
 if((sublen==rootlen) && pds_utf8_stricmp(rootdir,subdir)==0)
  return 1;
 if((sublen>rootlen) && (subdir[rootlen]==PDS_DIRECTORY_SEPARATOR_CHAR) && pds_strlicmp(subdir,rootdir)==0)
  return 1;
 return 0;
}

#ifdef MPXPLAY_LINK_CREDENTIALS
static void diskfile_filecopymove_removeindir_retry(struct filecopy_t *fc)
{
 if(mpxplay_diskdrive_rmdir(fc->mdds_src,fc->lastfilename)==MPXPLAY_ERROR_FILEHAND_OK)
  diskfile_filecopymove_update_sides(fc,fc->lastfilename,NULL,NULL);
 else
  fc->retcode=MPXPLAY_ERROR_FILEHAND_REMOVEDIR;

 if(diskfile_filecopymove_credentials_init(fc,diskfile_filecopymove_removeindir_retry))
  return;

 mpxplay_timer_addfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
}
#endif

static int diskfile_filecopymove_remove_indir(struct filecopy_t *fc,char *path)
{
 int retcode;

 retcode=mpxplay_diskdrive_rmdir(fc->mdds_src,path);
 if(retcode==MPXPLAY_ERROR_FILEHAND_OK)
  return 0;

 fc->retcode=MPXPLAY_ERROR_FILEHAND_REMOVEDIR;
 pds_strcpy(fc->lastfilename,path);
 return -1;
}

static int diskfile_filecopymove_getnextfile(struct filecopy_t *fc)
{
 struct playlist_entry_info *pei;
 struct pds_subdirscan_t *dsi;
 int fferror;
 char srcname[MAX_PATHNAMELEN],path[MAX_PATHNAMELEN],newname[MAX_PATHNAMELEN];

 if(fc->pei_curr)
  pei=fc->pei_selected=fc->pei_curr;
 else
  pei=fc->pei_selected;

 fc->pei_curr=NULL;

 if((pei>fc->psi_src->lastentry) || (pei>fc->pei_last))
  goto err_out_finish;
 if(fc->filenum_selcount>=fc->entries_selected)
  goto err_out_finish;
 if((fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) && !(pei->infobits&PEIF_SELECTED))
  goto err_out_skip;
 if((fc->cpy_type&DISKFILE_CPYTYPE_RENBYID3) && (GET_HFT(pei->entrytype)==HFT_DFT))
  goto err_out_skip;
 if((pei->entrytype==DFT_DRIVE) || ((pei->entrytype==DFT_UPDIR) && !(fc->cpy_type&DISKFILE_CPYTYPE_COPY)) || (pei->entrytype==DFT_UPLIST)){
  fc->retcode=MPXPLAY_ERROR_FILEHAND_COPYDIR;
  goto err_out_skip;
 }

 switch(pei->entrytype){
  case DFT_UPDIR:pds_strcpy(srcname,fc->psi_src->currdir);break;
  default:pds_strcpy(srcname,pei->filename);break;
 }

 if(((pei->entrytype==DFT_UPDIR) || (pei->entrytype==DFT_SUBDIR)) && !(fc->cpy_type&DISKFILE_CPYTYPE_RENAME)){
  dsi=&fc->dsi;
  if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_SUBFINDNEXT)){
   unsigned int len;

   if((fc->cpy_type&DISKFILE_CPYTYPE_DEL) && !(fc->cpy_ctrl&DISKFILE_CPYCTRL_DELSUBDIR)){
    void *tw;

    diskfile_statwin_close(fc);

    tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN,"Delete directory",diskfile_filemovedel_delsubdir_keyhand,fc);
    display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,"The following directory will be DELETED");
    display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,srcname);
    display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,buttons_error_delsubdir,NULL);
    display_textwin_openwindow_items(tw,0,0,0);
    return 2;
   }
   if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_ALLSUBDIR))
    funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_DELSUBDIR);

   fc->mdds_src=pei->mdds;
   pds_strcpy(fc->outpath_curr,fc->outpath_root);
   // create subdirectory in output dir
   if((fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_MOVE)) && !(fc->cpy_ctrl&DISKFILE_CPYCTRL_SUBONECOPY)){

    pds_utf8_filename_wildcard_rename(newname,srcname,pds_getfilename_from_fullname(fc->outpath_argument));
    pds_filename_assemble_fullname(fc->outpath_newsubdir,fc->outpath_root,newname);

    if(diskfile_filecopymove_check_samerootdir(srcname,fc->outpath_newsubdir)){ // copy/move a directory to itself?
     fc->retcode=MPXPLAY_ERROR_FILEHAND_SAMEDIR;
     pds_strcpy(fc->lastfilename,fc->outpath_newsubdir);
     goto err_out_skip;
    }

    if(fc->cpy_ctrl&DISKFILE_CPYCTRL_EMPTYDIRCOPY){
     if(diskfile_filecopymove_make_subdir(fc,fc->outpath_newsubdir)<0)
      goto err_out_skip;
     pei=fc->pei_selected;
    }
   }else
    pds_strcpy(fc->outpath_newsubdir,srcname);

   // start search in subdirs
   len=pds_strcpy(path,srcname);
   len+=pds_strcpy(&path[len],PDS_DIRECTORY_SEPARATOR_STR);
   len+=pds_strcpy(&path[len],PDS_DIRECTORY_ALLDIR_STR);
   len+=pds_strcpy(&path[len],PDS_DIRECTORY_SEPARATOR_STR);
   pds_strcpy(&path[len],fc->source_filtermask);
   if(mpxplay_diskdrive_subdirscan_open(fc->mdds_src,path,_A_NORMAL,dsi)==0){
    funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SUBFINDNEXT);
    diskfile_filecopymove_build_outpath(fc,fc->outpath_newsubdir);
   }else{
    fc->filenum_selcount++;
    goto err_out_skip;
   }
  }

  fferror=mpxplay_diskdrive_subdirscan_findnextfile(fc->mdds_src,dsi);

  if(fferror<0){ // no more files in subdir
   mpxplay_diskdrive_subdirscan_close(fc->mdds_src,dsi);
   funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SUBFINDNEXT);
   fc->filenum_selcount++;
   if((fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_DEL)) && (fc->retcode==MPXPLAY_ERROR_FILEHAND_OK)){
    if(diskfile_filecopymove_remove_indir(fc,dsi->startdir)==0) // delete subdir at move/del
     diskfile_filecopymove_update_sides(fc,dsi->startdir,NULL,pei);
    else
     fc->pei_curr=pei;
   }
   if(((fc->retcode!=MPXPLAY_ERROR_FILEHAND_OK) && (fc->cpy_ctrl&DISKFILE_CPYCTRL_IGNALLERR)) || !(fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_DEL))){
    if((pei->infobits&PEIF_SELECTED) && ((fc->retcode==MPXPLAY_ERROR_FILEHAND_OK) || (fc->cpy_type&DISKFILE_CPYTYPE_MOVE))){ // !!! failed copy at move?
     funcbit_disable(pei->infobits,PEIF_SELECTED);
     if(fc->psi_src->selected_files)
      fc->psi_src->selected_files--;
     refdisp|=RDT_EDITOR;
    }
    fc->pei_selected++;
    fc->pei_curr=NULL;
   }
   goto err_out_getnext;
  }

  if(dsi->flags&SUBDIRSCAN_FLAG_UPDIR){
   if((fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_DEL)) && (fc->retcode==MPXPLAY_ERROR_FILEHAND_OK))
    diskfile_filecopymove_remove_indir(fc,dsi->prevdir); // delete subdir at move/del
   diskfile_filecopymove_build_outpath(fc,fc->outpath_newsubdir);
  }
  if((dsi->flags&SUBDIRSCAN_FLAG_SUBDIR) && (fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_MOVE))){
   if(fc->cpy_ctrl&DISKFILE_CPYCTRL_EMPTYDIRCOPY){
    if(diskfile_filecopymove_make_dirtree(fc,0)<0)
     return 1;
   }else
    funcbit_enable(fc->count_ctrl,DISKFILE_COUNTCTRL_NEWSUBDIR);
  }

  if((fferror==0) && !(dsi->ff->attrib&(_A_SUBDIR|_A_VOLID))){
   if((fc->cpy_ctrl&DISKFILE_CPYCTRL_NOEXTCHK) || mpxplay_infile_check_extension(dsi->ff->name,fc->mdds_src) || playlist_loadlist_check_extension(dsi->ff->name)){
    if(fc->count_ctrl&DISKFILE_COUNTCTRL_NEWSUBDIR){
     funcbit_disable(fc->count_ctrl,DISKFILE_COUNTCTRL_NEWSUBDIR);
     if(diskfile_filecopymove_make_dirtree(fc,1)<0)
      return 1;
    }
    pds_strcpy(fc->infilename,dsi->fullname);
    pds_filename_assemble_fullname(fc->outfilename,fc->outpath_curr,fc->infilename);
    return 0;
   }
  }
  return 1;
 }

 // normal file (audio, playlist)
 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) || (fc->cpy_type&DISKFILE_CPYTYPE_RENAME) || pds_utf8_filename_wildcard_cmp(pei->filename,fc->source_filtermask)){
  pds_strcpy(fc->infilename,pei->filename);
  if((pei->infobits & PEIF_INDEXED) && !(fc->cpy_type & DISKFILE_CPYTYPE_IGNOREINDEX)){ // indexed copy (split file)
   if(!(fc->cpy_type&DISKFILE_CPYTYPE_COPY))
    goto err_out_getnext;
   if(fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE){
    if(!diskfile_renamebyid3_createoutfilename(fc,fc->psi_src,pei,newname,sizeof(newname),fc->cpy_type))
     goto err_out_getnext;
   }else
    pds_utf8_filename_wildcard_rename(newname,fc->infilename,pds_getfilename_from_fullname(fc->outpath_argument));
   pds_strcpy(fc->outpath_curr,fc->outpath_root);
  }else if(fc->cpy_type&DISKFILE_CPYTYPE_RENAME){ // rename or rename by ID3
   if(fc->cpy_type&DISKFILE_CPYTYPE_RENBYID3){
    if(!diskfile_renamebyid3_createoutfilename(fc,fc->psi_src,pei,newname,sizeof(newname),fc->cpy_type))
     goto err_out_getnext;
   }else
    pds_utf8_filename_wildcard_rename(newname,fc->infilename,pds_getfilename_from_fullname(fc->outpath_argument));
   pds_getpath_from_fullname(fc->outpath_curr,fc->infilename);
  }else{ // single move or copy
   pds_strcpy(fc->outpath_curr,fc->outpath_root);
   if(fc->cpy_ctrl&DISKFILE_CPYCTRL_WITHSUBDIR){
    pds_getpath_from_fullname(path,fc->infilename); // get the deepest subdir from infile (cut filename)
    pds_filename_assemble_fullname(fc->outpath_newsubdir,fc->outpath_root,pds_getfilename_from_fullname(path)); // and add to the path of outfile (cut last dir)
    if(diskfile_filecopymove_make_subdir(fc,fc->outpath_newsubdir)<0)
     goto err_out_skip;
    pds_strcpy(fc->outpath_curr,fc->outpath_newsubdir);
   }
   pds_utf8_filename_wildcard_rename(newname,fc->infilename,pds_getfilename_from_fullname(fc->outpath_argument));
  }
  pds_filename_assemble_fullname(fc->outfilename,fc->outpath_curr,newname);
  fc->pei_curr=pei;
  if(!pei->mdds)
   pei->mdds=playlist_loaddir_filename_to_drivehandler(pei->filename);
  fc->mdds_src=pei->mdds;
  fc->filenum_selcount++;
  return 0;
 }

err_out_skip:
 fc->pei_selected++;
err_out_getnext:
 if(fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) // there are more files/dirs
  return 1;
err_out_finish:
 return -1; // no next, finish
}

// possible dirname in outfilename (rename at overwrite)
static void diskfile_filecopymove_makefilename_output(struct filecopy_t *fc)
{
 char newname[MAX_PATHNAMELEN];

 if(pds_strcutspc(fc->outfilename)){

  pds_sfn_limit(fc->outfilename);

  pds_filename_build_fullpath(newname,fc->outpath_curr,fc->outfilename);
  fc->mdds_dest=playlist_loaddir_path_to_drivehandler(fc->psi_dest,newname);

  if(pds_path_is_dir(newname) && mpxplay_diskdrive_checkdir(fc->mdds_dest,newname)){
   pds_strcpy(fc->outpath_curr,newname);
   pds_filename_assemble_fullname(fc->outfilename,fc->outpath_curr,pds_getfilename_any_from_fullname(fc->infilename));
  }else{
   pds_strcpy(fc->outfilename,newname);
   pds_getpath_from_fullname(fc->outpath_curr,fc->outfilename);
  }
 }
}

static void diskfile_filecopymove_makefilenames(struct filecopy_t *fc)
{
 diskfile_filecopymove_makefilename_output(fc);
 diskfile_filecopymove_checkfilenames(fc);
}

// output filename has made (in getnextfile), check it only
static void diskfile_filecopymove_checkfilenames(struct filecopy_t *fc)
{
 unsigned int el_len;
 void *tw;

 if(!fc->outfilename[0] || !fc->mdds_dest){ // ??? skips without notification
  fc->retcode=MPXPLAY_ERROR_FILEHAND_SKIPFILE;
  goto err_out_continue;
 }

 if(mpxplay_diskdrive_get_mdfs_infobits(fc->mdds_dest) & MPXPLAY_DRIVEHANDFUNC_INFOBIT_READONLY)
  goto err_out_dest;

 fc->retcode=MPXPLAY_ERROR_FILEHAND_OK;

 if(fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME)){ // move or rename
  if(pds_strcmp(fc->infilename,fc->outfilename)==0){ // input and output filenames are the same
   fc->retcode=MPXPLAY_ERROR_FILEHAND_SKIPFILE; // !!! automatic skip at move/rename
   goto err_out_continue;
  }
  if(pds_utf8_stricmp(fc->infilename,fc->outfilename)==0) // rename
   goto err_out_continue;
 }else{   // copy
  if(pds_utf8_stricmp(fc->infilename,fc->outfilename)==0) // input and output filenames are the same
   goto err_out_samefile;
 }

 if(fc->cpy_ctrl&DISKFILE_CPYCTRL_CREATENOEXT)
  goto err_out_continue;
 if(fc->cpy_ctrl&DISKFILE_CPYCTRL_OVERWRITE)
  goto err_out_continue;

 fc->filehand_data_dest=mpxplay_diskdrive_file_open(fc->mdds_dest,fc->outfilename,(O_RDONLY|O_BINARY),NULL);
 if(fc->filehand_data_dest){
  mpxplay_diskdrive_file_close(fc->filehand_data_dest);
  fc->filehand_data_dest=NULL;
  if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_SKIPFILE))
   goto err_out_exists;
  fc->retcode=MPXPLAY_ERROR_FILEHAND_SKIPFILE;
  goto err_out_continue;
 }

 if(!fc->pei_curr || (fc->pei_curr->entrytype!=DFT_SUBDIR))
  if(pds_filename_get_extension(fc->infilename) && !pds_filename_get_extension(fc->outfilename))
   goto err_out_noext;

err_out_continue:
 if(pds_utf8_stricmp(fc->outfilename,fc->mvp->pei0->filename)==0){
  struct mpxpframe_s *frp0 = fc->mvp->fr_primary;
  if((frp0->filetype&HFT_FILE_EXT) || (frp0->filehand_datas && frp0->infile_funcs && frp0->filebuf_funcs && frp0->infile_infos)){
   if(playcontrol&PLAYC_RUNNING)
    funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_STARTAVFILE);
   AU_stop(fc->mvp->aui);
   mpxplay_infile_suspend_close(frp0);
   funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_REOPENAVFILE);
  }else{
   mpxplay_infile_close(frp0);
  }
 }
 diskfile_filecopymove_select(fc);
 return;
err_out_exists:
 pds_strcpy(fc->lastfilename,fc->outfilename);
 el_len=pds_strlen(fc->outfilename)+1;
 if(el_len>50)
  el_len=50;
 diskfile_statwin_close(fc);
 tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN,fc->headtext,diskfile_filecopymove_existfile_keyhand,fc);
 display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,"The following file exists");
 display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,-1,el_len,fc->outfilename,MAX_PATHNAMELEN-2);
 display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,"Do you wish to overwrite the old file?");
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,buttons_error_existfile,&buttons_error_existfile[0]);
 display_textwin_openwindow_items(tw,0,0,0);
 return;
err_out_samefile:
 diskfile_statwin_close(fc);
 tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN,fc->headtext,diskfile_filecopymove_existfile_keyhand,fc);
 display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,"Cannot copy/move a file to itself!\nRename it or give a new target directory");
 display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,-1,50,fc->outfilename,MAX_PATHNAMELEN-2);
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,buttons_error_samefile,NULL);
 display_textwin_openwindow_items(tw,0,0,0);
 return;
err_out_noext:
 pds_strcpy(fc->lastfilename,fc->outfilename);
 diskfile_statwin_close(fc);
 tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN,fc->headtext,diskfile_filecopymove_existfile_keyhand,fc);
 display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,"You perform a file copy/move without extension\nor the target directory doesn't exists.\nYou can continue or give an other target:");
 display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,-1,50,fc->outfilename,MAX_PATHNAMELEN-2);
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,buttons_error_noext,NULL);
 display_textwin_openwindow_items(tw,0,0,0);
 return;
err_out_dest:
 diskfile_statwin_close(fc);
 tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN,fc->headtext,diskfile_filecopymove_existfile_keyhand,fc);
 display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,"Destination device is read only!\nGive a new target directory");
 display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,-1,50,fc->outfilename,MAX_PATHNAMELEN-2);
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,buttons_error_samefile,NULL);
 display_textwin_openwindow_items(tw,0,0,0);
 return;
}

static mpxp_int32_t diskfile_filecopy_diskdriv_psifn_callback(void *p_fc, unsigned int diskdrv_cb_cmd, void *data1, void *data2)
{
 struct filecopy_t *fc = (struct filecopy_t *)p_fc;
 char *str_data;
 unsigned int i3i_index;

 if(!fc)
  return MPXPLAY_ERROR_OK;

 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"diskfile_filecopy_diskdriv_psifn_callback: %8.8X", diskdrv_cb_cmd);

 if(diskdrv_cb_cmd == MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_CHG_BEG)
  funcbit_enable(fc->cpy_ctrl, DISKFILE_CPYCTRL_OUTPAUSED);
 else if(diskdrv_cb_cmd == MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_CHG_END)
  funcbit_disable(fc->cpy_ctrl, DISKFILE_CPYCTRL_OUTPAUSED);
 if(diskdrv_cb_cmd != MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_TITLE)
  return MPXPLAY_ERROR_OK;
 if(pds_strcmp((char *)data1, fc->pei_curr->filename) != 0) // ???
  return MPXPLAY_ERROR_OK;

 i3i_index = diskdrv_cb_cmd & MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_MASK;
 str_data = (char *)data2;
 if((i3i_index <= I3I_MAX) && fc->pei_curr && (pds_strcmp(fc->pei_curr->id3info[i3i_index], str_data) != 0))
  playlist_editlist_add_id3_one(fc->psi_src, fc->pei_curr, i3i_index, str_data, -1);
 funcbit_disable(fc->cpy_ctrl, DISKFILE_CPYCTRL_OUTPAUSED);
 funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_METADATACHG);

 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"diskfile_filecopy_diskdriv_psifn_callback: %s", str_data);

 return MPXPLAY_ERROR_OK;
}

static int diskfile_filecopy_openfile_output(struct filecopy_t *fc, mpxp_bool_t check_filename)
{
 if(!pds_filename_get_extension(fc->outfilename)){
  char *extension=NULL;
  mpxplay_diskdrive_file_config(fc->filehand_data_src,MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENTTYPEEXT,&extension,NULL);
  if(extension && extension[0]){
   pds_strcat(fc->outfilename,".");
   pds_strcat(fc->outfilename, extension); // !!! adds automatically extension to outfilename (if it has no one)
   if(check_filename){
    mpxplay_diskdrive_file_close(fc->filehand_data_src);
    fc->filehand_data_src = NULL;
    mpxplay_timer_addfunc(diskfile_filecopymove_checkfilenames,fc,MPXPLAY_TIMERFLAG_INDOS,0); // re-check new output filename
    return MPXPLAY_ERROR_FILEHAND_SKIPFILE;
   } // TODO: else there's no overwrite check
  }
 }

 fc->filehand_data_dest=mpxplay_diskdrive_file_open(fc->mdds_dest,fc->outfilename,(O_WRONLY|O_CREAT|O_BINARY),NULL);
 if(!fc->filehand_data_dest){
  fc->retcode=MPXPLAY_ERROR_FILEHAND_CANTCREATE;
  pds_strcpy(fc->lastfilename,fc->outfilename);
  return MPXPLAY_ERROR_FILEHAND_CANTCREATE;
 }
 fc->counted_copyprocess_starttime_ms = pds_gettimem();
 fc->filepos_out = 0;
 funcbit_disable(fc->cpy_ctrl, DISKFILE_CPYCTRL_OUTPAUSED);

 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"diskfile_filecopy_openfile_output fn:%s", fc->outfilename);

 return MPXPLAY_ERROR_FILEHAND_OK;
}

// read bitrate from the file (or from a live-stream by opening the parser with the buffered data)
static void diskfile_filecopy_get_source_bitrate(struct filecopy_t *fc, mpxp_bool_t live_stream)
{
 struct mpxpframe_s *frp = &fc->frp_src;
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"BR START bf:%d", fc->buf_stored_bytes);
 mpxplay_infile_close(frp);
 frp->filehand_datas = fc->filehand_data_src;
 frp->mdds = fc->mdds_src;
 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"BR get_infilefuncs_by_ext bf:%d", fc->buf_stored_bytes);
 mpxplay_infile_get_infilefuncs_by_ext(frp, fc->mdds_src, fc->infilename, (O_RDONLY|MPXPLAY_INFILE_OPENMODE_INFO_HEAD));
 if(frp->infile_funcs){
  mpxp_uint32_t openmode;
  int result;
  if(live_stream){
   //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"FILL START bf:%d b:%8.8X d:%8.8X", fc->buf_stored_bytes, fc->buffer, *((unsigned long *)fc->buffer));
   result = mpxplay_mpxinbuf_buffer_fill(frp, fc->buffer, fc->buf_stored_bytes);
   mpxplay_debugf(MPXPLAY_DEBUGOUT_BITRATE,"FILL %8.8X %d ft:%s res:%d fp:%d pbf:%d pbp:%d fh:%8.8X",
			(unsigned int)fc->buffer, fc->buf_stored_bytes, frp->infile_funcs->file_extensions[0], result,
			(int)frp->filepos, frp->prebufferbytes_forward, frp->prebufferputp, (unsigned int)frp->filehand_datas);
   funcbit_enable(frp->buffertype, PREBUFTYPE_WRITEPROTECT);
   openmode = O_RDONLY | MPXPLAY_INFILE_OPENMODE_INFO_HEAD;
  }else{
   openmode = O_RDONLY | MPXPLAY_INFILE_OPENMODE_INFO_HEAD | MPXPLAY_INFILE_OPENMODE_INFO_LENGTH;
  }
  result = mpxplay_infile_open(frp,fc->infilename, openmode);
  if((result == MPXPLAY_ERROR_INFILE_OK) && (live_stream || !funcbit_test(frp->flags, MPXPLAY_MPXPFRAME_FLAG_FILEFORMAT_CONTAINER))){
   if(live_stream || (frp->infile_infos->timemsec < 8)){
    fc->stream_bitrate = 0;
    if(frp->infile_infos->audio_decoder_infos)
     fc->stream_bitrate += frp->infile_infos->audio_decoder_infos->bitrate;
    if(frp->infile_infos->video_decoder_infos) // TODO
     fc->stream_bitrate += frp->infile_infos->video_decoder_infos->video_bitrate;
   }else
    fc->stream_bitrate = frp->infile_infos->filesize / (frp->infile_infos->timemsec / 8);
  }
  mpxplay_debugf(MPXPLAY_DEBUGOUT_BITRATE,"BITRATE:%d res:%d fp:%d pbf:%d pbr:%d pbp:%d pbg:%d", fc->stream_bitrate, result, (int)frp->filepos,
			frp->prebufferbytes_forward, frp->prebufferbytes_rewind, frp->prebufferputp, frp->prebuffergetp);
  mpxplay_infile_close(frp);
 }
 if((fc->stream_bitrate <= 0) && (live_stream || !funcbit_test(frp->flags, MPXPLAY_MPXPFRAME_FLAG_FILEFORMAT_CONTAINER))){
  int diskdrive_bitrate = mpxplay_diskdrive_file_config(fc->filehand_data_src, MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENTBITRATE, NULL, NULL);
  if(diskdrive_bitrate > 0)
   fc->stream_bitrate = diskdrive_bitrate;
  else
   fc->stream_bitrate = 0;
 }
}

static int diskfile_filecopy_openfile_newoutput(struct filecopy_t *fc, mpxp_bool_t check_filename)
{
 unsigned int copy_ctrl = fc->cpy_ctrl, path_len;
 int retval = MPXPLAY_ERROR_FILEHAND_OK;
 char newoutname[MAX_PATHNAMELEN];

 if(!(fc->cpy_type & (DISKFILE_CPYTYPE_NEWOUTBYID3|DISKFILE_CPYTYPE_OUTCUEBYID3)) && !(copy_ctrl & DISKFILE_CPYCTRL_OUTFNRECREAT))
  return MPXPLAY_ERROR_FILEHAND_OK;
 funcbit_disable(fc->cpy_ctrl,(DISKFILE_CPYCTRL_METADATACHG|DISKFILE_CPYCTRL_OUTFNRECREAT));
 path_len = pds_strlen(fc->outpath_curr) + pds_strlen(PDS_DIRECTORY_SEPARATOR_STR);

 // check output filename (metadata) change
 pds_strcpy(newoutname, fc->outfilename);
 if(!diskfile_renamebyid3_createoutfilename(fc, fc->psi_src, fc->pei_curr, &newoutname[path_len], (MAX_PATHNAMELEN / 2), (fc->cpy_type & ~DISKFILE_CPYTYPE_OUTTIMESTAMP))) // create filename without timestamp
  return MPXPLAY_ERROR_FILEHAND_CANTCREATE;
 if( !(copy_ctrl & DISKFILE_CPYCTRL_OUTFNRECREAT) // not first output file creation
  && !((fc->cpy_type & DISKFILE_CPYTYPE_OUTCUEBYID3) && fc->psi_list_dest && (fc->psi_list_dest->lastentry <= fc->psi_list_dest->firstentry)) // not first index creation (or no indexes at all)
  && (pds_stricmp(newoutname, fc->outfilenam_notimestamp) == 0)){ // output filename didn't change (we have to check filename change without timestamp change)
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"diskfile_filecopy_openfile_newoutput SKIP %s", newoutname);
  return MPXPLAY_ERROR_FILEHAND_OK;
 }
 pds_strcpy(fc->outfilenam_notimestamp, newoutname);
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"diskfile_filecopy_openfile_newoutput PROCESS %s", newoutname);

 // close previous output file
 if((copy_ctrl & DISKFILE_CPYCTRL_OUTFNRECREAT) || (fc->cpy_type & DISKFILE_CPYTYPE_NEWOUTBYID3)){
  if(fc->filehand_data_dest){
   mpxplay_diskdrive_file_close(fc->filehand_data_dest);
   fc->filehand_data_dest = NULL;
   diskfile_filecopymove_update_sides(fc, NULL, fc->outfilename, NULL);
  }
 }

 if((copy_ctrl & DISKFILE_CPYCTRL_OUTFNRECREAT) || (fc->cpy_type & DISKFILE_CPYTYPE_NEWOUTBYID3)){
  // create output filename
  if(!diskfile_renamebyid3_createoutfilename(fc, fc->psi_src, fc->pei_curr, &fc->outfilename[path_len], (MAX_PATHNAMELEN / 2), fc->cpy_type & ~DISKFILE_CPYTYPE_OUTCUEBYID3)) // then create filename with timestamp
   return MPXPLAY_ERROR_FILEHAND_CANTCREATE;
  // open (new) output file
  if(check_filename){
   mpxplay_timer_addfunc(diskfile_filecopymove_checkfilenames,fc,MPXPLAY_TIMERFLAG_INDOS,0); // re-check new output filename
   retval = MPXPLAY_ERROR_FILEHAND_SKIPFILE;
  }else{
   diskfile_filecopymove_makefilename_output(fc);
   retval = diskfile_filecopy_openfile_output(fc, check_filename);
  }
 }

 // create index point for output file
 if((fc->cpy_type & DISKFILE_CPYTYPE_OUTCUEBYID3) && ((retval == MPXPLAY_ERROR_FILEHAND_OK) || (retval == MPXPLAY_ERROR_FILEHAND_SKIPFILE))){
  struct playlist_entry_info *pei, pei_tmp;

  if(copy_ctrl & DISKFILE_CPYCTRL_OUTFNRECREAT)
   diskfile_filecopy_get_source_bitrate(fc, TRUE);

  pei = playlist_editlist_add_entry(fc->psi_list_dest);
  if(!pei)
   return MPXPLAY_ERROR_FILEHAND_MEMORY;
  pds_memcpy(&pei_tmp, fc->pei_curr, sizeof(pei_tmp));
  pei_tmp.filename = &fc->outfilename[0];
  pei_tmp.mdds = fc->psi_dest->mdds;
  if(!playlist_editlist_addfile_one(NULL, fc->psi_list_dest, &pei_tmp, pei, (EDITLIST_MODE_FILENAME | EDITLIST_MODE_ID3)))
  {
   mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"playlist_editlist_addfile_one FAILED fn:%s", fc->outfilename);
   return MPXPLAY_ERROR_FILEHAND_CANTCREATE;
  }
  if(!(fc->cpy_type & DISKFILE_CPYTYPE_NEWOUTBYID3)){ // we write single file list in DISKFILE_CPYTYPE_NEWOUTBYID3 mode
   //  pei = fc->psi_list_dest->lastentry; // FIXME addfile_one without add_entry
   funcbit_enable(pei->infobits,PEIF_INDEXED);
   if(fc->psi_list_dest->lastentry > fc->psi_list_dest->firstentry){
    mpxp_int64_t timepos_calculated_ms = 0;
    //mpxp_int64_t timepos_elapsed_ms = pds_gettimem() - fc->counted_copyprocess_starttime_ms;
    if(fc->stream_bitrate)
     timepos_calculated_ms = (mpxp_int64_t)fc->filepos_out * (mpxp_int64_t)8 / (mpxp_int64_t)fc->stream_bitrate;
    //if(timepos_calculated_ms < timepos_elapsed_ms)
    // timepos_calculated_ms = timepos_elapsed_ms;
    pei->pstime = timepos_calculated_ms;
   }
  }
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"INDEX fn:%s t:%d b:%llu br:%d", fc->outfilename, pei->pstime, fc->filepos_out, fc->stream_bitrate);
 }

 return retval;
}

// this function opens newoutput by delay, waiting for the first metadata
static int diskfile_filecopy_openfile_newout_trigger(struct filecopy_t *fc, mpxp_bool_t check_filename)
{
 if(fc->cpy_ctrl & DISKFILE_CPYCTRL_METADATACHG)
  goto newout_open;
 if(fc->cpy_ctrl & DISKFILE_CPYCTRL_OUTFNRECREAT){
  if(fc->buf_stored_bytes >= fc->buf_blocksize)
   goto newout_open;
  if(!fc->outfnrecreat_timeout)
   fc->outfnrecreat_timeout = pds_gettimem() + DISKFILE_DEFAULT_TIMEOUTMS_READ;
  else if(pds_gettimem() >= fc->outfnrecreat_timeout){
   fc->outfnrecreat_timeout = 0;
   goto newout_open;
  }
 }
 return MPXPLAY_ERROR_FILEHAND_OK;
newout_open:
 fc->outfnrecreat_timeout = 0;
 return diskfile_filecopy_openfile_newoutput(fc, check_filename);
}

static void diskfile_filecopy_openfiles(struct filecopy_t *fc)
{
 int retcode;
 fc->retcode = MPXPLAY_ERROR_FILEHAND_OK;
 fc->filepos_in = 0;
 fc->filepos_out = 0;

 if(!fc->filehand_data_src){ // input is not open yet (possible re-entrancy to this function)
  fc->filehand_data_src=mpxplay_diskdrive_file_open(fc->mdds_src,fc->infilename,(O_RDONLY|O_BINARY|MPXPLAY_INFILE_OPENMODE_READ_CONTENT),NULL);
  if(!fc->filehand_data_src){
   fc->retcode=MPXPLAY_ERROR_FILEHAND_CANTOPEN;
   goto err_out_open;
  }

  // initialize copy buffer by drive datas
  if(!fc->buffer){
   fc->buf_blocksize = mpxplay_diskdrive_file_config(fc->filehand_data_src, MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_CHKBUFBLOCKBYTES, NULL, NULL);
   if(fc->buf_blocksize < DISKFILE_FILECOPY_BLOCKSIZE_MIN)
    fc->buf_blocksize = DISKFILE_FILECOPY_BLOCKSIZE_MIN;
   fc->buf_size = DISKFILE_FILECOPY_BUFFER_BLOCKS * fc->buf_blocksize;
   fc->buffer=pds_malloc(fc->buf_size);
   if(!fc->buffer){
    fc->retcode=MPXPLAY_ERROR_FILEHAND_MEMORY;
    goto err_out_open;
   }
  }

  fc->buf_stored_bytes = 0;

  // source is live stream
  if(mpxplay_diskdrive_file_config(fc->filehand_data_src, MPXPLAY_DISKFILE_CFGFUNCNUM_GET_ISLIVESTREAM, NULL, NULL) == 1){
   if(fc->cpy_type&(DISKFILE_CPYTYPE_RENAME|DISKFILE_CPYTYPE_MOVE)){
    fc->retcode=MPXPLAY_ERROR_FILEHAND_READONLY;
    goto err_out_open;
   }
   funcbit_enable(fc->cpy_ctrl, DISKFILE_CPYCTRL_STREAMSRC);
   if(fc->cpy_type&(DISKFILE_CPYTYPE_NEWOUTBYID3|DISKFILE_CPYTYPE_OUTCUEBYID3)){
    if(fc->cpy_type&DISKFILE_CPYTYPE_OUTCUEBYID3){ // allocate a virtual playlist to store indexes
     fc->psi_list_dest = pds_calloc(1,sizeof(*(fc->psi_list_dest)));
     if(!fc->psi_list_dest){
      fc->retcode=MPXPLAY_ERROR_FILEHAND_MEMORY;
      goto err_out_open;
     }
     fc->psi_list_dest->mvp = fc->mvp;
    }
    mpxplay_diskdrive_file_config(fc->filehand_data_src, MPXPLAY_DISKDRIV_CFGFUNCNUM_SET_CALLBACKPSIFN, diskfile_filecopy_diskdriv_psifn_callback, fc);
   }
  }else
   funcbit_disable(fc->cpy_ctrl, DISKFILE_CPYCTRL_STREAMSRC);
 }

 fc->filelen_in = mpxplay_diskdrive_file_length(fc->filehand_data_src);

 // calculate indexed filepos and filelen
 if(!funcbit_test(fc->cpy_ctrl, DISKFILE_CPYCTRL_STREAMSRC) && fc->pei_curr && (fc->pei_curr->infobits & PEIF_INDEXED) && !(fc->cpy_type & DISKFILE_CPYTYPE_IGNOREINDEX)){
  if((pds_stricmp(fc->infilenam_bitrate, fc->infilename) != 0) || (fc->stream_bitrate <= 0)){ // bitrate is not read for this filename
   fc->stream_bitrate = 0;
   diskfile_filecopy_get_source_bitrate(fc, FALSE);
   if(fc->stream_bitrate <= 0){
    fc->retcode = MPXPLAY_ERROR_FILEHAND_CANTOPEN;
    goto err_out_open;
   }
   pds_strcpy(fc->infilenam_bitrate, fc->infilename);
  }
  // seek to index begin position
  fc->filepos_in = fc->pei_curr->pstime * fc->stream_bitrate / 8;
  if(mpxplay_diskdrive_file_seek(fc->filehand_data_src, fc->filepos_in, SEEK_SET) != fc->filepos_in){
   fc->retcode = MPXPLAY_ERROR_FILEHAND_CANTSEEK;
   goto err_out_open;
  }
  // calculate input file end index position in bytes
  if(fc->pei_curr->petime){
   mpxp_filesize_t file_end = (mpxp_int64_t)fc->pei_curr->petime * (mpxp_int64_t)fc->stream_bitrate / (mpxp_int64_t)8;
   if((file_end > 0) && (file_end < fc->filelen_in))
    fc->filelen_in = file_end;
  }
 }

 // set zero wait for stream copy
 if(funcbit_test(fc->cpy_ctrl, DISKFILE_CPYCTRL_STREAMSRC)){
  int readwait = 0;
  mpxplay_diskdrive_file_config(fc->filehand_data_src, MPXPLAY_DISKFILE_CFGFUNCNUM_SET_READWAIT, &readwait, NULL);
 }

 fc->filelen_out = fc->filelen_in - fc->filepos_in;
 fc->counted_copyprocess_starttime_ms = pds_gettimem();

 if(fc->cpy_ctrl&DISKFILE_CPYCTRL_OUTFNRECREAT){   // assumed stream (without manual filename)
  if(fc->cpy_type&(DISKFILE_CPYTYPE_NEWOUTBYID3|DISKFILE_CPYTYPE_OUTCUEBYID3))  // output split by metadata or indexing
   retcode = MPXPLAY_ERROR_FILEHAND_OK;   // we going to open the output in diskfile_filecopy_do_copy()
  else                                  // continuous record (no split)
   retcode = diskfile_filecopy_openfile_newoutput(fc, TRUE); // we needed only the station information only (from the input stream), got it at src open
 }else
  retcode = diskfile_filecopy_openfile_output(fc, TRUE); // normal file

 if(retcode == MPXPLAY_ERROR_FILEHAND_SKIPFILE) // output filename is re-checked (maybe exists)
  return;
 if(retcode < 0)
  goto err_out_open;

 mpxplay_timer_addfunc(diskfile_filecopy_do_copy,fc,(MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS),((fc->cpy_ctrl&DISKFILE_CPYCTRL_STREAMSRC)? 1:0)); // !!! FIXME: can cause bugs at very high (>50mbit) stream bitrates (we shall change copy block size)

 return;

err_out_open:
 mpxplay_timer_addfunc(diskfile_filecopy_closefiles,fc,MPXPLAY_TIMERFLAG_INDOS,0);
}

static void diskfile_filecopy_closefiles(struct filecopy_t *fc)
{
 struct mpxpframe_s *frp=fc->mvp->fr_primary;
 char cue_filename[MAX_PATHNAMELEN], cue_fullname[MAX_PATHNAMELEN];

 mpxplay_diskdrive_file_close(fc->filehand_data_src);
 mpxplay_diskdrive_file_close(fc->filehand_data_dest);

 if(fc->filehand_data_dest && (fc->retcode!=MPXPLAY_ERROR_FILEHAND_OK))
  mpxplay_diskdrive_unlink(fc->mdds_dest,fc->outfilename);

 fc->filehand_data_src=fc->filehand_data_dest=NULL;
 fc->filebytes_copied+=(fc->filelen_in)? fc->filelen_in : fc->filepos_out; // TODO: we always have to use filepos_out, but counted_filesizes_all is not correct at indexes

 // currently playing file is moved/renamed to another drive
 if((fc->retcode==MPXPLAY_ERROR_FILEHAND_OK) && (fc->cpy_type&DISKFILE_CPYTYPE_MOVE) && ((frp->filetype & HFT_FILE_EXT) || frp->filehand_datas) && (pds_utf8_stricmp(fc->mvp->pei0->filename,fc->infilename)==0) && !diskfile_filemove_check_samedrive(fc)){
  if(frp->filetype & HFT_FILE_EXT){
   if(playcontrol&PLAYC_RUNNING)
    funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_STARTAVFILE);
   AU_stop(fc->mvp->aui);
   mpxplay_infile_suspend_close(frp);
  }else{
   mpxplay_diskdrive_file_close(frp->filehand_datas);
   frp->filehand_datas=NULL;
  }
  fc->retcode=mpxplay_diskdrive_unlink(fc->mdds_src,fc->infilename); // delete file from old location
  frp->mdds=fc->mdds_dest;
  funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_REOPEN);
 }

 // save a CUE with the same name within the output stream (optional)
 if(fc->cpy_type&DISKFILE_CPYTYPE_OUTCUEBYID3){
  cue_filename[0] = 0;
  if((fc->cpy_type & DISKFILE_CPYTYPE_NEWOUTBYID3) && diskfile_renamebyid3_createoutfilename(fc, fc->psi_src, fc->pei_curr, &cue_filename[0], (MAX_PATHNAMELEN / 2), (fc->cpy_type & ~(DISKFILE_CPYTYPE_NEWOUTBYID3|DISKFILE_CPYTYPE_OUTCUEBYID3))))
    pds_filename_build_fullpath(cue_fullname, fc->outpath_curr, cue_filename);
  else
   pds_strcpy(cue_fullname, fc->outfilename);
  mpxplay_playlist_savelist_set_fileextension_by_listtype(PLST_CUE, cue_fullname);
  playlist_savelist_save_playlist(NULL, fc->psi_list_dest, cue_fullname, PLST_CUE);
  playlist_editlist_updatesides_add_dft(fc->mvp, cue_fullname, DFT_PLAYLIST);
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"INDEX SAVE fn:%s", cue_fullname);
 }

 if(fc->psi_list_dest){ // maybe later psi_list_dest will be no save-CUE dependant only
  playlist_editlist_tab_close(fc->psi_list_dest);
  pds_free(fc->psi_list_dest);
  fc->psi_list_dest=NULL;
 }

 mpxplay_timer_addfunc(diskfile_filecopymove_postprocess,fc,MPXPLAY_TIMERFLAG_INDOS,0);
}

static display_textwin_button_t buttons_filecopy_normalfile[]={
 {"[ Pause ]"   ,0x1970}, // 'p'
 {""            ,0x1950}, // 'P'
 {"[ Cancel ]"  ,0x2e63}, // 'c'
 {""            ,0x2e43}, // 'C'
#ifdef MPXPLAY_GUI_CONSOLE
 {""            ,KEY_ESC},// ESC
#endif
 {NULL,0}
};

static display_textwin_button_t buttons_filecopy_stream[]={
 {"[ Finish ]"  ,0x2166}, // 'f'
 {""            ,0x2146}, // 'F'
 {"[ Cancel ]"  ,0x2e63}, // 'c'
 {""            ,0x2e43}, // 'C'
#ifdef MPXPLAY_GUI_CONSOLE
 {""            ,KEY_ESC},// ESC
#endif
 {NULL,0}
};

static void diskfile_filecopy_keycheck(struct filecopy_t *fc,unsigned int extkey)
{
 switch(extkey){
  case 0x1970:  // 'p'
  case 0x1950:  // 'P'
   funcbit_inverse(fc->cpy_ctrl, DISKFILE_CPYCTRL_PAUSED);
   mpxplay_timer_modifyfunc(diskfile_filecopy_do_copy,fc,(MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS),(funcbit_test(fc->cpy_ctrl,DISKFILE_CPYCTRL_PAUSED))? 100:0);
   break;
  case 0x2166:  // 'f'
  case 0x2146:  // 'F'
   mpxplay_timer_deletefunc(diskfile_filecopy_do_copy,fc);
   mpxplay_timer_addfunc(diskfile_filecopy_closefiles,fc,MPXPLAY_TIMERFLAG_INDOS,0);
   break;
  case KEY_ESC:
  case 0x2e63:  // 'c'
  case 0x2e43:  // 'C'
   fc->retcode=MPXPLAY_ERROR_FILEHAND_USERABORT;
   fc->cpy_type=fc->cpy_ctrl=0;
   break;
  case KEY_ENTER1:
  case KEY_ENTER2:
   funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_DONTWAITHPP);
 }
}

static void diskfile_filecopymove_progress_window(struct filecopy_t *fc,unsigned int waitflag)
{
 long percent_file,percent_group=0;
 char *hedtext,barstr_file[DISKFILE_FILECOPY_BARBUFLEN],barstr_group[DISKFILE_FILECOPY_BARBUFLEN];
 char infilename[MAX_PATHNAMELEN],outfilename[MAX_PATHNAMELEN];
 char msg[2*MAX_PATHNAMELEN+48+DISKFILE_FILECOPY_BARBUFLEN*2],tottmp[64+DISKFILE_FILECOPY_BARBUFLEN];

 if(!(fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_DEL|DISKFILE_CPYTYPE_RENAME|DISKFILE_CPYTYPE_MOVE))){
  fc->retcode=MPXPLAY_ERROR_FILEHAND_USERABORT;
  return;
 }
 if(waitflag){
  if(!fc->statwin_begintime_waitdisp)
   fc->statwin_begintime_waitdisp=pds_gettimem()+DISKFILE_DEFAULT_TIMEOUTMS_WAITDISP;
  if(pds_gettimem()<fc->statwin_begintime_waitdisp)
   waitflag=0;
 }else
  fc->statwin_begintime_waitdisp=0;

 if(fc->cpy_type&DISKFILE_CPYTYPE_DEL)
  hedtext=" Delete ";
 else if(fc->cpy_type&DISKFILE_CPYTYPE_RENAME)
  hedtext=" Rename ";
 else if(fc->cpy_type&DISKFILE_CPYTYPE_MOVE)
  hedtext=" Move file ";
 else if(fc->cpy_ctrl&DISKFILE_CPYCTRL_STREAMSRC)
  hedtext=" Record stream ";
 else
  hedtext=" File copy ";

 if(waitflag){
  if(fc->statwin_type != DISKFILE_STATWINTYPE_WAITHIGHPRIO){
   diskfile_statwin_close(fc);
   pds_strcpy(msg,"Waiting for the higher priority processes...\n(press ESC to exit, ENTER to start copy)");
   fc->statwin_tw = display_textwin_openwindow_buttons(NULL,TEXTWIN_FLAG_MSGCENTERALIGN,hedtext,msg,diskfile_filecopy_keycheck,fc,NULL);
   fc->statwin_type = DISKFILE_STATWINTYPE_WAITHIGHPRIO;
  }
 }else{
  pds_strcpy(infilename,fc->infilename);
  pds_utf8_str_centerize(infilename,DISKFILE_FILECOPY_BARLEN,1);
  if(!(fc->cpy_type&DISKFILE_CPYTYPE_DEL)){
   pds_strcpy(outfilename,fc->outfilename);
   pds_utf8_str_centerize(outfilename,DISKFILE_FILECOPY_BARLEN,1);
  }
  if(fc->cpy_type&(DISKFILE_CPYTYPE_DEL|DISKFILE_CPYTYPE_RENAME|DISKFILE_CPYTYPE_MOVE)){
   if(fc->cpy_type&DISKFILE_CPYTYPE_DEL)
    snprintf(msg,sizeof(msg),"Deleting\n%s",infilename);
   else if(fc->cpy_type&DISKFILE_CPYTYPE_RENAME)
    snprintf(msg,sizeof(msg),"Renaming\n%s\nto\n%s",infilename,outfilename);
   else if(fc->cpy_type&DISKFILE_CPYTYPE_MOVE)
    snprintf(msg,sizeof(msg),"Moving\n%s\nto\n%s",infilename,outfilename);
   if(fc->counted_filenum_all>1)
    percent_group=(long)(100.0*((float)fc->filenum_curr)/(float)fc->counted_filenum_all);
  }else{
   if(fc->cpy_ctrl & DISKFILE_CPYCTRL_STREAMSRC){
    mpxp_int64_t elapsed_ms = pds_gettimem() - fc->counted_copyprocess_starttime_ms;
    mpxp_int64_t elapsed_sec = elapsed_ms / 1000;
    int elapsed_mbytes = (fc->filepos_out >= 100000000)? (fc->filepos_out / 1000000) : (fc->filepos_out / 1000); // we write MB abouve 100MB, else kB
    int minutes = elapsed_sec / 60;
    int seconds = elapsed_sec % 60;
    snprintf(msg,sizeof(msg),"Saving the stream\n%s\nto\n%s\nElapsed: %2d:%2.2d / %4d %s", infilename, outfilename, minutes, seconds, elapsed_mbytes, ((fc->filepos_out >= 100000000)? "MB": "kB"));
   }else{
    if(fc->filelen_out<1)
     percent_file=0;
    else if(fc->filelen_out<10000)
     percent_file=100*fc->filepos_out/fc->filelen_out;
    else
     percent_file=fc->filepos_out/(fc->filelen_out/100);
    playlist_diskfile_filecopy_makebar(barstr_file,percent_file);
    snprintf(msg,sizeof(msg),"Copying the file\n%s\nto\n%s\n%s",infilename,outfilename,barstr_file);
   }
   if((fc->counted_filenum_all>1) && (fc->counted_filesizes_all>1))
    percent_group=(long)(100.0*((float)fc->filebytes_copied+(float)fc->filepos_in)/(float)fc->counted_filesizes_all); // TODO: we always have to use filepos_out, but counted_filesizes_all is not correct at indexes
  }
  if(fc->directories_selected || (fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE)){
   if(fc->counted_filenum_all>1){
    playlist_diskfile_filecopy_makebar(barstr_group,percent_group);
    sprintf(tottmp,"\nTotal %d/%d\n%s",fc->filenum_curr,fc->counted_filenum_all,barstr_group);
   }else
    sprintf(tottmp,"\nTotal: %d",fc->filenum_curr);
   pds_strcat(msg,tottmp);
  }
  if(fc->statwin_type != DISKFILE_STATWINTYPE_COPYMOVE)
   diskfile_statwin_close(fc);
  if(fc->statwin_tw)
   display_textwin_update_msg(fc->statwin_tw,fc->count_itemnum,msg);
  else{
   fc->statwin_tw=display_textwin_allocwindow_items(fc->statwin_tw,TEXTWIN_FLAG_MSGCENTERALIGN|TEXTWIN_FLAG_DONTCLOSE,hedtext,diskfile_filecopy_keycheck,fc);
   fc->count_itemnum=display_textwin_additem_msg_table(fc->statwin_tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,msg);
   display_textwin_additem_separatorline(fc->statwin_tw,-1);
   display_textwin_additem_buttons(fc->statwin_tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,(fc->cpy_ctrl&DISKFILE_CPYCTRL_STREAMSRC)? buttons_filecopy_stream:buttons_filecopy_normalfile,NULL);
   display_textwin_openwindow_items(fc->statwin_tw,0,0,0);
   fc->statwin_type = DISKFILE_STATWINTYPE_COPYMOVE;
  }
 }
}

static void diskfile_filecopy_do_copy(struct filecopy_t *fc)
{
 long inbytes, outbytes, retval, lps_wait = 0;

 if(funcbit_test(fc->cpy_ctrl, DISKFILE_CPYCTRL_PAUSED))
  return;

 if(!funcbit_test(fc->cpy_ctrl,DISKFILE_CPYCTRL_DONTWAITHPP))
  lps_wait = mpxplay_timer_lowpriorstart_wait();

 diskfile_filecopymove_progress_window(fc,lps_wait);
 if(fc->retcode==MPXPLAY_ERROR_FILEHAND_USERABORT)
  goto end_copy;
 if(lps_wait)
  goto cont_copy;

 // handle indexed fileblock
 if(fc->pei_curr && (fc->pei_curr->infobits & PEIF_INDEXED) && !(fc->cpy_type & DISKFILE_CPYTYPE_IGNOREINDEX)){
  inbytes = fc->filelen_in - fc->filepos_in;
  if(inbytes <= 0)
   goto end_copy;
  if(inbytes > fc->buf_blocksize)
   inbytes = fc->buf_blocksize;
 }else
  inbytes = fc->buf_blocksize;

 // handle buffer space
 inbytes = min(inbytes, (fc->buf_size - fc->buf_stored_bytes));
 if(inbytes > 0){
  inbytes = mpxplay_diskdrive_file_read(fc->filehand_data_src, &fc->buffer[fc->buf_stored_bytes], inbytes);
  if(inbytes <= 0){
   if(fc->cpy_ctrl&DISKFILE_CPYCTRL_STREAMSRC){
    //if(inbytes < 0)
    // goto end_copy; // TODO: ??? finishes recording without error message (but closes the file properly)
    goto cont_copy; // try to continue record
   }
   if(fc->filepos_in < fc->filelen_in)
    fc->retcode = MPXPLAY_ERROR_FILEHAND_CANTREAD;
   goto end_copy;
  }
  fc->buf_stored_bytes += inbytes;
  fc->filepos_in += inbytes;
 }else{
  funcbit_disable(fc->cpy_ctrl, DISKFILE_CPYCTRL_OUTPAUSED);
 }

 retval = diskfile_filecopy_openfile_newout_trigger(fc, TRUE); // check filename changes and open a new file if required
 if(retval == MPXPLAY_ERROR_FILEHAND_SKIPFILE)
  goto skip_file;
 else if(retval != MPXPLAY_ERROR_FILEHAND_OK)
  goto end_copy;

 if(!funcbit_test(fc->cpy_ctrl, DISKFILE_CPYCTRL_OUTPAUSED) && fc->filehand_data_dest){
  outbytes = mpxplay_diskdrive_file_write(fc->filehand_data_dest, fc->buffer, fc->buf_stored_bytes);
  if(outbytes < fc->buf_stored_bytes){
   fc->retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
   goto end_copy;
  }
  fc->buf_stored_bytes = 0;
  fc->filepos_out += outbytes;
 }

 goto cont_copy; // continue copy

skip_file:
 mpxplay_timer_deletefunc(diskfile_filecopy_do_copy,fc);
 return;
end_copy:
 mpxplay_timer_deletefunc(diskfile_filecopy_do_copy,fc);
 mpxplay_timer_addfunc(diskfile_filecopy_closefiles,fc,MPXPLAY_TIMERFLAG_INDOS,0);
cont_copy:
 return;
}

static unsigned int diskfile_filemove_check_samedrive(struct filecopy_t *fc)
{
 int drive_d,drive_s;

 drive_s=pds_getdrivenum_from_path(fc->infilename);
 drive_d=pds_getdrivenum_from_path(fc->outfilename);

 if(drive_d==drive_s)
  return 1;

 return 0;
}

static void diskfile_filemove_do(struct filecopy_t *fc)
{
 struct mpxpframe_s *frp;
 diskfile_filecopymove_progress_window(fc,0);
 if(fc->retcode==MPXPLAY_ERROR_FILEHAND_USERABORT)
  return;
 frp=fc->mvp->fr_primary;
 if((frp->filetype & HFT_FILE_EXT) || frp->filehand_datas){
  if( ((fc->cpy_type&DISKFILE_CPYTYPE_RENAME) && (pds_strnicmp(fc->mvp->pei0->filename,fc->infilename,pds_strlen(fc->infilename))==0)) // currently playing file or its updir is renamed
   || ((fc->cpy_type&DISKFILE_CPYTYPE_MOVE) && pds_utf8_stricmp(fc->mvp->pei0->filename,fc->infilename)==0) // currently playing file is moved
  ){
   if(frp->filetype & HFT_FILE_EXT){
    if(playcontrol&PLAYC_RUNNING)
     funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_STARTAVFILE);
    AU_stop(fc->mvp->aui);
    mpxplay_infile_suspend_close(frp);
   }else{
    mpxplay_diskdrive_file_close(frp->filehand_datas);
    frp->filehand_datas=NULL;
   }
   funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_REOPEN);
  }
 }
 if(fc->cpy_ctrl&DISKFILE_CPYCTRL_OVERWRITE)
  mpxplay_diskdrive_unlink(fc->mdds_src,fc->outfilename);
 fc->retcode=mpxplay_diskdrive_rename(fc->mdds_src,fc->infilename,fc->outfilename);
}

static void diskfile_filecopyrename_postprocess(struct filecopy_t *fc)
{
 if(fc->pei_curr){ // if not a subdir-filename
  if(((fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_RENAME)) && (fc->retcode==MPXPLAY_ERROR_FILEHAND_OK))
   || (fc->retcode==MPXPLAY_ERROR_FILEHAND_DELETE) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_SKIPFILE)
   || ((fc->cpy_type&DISKFILE_CPYTYPE_MOVE) && (!(fc->psi_src->editsidetype&PLT_DIRECTORY) || fc->psi_src->sublistlevel))
   || (fc->cpy_ctrl&DISKFILE_CPYCTRL_SKIPFILE)
  ){
   if(fc->pei_curr->infobits&PEIF_SELECTED){ // clear SELECTED flag
    funcbit_disable(fc->pei_curr->infobits,PEIF_SELECTED);
    if(fc->psi_src->selected_files)
     fc->psi_src->selected_files--;
    refdisp|=RDT_EDITOR;
   }
  }
  if((fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_RENAME)) || (fc->retcode!=MPXPLAY_ERROR_FILEHAND_OK)
   || ((fc->cpy_type&DISKFILE_CPYTYPE_MOVE) && (!(fc->psi_src->editsidetype&PLT_DIRECTORY) || fc->psi_src->sublistlevel)))
   fc->pei_curr++; // skip if entry was not deleted (copy,rename,move in playlist,error)
 }
 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_ALLFILE))
  funcbit_disable(fc->cpy_ctrl,(DISKFILE_CPYCTRL_CREATENOEXT|DISKFILE_CPYCTRL_OVERWRITE|DISKFILE_CPYCTRL_SKIPFILE));
 fc->filenum_curr++;
}

static void diskfile_filecopymove_postprocess(struct filecopy_t *fc)
{
 struct mpxpframe_s *frp=fc->mvp->fr_primary;
 char *reopenfilename,strtmp[MAX_PATHNAMELEN];
 struct playlist_entry_info pei_tmp;

 if(funcbit_test(fc->cpy_ctrl,DISKFILE_CPYCTRL_REOPEN)){ // currently playing file is moved/renamed
  if(fc->retcode==MPXPLAY_ERROR_FILEHAND_OK){ // re-open (out/new)file at new location
   if(fc->pei_curr && (fc->pei_curr->entrytype==DFT_SUBDIR)){ // parent/updir of currently playing file is renamed
    unsigned int pos=pds_strcpy(strtmp,fc->outfilename);
    pds_strcpy(&strtmp[pos],&fc->mvp->pei0->filename[pds_strlen(fc->infilename)]);
    reopenfilename=&strtmp[0];
   }else
    reopenfilename=&fc->outfilename[0];
   pds_memset(&pei_tmp,0,sizeof(pei_tmp));
   if(playlist_editlist_allocated_copy_entry(&pei_tmp,fc->mvp->pei0)){
    playlist_editlist_del_filename(NULL,&pei_tmp);
    playlist_editlist_add_filename(NULL,&pei_tmp,reopenfilename);
    playlist_pei0_set(fc->mvp,&pei_tmp,0);
    playlist_editlist_allocated_clear_entry(&pei_tmp);
   }
  }else{ // re-open input file at move fault
   reopenfilename=fc->mvp->pei0->filename;
  }
  if(frp->filetype & HFT_FILE_EXT){
   if(mpxplay_infile_avfile_open(frp,reopenfilename,(MPXPLAY_INFILE_OPENMODE_PLAY|MPXPLAY_INFILE_OPENMODE_INFO_LENGTH),frp->frameNum))
    if(fc->cpy_ctrl&DISKFILE_CPYCTRL_STARTAVFILE)
     mpxplay_play_start_or_pause(fc->mvp);
  }else{
   frp->filehand_datas=mpxplay_diskdrive_file_open(frp->mdds,reopenfilename,(O_RDONLY|O_BINARY|MPXPLAY_INFILE_OPENMODE_READ_CONTENT),NULL);
   mpxplay_diskdrive_file_seek(frp->filehand_datas,frp->filepos,SEEK_SET);
  }
  funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_REOPEN);
 }else if(funcbit_test(fc->cpy_ctrl,DISKFILE_CPYCTRL_REOPENAVFILE)){
  if(mpxplay_infile_avfile_open(frp,fc->mvp->pei0->filename,MPXPLAY_INFILE_OPENMODE_PLAY|MPXPLAY_INFILE_OPENMODE_INFO_LENGTH,frp->frameNum)){
   if(frp->filetype & HFT_FILE_EXT){
    if(fc->cpy_ctrl&DISKFILE_CPYCTRL_STARTAVFILE)
     mpxplay_play_start_or_pause(fc->mvp);
   }else{
    MIXER_allfuncinit_reinit(frp->mpi);
    if(fc->cpy_ctrl&DISKFILE_CPYCTRL_STARTAVFILE)
     AU_prestart(fc->mvp->aui);
   }
   refdisp|=RDT_HEADER;
  }
  funcbit_disable(fc->cpy_ctrl,(DISKFILE_CPYCTRL_REOPENAVFILE|DISKFILE_CPYCTRL_STARTAVFILE));
 }

 //if((fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_COPY)) && (fc->retcode==MPXPLAY_ERROR_FILEHAND_OK) && !mpxinbuf_lowlevel_file_isopen(frp) && (pds_utf8_stricmp(fc->mvp->pei0->filename,fc->outfilename)==0)){ // re-open currently played file (delete, after copy/create again)
 // if(mpxinbuf_lowlevel_file_open_read(frp,fc->outfilename))
 //  mpxinbuf_lowlevel_file_seek(frp,frp->filepos);
 //}

 if(fc->pei_curr && (fc->cpy_type&(DISKFILE_CPYTYPE_COPY|DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME))){
  // delete, update or create new playlist entry in directory browser after copy/move/rename
  if((fc->retcode==MPXPLAY_ERROR_FILEHAND_OK) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_CANTREAD) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_CANTWRITE) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_USERABORT) || (fc->retcode==MPXPLAY_ERROR_FILEHAND_DELETE)){
   pds_getpath_from_fullname(fc->outpath_curr,fc->outfilename);
   diskfile_filecopymove_update_sides(fc,fc->infilename,fc->outfilename,fc->pei_curr);
  }
 }

#ifdef MPXPLAY_LINK_CREDENTIALS
 if(diskfile_filecopymove_credentials_init(fc,diskfile_filecopymove_checkfilenames))
  return;
#endif

 if(fc->retcode==MPXPLAY_ERROR_FILEHAND_USERABORT) // aborted by user
  goto err_out_finish;

 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_ALLFILE))
  funcbit_disable(fc->cpy_ctrl,(DISKFILE_CPYCTRL_CREATENOEXT|DISKFILE_CPYCTRL_OVERWRITE|DISKFILE_CPYCTRL_SKIPFILE));

 if(fc->retcode!=MPXPLAY_ERROR_FILEHAND_OK)
  pds_strcpy(fc->lastfilename,fc->outfilename);

 fc->outfilename[0]=0;

 diskfile_filecopyrename_postprocess(fc);

 if(fc->retcode==MPXPLAY_ERROR_FILEHAND_SKIPFILE) // not a real error, just sign
  fc->retcode=MPXPLAY_ERROR_FILEHAND_OK;          // clear it

 if((fc->retcode!=MPXPLAY_ERROR_FILEHAND_OK) && !(fc->cpy_ctrl&DISKFILE_CPYCTRL_IGNALLERR)){
  fc->last_error=fc->retcode;
  playlist_diskfile_show_errmsg(fc);
  return;
 }

 mpxplay_timer_addfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
 return;

err_out_finish:
 mpxplay_timer_addfunc(diskfile_filecopy_dealloc,fc,MPXPLAY_TIMERFLAG_INDOS,0);
}

static void diskfile_filecopymove_select(struct filecopy_t *fc)
{
 if(fc->retcode==MPXPLAY_ERROR_FILEHAND_OK){
  if( (fc->cpy_type&DISKFILE_CPYTYPE_RENAME) // rename
   || ((fc->cpy_type&DISKFILE_CPYTYPE_MOVE) && diskfile_filemove_check_samedrive(fc)) // move
  ){
   diskfile_filemove_do(fc);
  }else{ // copy
   mpxplay_timer_addfunc(diskfile_filecopy_openfiles,fc,MPXPLAY_TIMERFLAG_INDOS,0);
   return;
  }
 }
 mpxplay_timer_addfunc(diskfile_filecopymove_postprocess,fc,MPXPLAY_TIMERFLAG_INDOS,0);
}

static void diskfile_filecopymove_loop(struct filecopy_t *fc)
{
 int retcode;

 if(fc->retcode==MPXPLAY_ERROR_FILEHAND_USERABORT) // aborted by user
  goto err_out_msg;

 if(!funcbit_test(fc->cpy_ctrl,(DISKFILE_CPYCTRL_DONTWAITHPP|DISKFILE_CPYCTRL_SKIPWAITHPP))){
  int lps_wait=mpxplay_timer_lowpriorstart_wait();
  if(lps_wait){
   diskfile_filecopymove_progress_window(fc,lps_wait);
   if(fc->retcode==MPXPLAY_ERROR_FILEHAND_USERABORT)
    goto err_out_msg;
   return;
  }
 }

 if(!funcbit_test(fc->cpy_ctrl,DISKFILE_CPYCTRL_SKIPWAITHPP))
  fc->statwin_begintime_waitdisp=0;
 funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SKIPWAITHPP);

 fc->retcode=MPXPLAY_ERROR_FILEHAND_OK;

 retcode=diskfile_filecopymove_getnextfile(fc);

#ifdef MPXPLAY_LINK_CREDENTIALS
 if((fc->retcode==MPXPLAY_ERROR_FILEHAND_REMOVEDIR) && diskfile_filecopymove_credentials_init(fc,diskfile_filecopymove_removeindir_retry))
  goto err_out_next;
#endif

 if(retcode>0){  // no file
  if(retcode==1){ // no file, get next (loop in timer)
   if((fc->retcode!=MPXPLAY_ERROR_FILEHAND_OK) && !(fc->cpy_ctrl&DISKFILE_CPYCTRL_IGNALLERR))
    goto err_out_msg;
   else
    mpxplay_timer_modifyfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
  }else{ //do nothing (warning message follows from getnextfile)
   goto err_out_next;
  }
 }else{
  mpxplay_timer_modifyfunc(&diskfile_filecopymove_loop,fc,0,0);
  if(retcode==0){ // no error, do copy/move/del
   if(fc->cpy_type&DISKFILE_CPYTYPE_DEL)
    playlist_diskfile_delete_do(fc);
   else
    diskfile_filecopymove_checkfilenames(fc);
  }else{           // finish/abort copy
   if((fc->retcode!=MPXPLAY_ERROR_FILEHAND_OK) && !(fc->cpy_ctrl&DISKFILE_CPYCTRL_IGNALLERR)){
    funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_MULTIFILE); // hack for [Ok] button
    goto err_out_msg;
   }else
    goto err_out_finish;
  }
 }
 return;

err_out_msg:
 mpxplay_timer_modifyfunc(&diskfile_filecopymove_loop,fc,0,0);
 fc->last_error=fc->retcode;
 playlist_diskfile_show_errmsg(fc);
 return;
err_out_finish:
 mpxplay_timer_addfunc(diskfile_filecopy_dealloc,fc,MPXPLAY_TIMERFLAG_INDOS,0);
err_out_next:
 mpxplay_timer_modifyfunc(&diskfile_filecopymove_loop,fc,0,0);
 return;
}

static int diskfile_filecopymove_start(struct filecopy_t *fc)
{
 unsigned int len;
 char *p, destfn[MAX_PATHNAMELEN]="",path[MAX_PATHNAMELEN],newname[MAX_PATHNAMELEN];

 diskfile_count_files_stop(fc);
 funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_NOEXTCHK);

 if((fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) || fc->directories_selected){
  if(!fc->source_filtermask[0] || !pds_strlenc(fc->source_filtermask,' ')) // empty source_filtermask, do nothing
  {
   pds_strcpy(source_default_filter_copy,PDS_DIRECTORY_ALLFILES_STR); // !!! all files (not only the supported)
   return MPXPLAY_ERROR_FILEHAND_CANTOPEN;
  }
 }else if(pds_stricmp(fc->outfilenam_notimestamp, fc->outpath_argument) == 0)  // output file has been not modified manually
  funcbit_enable(fc->cpy_ctrl, DISKFILE_CPYCTRL_OUTFNRECREAT);                 // set this for streams to create filename from metadata

 pds_sfn_limit(fc->outpath_argument);

 if(fc->cpy_type&DISKFILE_CPYTYPE_RENAME){
  funcbit_disable(fc->cpy_type,DISKFILE_CPYTYPE_MOVE);
  goto copymove_start_skip1;
 }

 p = pds_getfilename_from_fullname(fc->outpath_argument);
 if(p)
  pds_strcpy(destfn,p);
 if(!pds_getpath_from_fullname(fc->outpath_curr,fc->outpath_argument) && p)
  fc->outpath_curr[0]=0;

 if(pds_filename_wildcard_chk(destfn)){
  if((fc->directories_selected>1) || (fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE)){ // multiply dir/file copy/move/rename
   if(fc->outpath_curr[0])
    pds_filename_build_fullpath(fc->outpath_root,fc->path_src,fc->outpath_curr);
   else{
    pds_strcpy(fc->outpath_root,fc->path_src);
    if(fc->cpy_type&DISKFILE_CPYTYPE_MOVE){ // multiply rename
     funcbit_disable(fc->cpy_type,DISKFILE_CPYTYPE_MOVE);
     funcbit_enable(fc->cpy_type,DISKFILE_CPYTYPE_RENAME);
    }
   }
   goto copymove_start_skip1;
  }else{ // copy one directory or file to a new one (given by a wildcard string)
   char *srcfn=pds_getfilename_from_fullname(fc->selected_filename);
   pds_utf8_filename_wildcard_rename(newname,srcfn,destfn);
   if(fc->outpath_curr[0])
    pds_filename_build_fullpath(path,fc->path_src,fc->outpath_curr);
   else
    pds_getpath_from_fullname(path,fc->selected_filename);
   len=pds_strlen(path);
   if(path[len-1]!=PDS_DIRECTORY_SEPARATOR_CHAR)
    len+=pds_strcpy(&path[len],PDS_DIRECTORY_SEPARATOR_STR);
   pds_strcpy(&path[len],newname);
   funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SUBONECOPY);
  }
 }else // single file without wildcard
  pds_filename_build_fullpath(path,fc->path_src,fc->outpath_argument);

 fc->mdds_dest=playlist_loaddir_path_to_drivehandler(fc->psi_dest,path);

 if(pds_path_is_dir(path) && mpxplay_diskdrive_checkdir(fc->mdds_dest,path)){ // argument is an existent dir
  if((fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME)) && !(fc->outpath_curr[0]) && (fc->directories_selected==1) && !(fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) && (pds_utf8_stricmp(fc->selected_filename,path)==0)){ // uppercase/lowercase rename dir
   pds_getpath_from_fullname(fc->outpath_root,fc->selected_filename);
   funcbit_disable(fc->cpy_type,DISKFILE_CPYTYPE_MOVE);
   funcbit_enable(fc->cpy_type,DISKFILE_CPYTYPE_RENAME);
  }else
   diskfile_filecopymove_rebuild_outpathargument(fc,path);
 }else{
  if((fc->directories_selected>1) || (fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE)){
   fc->retcode=MPXPLAY_ERROR_FILEHAND_MULTITO1;
   goto err_out_start;
  }
  if((fc->cpy_type&(DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME)) && !(fc->outpath_curr[0])){ // rename one dir or file
   pds_getpath_from_fullname(fc->outpath_root,fc->selected_filename);
   funcbit_disable(fc->cpy_type,DISKFILE_CPYTYPE_MOVE);
   funcbit_enable(fc->cpy_type,DISKFILE_CPYTYPE_RENAME);
  }else{
   if(fc->directories_selected){ // copy/move content of a subdir to another (create outdir, don't copy highest subdirname)
    if(diskfile_filecopymove_check_samerootdir(fc->selected_filename,path)){ // copy/move a directory to itself?
     fc->retcode=MPXPLAY_ERROR_FILEHAND_SAMEDIR;
     pds_strcpy(fc->lastfilename,path);
     goto err_out_start;
    }
    pds_getpath_from_fullname(fc->outpath_curr,path);
    diskfile_filecopymove_rebuild_outpathargument(fc,path);
    if(diskfile_filecopymove_make_subdir(fc,path)<0)
     goto err_out_start;
    funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SUBONECOPY);
   }else{ // single file
    pds_filename_build_fullpath(fc->outpath_root,fc->path_src,fc->outpath_curr);
    pds_filename_assemble_fullname(fc->outpath_argument,fc->outpath_root,destfn);
    funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SUBONECOPY);
   }
  }
 }

copymove_start_skip1:

 if(!funcbit_test(fc->cpy_type,DISKFILE_CPYTYPE_RENAME) && !fc->mdds_dest){
  fc->retcode=MPXPLAY_ERROR_FILEHAND_CANTCOPY;
  pds_strcpy(fc->lastfilename,fc->outpath_argument);
  goto err_out_start;
 }

 fc->outpath_curr[0]=0;

 if((fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) || fc->directories_selected){
  pds_strcpy(source_default_filter_copy,fc->source_filtermask);
  if(funcbit_test(desktopmode,DTM_EDIT_ALLFILES) || pds_strricmp(fc->source_filtermask,".*")!=0)
   funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_NOEXTCHK);
 }
 funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SKIPWAITHPP);
 mpxplay_timer_addfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
 return MPXPLAY_ERROR_FILEHAND_OK; // success, continue copy/move

err_out_start:
 fc->last_error=fc->retcode;
 funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_MULTIFILE); // hack for [Ok] button
 playlist_diskfile_show_errmsg(fc);
 return MPXPLAY_ERROR_FILEHAND_CANTOPEN;
}

//---------------------------------------------------------------------------------------------------------------------------
static display_textwin_button_t buttons_copymove_confirm[]={
 {""          ,KEY_ENTER1},// gray enter
 {""          ,KEY_ENTER2},// white enter
 {"[ Cancel ]",0x2e63},    // 'c'
 {""          ,0x2e43},    // 'C'
 {""          ,0x6c00},    // alt-F5
 {""          ,KEY_ESC},   // ESC
 {NULL,0}
};

static display_textwin_button_t buttons_copymove_copywithsubdir_confirm[]={
 {"[ single Files ]",KEY_ENTER1},// gray enter
 {""          ,KEY_ENTER2},// white enter
 {""          ,0x2166},    // 'f'
 {""          ,0x2146},    // 'F'
 {"[ with Subdir ]",0x1f73},// 's'
 {""          ,0x1f53},    // 'S'
 {"[ Cancel ]",0x2e63},    // 'c'
 {""          ,0x2e43},    // 'C'
 {""          ,0x6c00},    // alt-F5
 {""          ,KEY_ESC},   // ESC
 {NULL,0}
};

static void diskfile_copymove_startwindow_keyhand(struct filecopy_t *fc,unsigned int extkey)
{
 switch(extkey){
  case KEY_ENTER1:
  case KEY_ENTER2:
  case 0x2166:  // 'f'
  case 0x2146:funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_WITHSUBDIR);
  case 0x1f73:  // 's'
  case 0x1f53:  // 'S'
   if(diskfile_filecopymove_start(fc) == MPXPLAY_ERROR_FILEHAND_OK)
    return; // success, continue copy/move
 }
 mpxplay_timer_addfunc(diskfile_filecopy_dealloc,fc,MPXPLAY_TIMERFLAG_INDOS,0);
 return;
}

void playlist_diskfile_copy_or_move(struct mainvars *mvp,unsigned int move)
{
 struct playlist_side_info *psi=mvp->psie,*psio=psi->psio;
 struct playlist_entry_info *pei=psi->editorhighline;
 struct filecopy_t *fc;
 unsigned int pathlen;
 void *tw;
 const static char *functext[2]={"Copy","Move or rename"};
 char msg[MAX_PATHNAMELEN+64];

 if(move>1)
  return;
 if(!(displaymode&DISP_FULLSCREEN))
  return;

 fc=diskfile_filecopy_alloc(mvp);
 if(!fc)
  return;
 if(!mpxplay_infile_frame_alloc(&fc->frp_src)){ // TODO: we don't need this for everything, just complicate to decide it (stream record, indexed copy)
  fc->last_error=MPXPLAY_ERROR_FILEHAND_MEMORY;
  playlist_diskfile_show_errmsg(fc);
  return;
 }

 if(move)
  funcbit_enable(fc->cpy_type,DISKFILE_CPYTYPE_MOVE);
 else
  funcbit_enable(fc->cpy_type,DISKFILE_CPYTYPE_COPY);

 funcbit_enable(fc->cpy_ctrl,(DISKFILE_CPYCTRL_EMPTYDIRCOPY | DISKFILE_CPYCTRL_NOEXTCHK)); // !!!

 fc->selected_filename=pei->filename;
 if(psi->selected_files){
  funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_MULTIFILE);
  fc->pei_selected=psi->firstentry;
  fc->pei_last=psi->lastentry;
 }else{
  if((pei->entrytype==DFT_DRIVE) || ((pei->entrytype==DFT_UPDIR) && !(fc->cpy_type&DISKFILE_CPYTYPE_COPY)) || (pei->entrytype==DFT_UPLIST)){
   fc->last_error=MPXPLAY_ERROR_FILEHAND_COPYDIR;
   playlist_diskfile_show_errmsg(fc);
   return;
  }
  fc->pei_selected=fc->pei_last=pei;
  if(pei->entrytype==DFT_UPDIR)
   fc->selected_filename=&psi->currdir[0];
 }

 if(pei->entrytype == DFT_NOTCHECKED){
  if(mpxplay_diskdrive_drive_config(NULL, MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_CANBESTREAM, fc->selected_filename, NULL) == TRUE)
   funcbit_enable(fc->cpy_ctrl, DISKFILE_CPYCTRL_STREAMSRC);
 }
 else if(GET_HFT(pei->entrytype) & HFT_STREAM)
 {
  funcbit_enable(fc->cpy_ctrl, DISKFILE_CPYCTRL_STREAMSRC);
 }

 if((fc->cpy_ctrl & DISKFILE_CPYCTRL_STREAMSRC) || (pei->infobits & PEIF_INDEXED)){
  pds_getpath_from_fullname(fc->path_src,fc->selected_filename);
 }else{
  if((psi->editsidetype & PLT_DIRECTORY) && !psi->sublistlevel)
   pds_strcpy(fc->path_src,psi->currdir);
  else if(fc->cpy_type&DISKFILE_CPYTYPE_COPY)
   funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_WITHSUBDIR);
 }

 if((psio->editsidetype&PLT_DIRECTORY) && (psio->sublistlevel<=1)){
  if(!psio->sublistlevel)
   fc->psi_dest=psio;
  pds_strcpy(fc->path_dest,psio->currdir);
 }else
  pds_strcpy(fc->path_dest,fc->path_src);
 if(!fc->psi_dest)
  fc->psi_dest=psi;

 pds_strcpy(fc->outpath_root,fc->path_dest);
 fc->mdds_dest=fc->psi_dest->mdds;

 if(fc->path_dest[0] && (!(psi->editsidetype&PLT_DIRECTORY) || psi->sublistlevel || (!psi->selected_files && (pei->entrytype==DFT_UPDIR)) || (pds_strcmp(fc->path_dest,fc->path_src)!=0))){
  pathlen=pds_strcpy(fc->outpath_argument,fc->path_dest);
  if(fc->outpath_argument[pathlen-1]!=PDS_DIRECTORY_SEPARATOR_CHAR)
   pathlen+=pds_strcpy(&fc->outpath_argument[pathlen],PDS_DIRECTORY_SEPARATOR_STR);
 }else       // assumed rename or copy to the same dir
  pathlen=0; // show only filename

 pds_strcpy(fc->source_filtermask,source_default_filter_copy);

 diskfile_count_files_start(fc);

 if(psi->selected_files){
  sprintf(fc->headtext," %s files ",functext[move]);
  sprintf(msg," %s %d file%s to",functext[move],psi->selected_files,((psi->selected_files>1)? "s":""));
 }else{
  sprintf(fc->headtext," %s file ",functext[move]);
  sprintf(msg," %s \"%s\" to",functext[move], ((fc->cpy_ctrl & DISKFILE_CPYCTRL_STREAMSRC)? fc->selected_filename : pds_getfilename_any_from_fullname(fc->selected_filename)));
 }
 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_WITHSUBDIR)){
  sprintf(fc->buttontext,"[ %s ]",functext[move]);
  buttons_copymove_confirm[0].text=&fc->buttontext[0];
  buttons_copymove_confirm[4].extkey=(move)? 0x6d00:0x6c00; // !!! alt-F6/F5 hardwired
 }

 fc->count_tw=tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_MSGCENTERALIGN,fc->headtext,diskfile_copymove_startwindow_keyhand,fc);
 fc->count_itemnum=display_textwin_additem_msg_alloc(tw,0,0,0,msg);

 if((fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) || fc->directories_selected){
  if(pathlen || psi->selected_files)
   pds_strcpy(&fc->outpath_argument[pathlen],PDS_DIRECTORY_ALLFILES_STR);
  else // assumed rename/simple copy
   pds_strcpy(&fc->outpath_argument[pathlen],pds_getfilename_any_from_fullname(fc->selected_filename));
  display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,1,50,fc->outpath_argument,MAX_PATHNAMELEN-2);
  display_textwin_additem_separatorline(tw,2);
  display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,3,"Source filter: ");
  display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,sizeof("Source filter: ")-1,3,8,fc->source_filtermask,sizeof(fc->source_filtermask)-1);
  //display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,4,"(\"*.*\" : supported files; \"*.?*\" : all files)");
  if(fc->directories_selected && (fc->cpy_type&DISKFILE_CPYTYPE_MOVE)) // ???
   funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_NOEXTCHK);
 }else{
  if((!(fc->cpy_ctrl & DISKFILE_CPYCTRL_STREAMSRC) && !(pei->infobits & PEIF_INDEXED)) || !diskfile_renamebyid3_createoutfilename(fc,fc->psi_src,pei,&fc->outpath_argument[pathlen], (MAX_PATHNAMELEN / 2) , (fc->cpy_type & ~(DISKFILE_CPYTYPE_NEWOUTBYID3|DISKFILE_CPYTYPE_OUTTIMESTAMP))))
   pds_strcpy(&fc->outpath_argument[pathlen],pds_getfilename_any_from_fullname(fc->selected_filename));
  pds_strcpy(fc->outfilenam_notimestamp, fc->outpath_argument);
  display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,-1,50,fc->outpath_argument,MAX_PATHNAMELEN-2);
  if(fc->cpy_ctrl & DISKFILE_CPYCTRL_STREAMSRC){
   display_textwin_additem_switchline(tw, TEXTWIN_FLAG_MSGLEFTALIGN,0,-1,&fc->cpy_type,DISKFILE_CPYTYPE_NEWOUTBYID3,"Open new output file at title/metadata change (MP3,AAC streams only)");
   display_textwin_additem_switchline(tw, TEXTWIN_FLAG_MSGLEFTALIGN,0,-1,&fc->cpy_type,DISKFILE_CPYTYPE_OUTTIMESTAMP,"Add timestamp to output file names");
   display_textwin_additem_switchline(tw, TEXTWIN_FLAG_MSGLEFTALIGN,0,-1,&fc->cpy_type,DISKFILE_CPYTYPE_OUTCUEBYID3, "Save metadata changes into a CUE file (MP3,AAC streams only)");
  }else if(pei->infobits & PEIF_INDEXED){
   display_textwin_additem_switchline(tw, TEXTWIN_FLAG_MSGLEFTALIGN,0,-1,&fc->cpy_type,DISKFILE_CPYTYPE_IGNOREINDEX,"Ignore index (else split headerless stream files by index)");
  }
 }

 display_textwin_additem_separatorline(tw,-1);
 if(fc->cpy_ctrl&DISKFILE_CPYCTRL_WITHSUBDIR){
  display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,&buttons_copymove_copywithsubdir_confirm[0],NULL);
 }else
  display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,&buttons_copymove_confirm[0],NULL);
 display_textwin_openwindow_items(tw,0,0,0);
}

//-------------------------------------------------------------------------
// delete files

static display_textwin_button_t buttons_error_delcurrplay[]={
 {" Delete "   ,0x2064}, // 'd'
 {""           ,0x2044}, // 'D'
 {" Skip "     ,0x1f73}, // 's'
 {""           ,0x1f53}, // 'S'
 {" Cancel "   ,0x2e63}, // 'c'
 {""           ,0x2e43}, // 'C'
 {""           ,KEY_ESC},// ESC
 {NULL,0}
};

static void diskfile_filedel_delcurrplay_keyhand(struct filecopy_t *fc,unsigned int extkey)
{
 switch(extkey){
  case 0x2064:
  case 0x2044:funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_DELFILE);
              playlist_diskfile_delete_do(fc);
              break;
  case 0x1f73:
  case 0x1f53:if(fc->pei_curr)
               fc->pei_curr++;
              mpxplay_timer_addfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
              break;
  case 0x2e63:
  case 0x2e43:
  case KEY_ESC:fc->retcode=MPXPLAY_ERROR_FILEHAND_USERABORT;
               diskfile_filecopy_dealloc(fc);
 }
}

static void playlist_diskfile_delete_do(struct filecopy_t *fc)
{
 void *tw;
 struct mpxpframe_s *frp=fc->mvp->fr_primary;

 if(pds_utf8_stricmp(fc->mvp->pei0->filename,fc->infilename)==0){
  if(fc->cpy_ctrl&DISKFILE_CPYCTRL_DELFILE){
   mpxplay_diskdrive_file_close(frp->filehand_datas); // ???
   frp->filehand_datas=NULL;
  }else
   goto err_out_delcurrplay;
 }

 diskfile_filecopymove_progress_window(fc,0);
 if(fc->retcode==MPXPLAY_ERROR_FILEHAND_USERABORT)
  goto err_out_finish;

 fc->retcode=mpxplay_diskdrive_unlink(fc->mdds_src,fc->infilename);

#ifdef MPXPLAY_LINK_CREDENTIALS
 if(diskfile_filecopymove_credentials_init(fc,playlist_diskfile_delete_do))
  return;
#endif

 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_ALLFILE))
  funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_DELFILE);

 if(fc->retcode==MPXPLAY_ERROR_FILEHAND_OK){
  diskfile_filecopymove_update_sides(fc,fc->infilename,NULL,NULL);
  fc->filenum_curr++;
 }else{
  pds_strcpy(fc->lastfilename,fc->infilename);
  if((pds_utf8_stricmp(fc->mvp->pei0->filename,fc->infilename)==0) && !frp->filehand_datas){ // re-open currently played file (if delete was not successull)
   frp->filehand_datas=mpxplay_diskdrive_file_open(frp->mdds,fc->infilename,(O_RDONLY|O_BINARY|MPXPLAY_INFILE_OPENMODE_READ_CONTENT),NULL);
   mpxplay_diskdrive_file_seek(frp->filehand_datas,frp->filepos,SEEK_SET);
  }
  diskfile_filecopyrename_postprocess(fc);
 }

 if((fc->retcode!=MPXPLAY_ERROR_FILEHAND_OK) && !(fc->cpy_ctrl&DISKFILE_CPYCTRL_IGNALLERR)){
  fc->last_error=fc->retcode;
  playlist_diskfile_show_errmsg(fc);
  return;
 }

 mpxplay_timer_addfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
 return;

err_out_delcurrplay:
 diskfile_statwin_close(fc);
 tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN,fc->headtext,diskfile_filedel_delcurrplay_keyhand,fc);
 display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,"Program is currently playing this file");
 display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,fc->infilename);
 display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,"Do you wish to delete it?");
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,buttons_error_delcurrplay,NULL);
 display_textwin_openwindow_items(tw,0,0,0);
 return;

err_out_finish:
 mpxplay_timer_addfunc(diskfile_filecopy_dealloc,fc,MPXPLAY_TIMERFLAG_INDOS,0);
}

static void diskfile_delete_start(struct filecopy_t *fc,unsigned int extkey)
{
 switch(extkey){
  case KEY_ENTER1:
  case KEY_ENTER2:
   diskfile_count_files_stop(fc);
   if((fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) || fc->directories_selected){
    if(!fc->source_filtermask[0] || !pds_strlenc(fc->source_filtermask,' ')){ // empty source_filtermask, do nothing
     pds_strcpy(source_default_filter_del,"*.?*"); // restore default
     break;
    }
    pds_strcpy(source_default_filter_del,fc->source_filtermask);
   }
   fc->statwin_begintime_waitdisp=pds_gettimem();
   funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SKIPWAITHPP);
   mpxplay_timer_addfunc(diskfile_filecopymove_loop,fc,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_INDOS,0);
   return; // success, continue delete
 }
 mpxplay_timer_addfunc(diskfile_filecopy_dealloc,fc,MPXPLAY_TIMERFLAG_INDOS,0);
}

void playlist_diskfile_delete_init(struct mainvars *mvp)
{
 struct filecopy_t *fc;
 struct playlist_side_info *psi=mvp->psie;
 struct playlist_entry_info *pei=psi->editorhighline;
 unsigned int y;
 void *tw;
 char msg[MAX_PATHNAMELEN+64];

 if(!(displaymode&DISP_FULLSCREEN))
  return;

 if((psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel && (mpxplay_diskdrive_get_mdfs_infobits(psi->mdds) & MPXPLAY_DRIVEHANDFUNC_INFOBIT_READONLY)){
  display_textwin_openwindow_errormessage("Cannot delete from this drive (read only)!");
  return;
 }

 fc=diskfile_filecopy_alloc(mvp);
 if(!fc)
  return;

 funcbit_enable(fc->cpy_type,DISKFILE_CPYTYPE_DEL);
 funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_EMPTYDIRCOPY);

 if(psi->selected_files){
  funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_MULTIFILE);
  fc->pei_selected=psi->firstentry;
  fc->pei_last=psi->lastentry;
 }else{
  if((pei->entrytype==DFT_DRIVE) || (pei->entrytype==DFT_UPDIR) || (pei->entrytype==DFT_UPLIST)){
   fc->last_error=MPXPLAY_ERROR_FILEHAND_COPYDIR;
   playlist_diskfile_show_errmsg(fc);
   return;
  }
  fc->pei_selected=fc->pei_last=pei;
 }
 if(psi->selected_files || (pei->entrytype==DFT_SUBDIR))
  if(funcbit_test(desktopmode,DTM_EDIT_ALLFILES) || pds_strricmp(fc->source_filtermask,".*")!=0)
   funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_NOEXTCHK);

 pds_strcpy(fc->source_filtermask,source_default_filter_del);

 diskfile_count_files_start(fc);

 if(psi->selected_files || !(fc->count_ctrl&DISKFILE_COUNTCTRL_COMPLETE))
  pds_strcpy(fc->headtext," Delete files ");
 else
  pds_strcpy(fc->headtext," Delete file ");

 sprintf(fc->buttontext,"[%s]",fc->headtext);
 buttons_copymove_confirm[0].text=&fc->buttontext[0];
 buttons_copymove_confirm[4].extkey=0x6f00; // !!! alt-F8 hardwired

 fc->count_tw=tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_MSGCENTERALIGN,fc->headtext,diskfile_delete_start,fc);

 y=0;
 if(psi->selected_files || !(fc->count_ctrl&DISKFILE_COUNTCTRL_COMPLETE)){
  if(fc->directories_selected){
   unsigned long files_selected=(psi->selected_files)? (psi->selected_files-fc->directories_selected):0;
   sprintf(msg,"You have selected %d file%s and %d director%s",files_selected,((files_selected>1)? "s":""),fc->directories_selected,((fc->directories_selected>1)? "ies":"y"));
   display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,y++,msg);
   sprintf(msg,"(total %d file%s and %d subdir%s)",fc->counted_filenum_all,((fc->counted_filenum_all>1)? "s":""),fc->counted_directories,((fc->counted_directories>1)? "s":""));
   fc->count_itemnum=display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,y++,msg);
  }else{
   sprintf(msg,"You have selected %d file%s",fc->counted_filenum_all,((fc->counted_filenum_all>1)? "s":""));
   display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,y++,msg);
  }
  display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,y++,"Do you wish to delete them?");
  display_textwin_additem_separatorline(tw,y++);
 }else{
  snprintf(msg,sizeof(msg),"Delete \"%s\" %s from disk?\nAre you sure?",pds_getfilename_from_fullname(pei->filename),((pei->entrytype==DFT_SUBDIR)? "directory":"file"));
  display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,y++,msg);
 }
 if(psi->selected_files || fc->directories_selected){
  display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,y,"Filter: ");
  display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,sizeof("Filter: ")-1,y++,8,fc->source_filtermask,sizeof(fc->source_filtermask)-1);
  //display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,5,"(\"*.*\" : supported files; \"*.?*\" : all files)");
 }

 display_textwin_additem_separatorline(tw,-1);
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,&buttons_copymove_confirm[0],NULL);
 display_textwin_openwindow_items(tw,0,0,0);
}

//-------------------------------------------------------------------------
// collect/show multiply file infos from subdirs too

static display_textwin_button_t buttons_multifileinfos[]={
 {"[ Restart ]",0x1372},     // 'r'
 {""           ,0x1352},     // 'R'
 {"[ Stop ]"   ,0x1f73},     // 's'
 {""           ,0x1f53},     // 'S'
 {"[ Close ]"  ,0x2e63},     // 'c'
 {""           ,0x2e43},     // 'C'
 {""           ,0x3d00},     // F3
 {""           ,KEY_ESC},    // ESC
 //{""           ,KEY_TAB},
 {""           ,0x9400},     // ctrl-TAB
 {""           ,KEY_UP_GRAY},
 {""           ,KEY_UP_WHITE},
 {""           ,KEY_DOWN_GRAY},
 {""           ,KEY_DOWN_WHITE},
 {""           ,0x8400},     // ctrl-pgup
 {""           ,0x84e0},     //
 {""           ,0x7600},     // ctrl-pgdn
 {""           ,0x76e0},     //
 {NULL,0}
};

static void diskfile_multifileinfos_start(struct filecopy_t *fc)
{
 fc->cpy_type=DISKFILE_CPYTYPE_INFO_SIZE;
 funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_EMPTYDIRCOPY);

 if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_SHOWMULTI))
  diskfile_statwin_close(fc);

 if(fc->psi_src->selected_files){
  fc->pei_selected=fc->psi_src->firstentry;
  fc->pei_last=fc->psi_src->lastentry;
  funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_MULTIFILE);
 }else{
  fc->pei_selected=fc->pei_last=fc->psi_src->editorhighline;
  funcbit_disable(fc->cpy_ctrl,DISKFILE_CPYCTRL_MULTIFILE);
 }

 if(fc->psi_src->selected_files || (fc->pei_selected->entrytype==DFT_SUBDIR) || (fc->pei_selected->entrytype==DFT_UPDIR))
  diskfile_count_files_start(fc);
 else
  mpxplay_timer_addfunc(diskfile_count_files_stop,fc,MPXPLAY_TIMERFLAG_INDOS|MPXPLAY_TIMERFLAG_LOWPRIOR,0);
}

static void diskfile_multifileinfos_stop(struct filecopy_t *fc)
{
 if(fc->psi_src->selected_files || fc->pei_selected->entrytype==DFT_SUBDIR || fc->pei_selected->entrytype==DFT_UPDIR)
  diskfile_count_files_stop(fc);
}

static void diskfile_multifileinfos_close(struct filecopy_t *fc)
{
#ifdef MPXPLAY_FSIZE64
 if(!fc->psi_src->selected_files && (fc->pei_selected->entrytype==DFT_SUBDIR) && (funcbit_test(fc->count_ctrl,DISKFILE_COUNTCTRL_COMPLETE) || funcbit_test(fc->cpy_type,DISKFILE_CPYTYPE_INFO_TIME)))
  fc->pei_selected->filesize=(mpxp_filesize_t)fc->counted_filesizes_all;
#endif
 pds_strcpy(source_default_filter_info,fc->source_filtermask);
 if(fc->psi_src->selected_files || fc->pei_selected->entrytype==DFT_SUBDIR || fc->pei_selected->entrytype==DFT_UPDIR)
  diskfile_count_files_stop(fc);
 mpxplay_timer_addfunc(diskfile_filecopy_dealloc,fc,MPXPLAY_TIMERFLAG_INDOS,0);
}

static void diskfile_multifileinfos_buttonhandler(struct filecopy_t *fc,unsigned int extkey)
{
 switch(extkey){
  case KEY_ENTER1:
  case KEY_ENTER2:
   if(!(fc->cpy_ctrl&DISKFILE_CPYCTRL_SHOWMULTI))
    playlist_newsong_enter(fc->mvp,fc->mvp->psie);
   break;
  case 0x1372:
  case 0x1352:break;
  case 0x1f73:
  case 0x1f53:diskfile_multifileinfos_stop(fc);return;
  case 0x2e63:
  case 0x2e43:
  case 0x3d00:
  case KEY_ESC:diskfile_multifileinfos_close(fc); return;
  default:
   if(!mpxplay_control_keyboard_functions(extkey, fc->mvp))
    return;
   if(fc->mvp->step) // this is a hack to update info delayed (follow editorhighline) after skip
	mpxplay_timer_addfunc(diskfile_multifileinfos_start,fc,MPXPLAY_TIMERTYPE_SIGNAL,MPXPLAY_SIGNALTYPE_NEWFILE);
 }
 diskfile_multifileinfos_start(fc);
}

#define SHOW_FILEINFOS_ALLOCLEN    3072
#define SHOW_FILEINFOS_HEADINFOSIZE 384 // max. from "Length" to "Filedate"

extern unsigned int playlist_fileinfo_id3order[7];
extern char *playlist_fileinfo_id3text[7];

static void fileinfo_maketimemstr_info(char *str,unsigned long msec)
{
 sprintf(str,"%d:%2.2d.%2.2d",(unsigned long)(msec/(60*1000)),(unsigned long)(msec%(60*1000)/1000),(unsigned long)(msec%1000/10));
}

#ifdef MPXPLAY_GUI_CONSOLE
#define MPXPLAY_DISKFILE_FILEINFO_MAXFILELEN 38
#else
#define MPXPLAY_DISKFILE_FILEINFO_MAXFILELEN 50
#endif

static void diskfile_multifileinfos_wintext_single(struct filecopy_t *fc,char *msg)
{
 struct playlist_side_info *psi=fc->psi_src;
 struct playlist_entry_info *pei=fc->pei_selected;
 struct mpxpframe_s *frp=&fc->frp_src;
 struct mpxplay_infile_info_s *miis;
 struct mpxplay_audio_decoder_info_s *adi;
 struct mpxplay_diskdrive_data_s *mdds_file;
 unsigned int len,i,ff_result = 1;
 mpxp_int64_t timemsec;
 long timesec;
 struct pds_find_t ffblk;
 char *shortfname,strtmp[64],pathname[MAX_PATHNAMELEN];

 if(pei->mdds)
  mdds_file = pei->mdds;
 else if(psi->mdds && psi->mdds->mdfs && !(mpxplay_diskdrive_get_mdfs_infobits(psi->mdds) & MPXPLAY_DRIVEHANDFUNC_INFOBIT_LISTTYPEDIR))
  mdds_file = psi->mdds;
 else
  mdds_file = playlist_loaddir_filename_to_drivehandler(pei->filename);

 ff_result=mpxplay_diskdrive_findfirst(mdds_file,pei->filename,_A_NORMAL,&ffblk);

 if(GET_HFT(pei->entrytype)!=HFT_DFT)
  playlist_chkentry_get_onefileinfos_allagain(psi,pei,frp,((loadid3tag)? (loadid3tag|ID3LOADMODE_FILE):ID3LOADMODE_ALL),(MPXPLAY_INFILE_OPENMODE_CHECK|MPXPLAY_INFILE_OPENMODE_INFO_DECODER));

 shortfname = pds_getpath_from_fullname(pathname, pei->filename);

 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"pn:\"%s\" sn:\"%s\" fn:\"%s\"", pathname, shortfname, pei->filename);
 i=pds_utf8_strlen(pathname);
 len=max(MPXPLAY_DISKFILE_FILEINFO_MAXFILELEN,pds_utf8_strlen(shortfname));
 if(i>len)
  i=len;
 pds_utf8_str_centerize(pathname,i,1);
 len= pds_strcpy(msg,"Path    :"DISKFILE_GUI_TAB_CHAR"");
 len+=pds_strcat(msg,pathname);
 len+=pds_strcat(msg,"\n");
 len+=pds_strcat(msg,"Filename:"DISKFILE_GUI_TAB_CHAR"");
 len+=pds_strcat(msg,shortfname);
 len+=pds_strcat(msg,"\n");

 for(i=0;i<=I3I_MAX;i++){
  unsigned int index=playlist_fileinfo_id3order[i];
  if(len>=(SHOW_FILEINFOS_ALLOCLEN-SHOW_FILEINFOS_HEADINFOSIZE))
   break;
  len+=pds_strcat(msg,playlist_fileinfo_id3text[i]);
#ifndef MPXPLAY_GUI_CONSOLE
  len+=pds_strcat(msg,DISKFILE_GUI_TAB_CHAR);
#endif
  if(pei->id3info[index]){
   if((len+pds_strlen(pei->id3info[index]))>(SHOW_FILEINFOS_ALLOCLEN-SHOW_FILEINFOS_HEADINFOSIZE))
    break;
   len+=pds_strcat(msg,pei->id3info[index]);
  }else
   len+=pds_strcat(msg,"-");
  len+=pds_strcat(msg,"\n");
 }

 miis=frp->infile_infos;

 //len+=pds_strcat(msg,"Tag-type:"DISKFILE_GUI_TAB_CHAR"");
 //if(fileinfo_build_tagtype(strtmp,miis,0)){
 // len+=pds_strcat(msg,strtmp);
 // len+=pds_strcat(msg,"\n");
 //}else
 // len+=pds_strcat(msg,"-\n");

 if(frp->infile_funcs){

  timemsec=playlist_entry_get_timemsec(pei);
  timesec=(timemsec+50)/1000;
  timemsec=(timemsec+50)%1000;
  timemsec=timemsec/100;
  if(timesec>=3600)
   sprintf(strtmp,"Length  :"DISKFILE_GUI_TAB_CHAR"%d:%2.2d:%2.2d.%d",(timesec/3600),(timesec%3600)/60,(timesec%3600)%60,timemsec);
  else
   sprintf(strtmp,"Length  :"DISKFILE_GUI_TAB_CHAR"%d:%2.2d.%d",(timesec/60),(timesec%60),(long)timemsec);
  len+=pds_strcat(msg,strtmp);
  if(pei->infobits&PEIF_INDEXED){
   unsigned long endtime;
   len+=pds_strcat(msg," (index: ");
   fileinfo_maketimemstr_info(strtmp,pei->pstime);
   len+=pds_strcat(msg,strtmp);
   len+=pds_strcat(msg," - ");
   endtime=pei->petime;
   if(pei->petime)
    endtime=pei->petime;
   else
    endtime=pei->timemsec;
   fileinfo_maketimemstr_info(strtmp,endtime);
   len+=pds_strcat(msg,strtmp);
   len+=pds_strcat(msg,")");
  }
  len+=pds_strcat(msg,"\n");

  adi=miis->audio_decoder_infos;

  len+=pds_strcat(msg,"Filetype:"DISKFILE_GUI_TAB_CHAR"");
  if(miis->longname){
   len+=pds_strcat(msg,miis->longname);
   len+=pds_strcat(msg,"\n");
  }else{
   char *e;
   if(frp->infile_funcs && frp->infile_funcs->file_extensions[0])
    e=frp->infile_funcs->file_extensions[0];
   else{
    e=pds_strrchr(shortfname,'.');
    if(e)
     e++;
   }
   if(!adi->shortname)
    mpxplay_decoders_audio_search_waveid(miis->audio_stream,adi);
   if(e || adi->shortname){
    sprintf(strtmp,"%3.3s->%3.3s\n",((e)? e:"???"),(adi->shortname? adi->shortname:"???"));
    len+=pds_strcat(msg,strtmp);
   }else
    len+=pds_strcat(msg,"n/a\n");
  }

  len+=pds_strcat(msg,"Freq    :"DISKFILE_GUI_TAB_CHAR"");
  if(adi->freq<100000)
   sprintf(strtmp,"%dHz\n",adi->freq);
  else if(adi->freq<10000000)
   sprintf(strtmp,"%dkHz\n",adi->freq/1000);
  else
   sprintf(strtmp,"%2.1fMHz\n",(float)adi->freq/1000000);
  len+=pds_strcat(msg,strtmp);

  len+=pds_strcat(msg,"Channels:"DISKFILE_GUI_TAB_CHAR"");
  if(adi->channeltext){
   len+=pds_strcat(msg,adi->channeltext);
   len+=pds_strcat(msg,"\n");
  }else{
   switch(adi->filechannels){
    case 1:len+=pds_strcat(msg,"Mono\n");break;
    case 2:len+=pds_strcat(msg,"Stereo\n");break;
    default:sprintf(strtmp,"%d\n",adi->filechannels);
            len+=pds_strcat(msg,strtmp);
            break;
   }
  }

  len+=pds_strcat(msg,"Bit/rate:"DISKFILE_GUI_TAB_CHAR"");
  if(adi->bitratetext){
   len+=pds_strcat(msg,adi->bitratetext);
   len+=pds_strcat(msg,"\n");
  }else{
   if(adi->bitrate)
    sprintf(strtmp,"%d kbit/s\n",adi->bitrate);
   else
    sprintf(strtmp,"%d\n",adi->bits);
   len+=pds_strcat(msg,strtmp);
  }

 }

 len+=pds_strcat(msg,"Filesize:"DISKFILE_GUI_TAB_CHAR"");

 if(pei->filesize)
  sprintf(strtmp,"%1.2f MB",(float)pei->filesize/1048576.0);
 else if(ff_result==0)
  sprintf(strtmp,"%1.2f MB",(float)ffblk.size/1048576.0);
 else
  pds_strcpy(strtmp,"-");

 len+=pds_strcat(msg,strtmp);

 if(ff_result==0) {
  struct pds_fdate_t *fdate=&ffblk.fdate;
  sprintf(strtmp,"\nFiledate:"DISKFILE_GUI_TAB_CHAR"%4.4d.%2.2d.%2.2d. %2d:%2.2d",fdate->year+1980, fdate->month,fdate->day,fdate->hours,fdate->minutes);
  len+=pds_strcat(msg,strtmp);
#if (defined(MPXPLAY_FSIZE64) && defined(MPXPLAY_FILEDATES_ALL)) || defined(__DOS__)
#ifdef __DOS__
  if(is_lfn_support && (uselfn&USELFN_ENABLED))
#endif
  {
   struct pds_fdate_t *cdate=&ffblk.cdate;
   struct pds_fdate_t *adate=&ffblk.adate;
   if(!cdate->year && !cdate->month && !cdate->day && !cdate->hours && !cdate->minutes)
    sprintf(strtmp,"\nCreated :"DISKFILE_GUI_TAB_CHAR"-");
   else
    sprintf(strtmp,"\nCreated :"DISKFILE_GUI_TAB_CHAR"%4.4d.%2.2d.%2.2d. %2d:%2.2d",cdate->year+1980,
   cdate->month,cdate->day,cdate->hours,cdate->minutes);
   len+=pds_strcat(msg,strtmp);
   if(!adate->year && !adate->month && !adate->day && !adate->hours && !adate->minutes)
    sprintf(strtmp,"\nAccessed:"DISKFILE_GUI_TAB_CHAR"-");
   else
    sprintf(strtmp,"\nAccessed:"DISKFILE_GUI_TAB_CHAR"%4.4d.%2.2d.%2.2d. %2d:%2.2d",adate->year+1980,
   adate->month,adate->day,adate->hours,adate->minutes);
   len+=pds_strcat(msg,strtmp);
  }
#endif
  mpxplay_diskdrive_findclose(mdds_file,&ffblk);
 }

 pds_strcpy(fc->headtext," Fileinfo ");
 mpxplay_infile_close(frp);
}

static void diskfile_multifileinfos_wintext_multi(struct filecopy_t *fc,char *sout)
{
#ifdef MPXPLAY_GUI_CONSOLE
 sprintf(sout,"                        filesizes      time\n Directories:%7d\n Audio files:%7d %9.1f Mb %4d:%2.2d:%2.2d \n List files :%7d %9.1f Mb\n Other files:%7d %9.1f Mb\n All files  :%7d %9.1f Mb",
#else
 sprintf(sout," \t \tfilesizes\ttime\nDirectories:\t\r%7d\t \t \nAudio files:\t\r%7d\t\r %9.1f Mb\t\r%4d:%2.2d:%2.2d\nList files:   \t\r%7d\t\r %9.1f Mb\t \nOther files:\t\r%7d\t\r %9.1f Mb\t \nAll files:    \t\r%7d\t\r %9.1f Mb\t ",
#endif
  fc->counted_directories,
  fc->counted_filenum_media,(float)fc->counted_filesizes_media/1048576.0,
   (long)((fc->counted_timemsec_media+500)/3600000),((long)((fc->counted_timemsec_media+500)/60000))%60,((long)((fc->counted_timemsec_media+500)/1000))%60,
  fc->counted_filenum_list,(float)fc->counted_filesizes_list/1048576.0,
  fc->counted_filenum_other,
   ((float)(fc->counted_filesizes_all-fc->counted_filesizes_media-fc->counted_filesizes_list)/1048576.0),
  fc->counted_filenum_all,(float)fc->counted_filesizes_all/1048576.0);

 if(funcbit_test(fc->count_ctrl,DISKFILE_COUNTCTRL_COMPLETE))
  pds_strcpy(fc->headtext," Counted files ");
 else if(funcbit_test(fc->cpy_type,DISKFILE_CPYTYPE_INFO_SIZE))
  snprintf(fc->headtext,sizeof(fc->headtext)," Counting files (%d) ",fc->counted_filenum_media+fc->counted_filenum_list+fc->counted_filenum_other);
 else
  snprintf(fc->headtext,sizeof(fc->headtext)," Counting times (%d/%d) ",fc->counted_filenum_mtime,fc->counted_filenum_media);
}

static void diskfile_show_multifileinfos_window(struct filecopy_t *fc)
{
 char *sout;
 sout=(char *)alloca(SHOW_FILEINFOS_ALLOCLEN);
 if(!sout)
  return;

 if(fc->psi_src->selected_files || fc->pei_selected->entrytype==DFT_SUBDIR || fc->pei_selected->entrytype==DFT_UPDIR)
  diskfile_multifileinfos_wintext_multi(fc,sout);
 else
  diskfile_multifileinfos_wintext_single(fc,sout);

 if(fc->statwin_tw){
  display_textwin_draw_window_headtext(fc->statwin_tw,fc->headtext);
  display_textwin_update_msg(fc->statwin_tw,fc->count_itemnum,sout);
 }else{
  fc->statwin_tw=display_textwin_allocwindow_items(fc->statwin_tw,TEXTWIN_FLAG_MSGLEFTALIGN|TEXTWIN_FLAG_DONTCLOSE,fc->headtext,diskfile_multifileinfos_buttonhandler,fc);
  fc->count_itemnum=display_textwin_additem_msg_table(fc->statwin_tw,TEXTWIN_FLAG_MSGLEFTALIGN,0,-1,sout);
  if(fc->cpy_ctrl&DISKFILE_CPYCTRL_SHOWMULTI){
   display_textwin_additem_separatorline(fc->statwin_tw,6);
   display_textwin_additem_msg_static(fc->statwin_tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,7,"Source filter: ");
   display_textwin_additem_editline(fc->statwin_tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,sizeof("Source filter: ")-1,7,8,fc->source_filtermask,sizeof(fc->source_filtermask)-1);
   display_textwin_additem_separatorline(fc->statwin_tw,8);
   display_textwin_additem_buttons(fc->statwin_tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,9,buttons_multifileinfos,NULL);
  }
  display_textwin_openwindow_items(fc->statwin_tw,0,0,0);
 }
#ifdef MPXPLAY_FSIZE64
 if(!fc->psi_src->selected_files && (fc->pei_selected->entrytype==DFT_SUBDIR) && (funcbit_test(fc->count_ctrl,DISKFILE_COUNTCTRL_COMPLETE) || funcbit_test(fc->cpy_type,DISKFILE_CPYTYPE_INFO_TIME)))
  fc->pei_selected->filesize=(mpxp_filesize_t)fc->counted_filesizes_all;
#endif
}

void playlist_diskfile_show_multifileinfos(struct mainvars *mvp)
{
 struct filecopy_t *fc;
 struct playlist_side_info *psi;

 if(!(displaymode&DISP_FULLSCREEN))
  return;

 fc=diskfile_filecopy_alloc(mvp);
 if(!fc)
  return;

 if(!mpxplay_infile_frame_alloc(&fc->frp_src)){
  diskfile_filecopy_dealloc(fc);
  return;
 }

 psi=fc->psi_src;
 if(psi->selected_files || psi->editorhighline->entrytype==DFT_SUBDIR || psi->editorhighline->entrytype==DFT_UPDIR)
  funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_SHOWMULTI);

 if(!source_default_filter_info[0] || !pds_strlenc(source_default_filter_info,' '))
  pds_strcpy(source_default_filter_info,PDS_DIRECTORY_ALLFILE_STR);

 pds_strcpy(fc->source_filtermask,source_default_filter_info);

 diskfile_multifileinfos_start(fc);

 if(funcbit_test(fc->cpy_ctrl,DISKFILE_CPYCTRL_SHOWMULTI))
  diskfile_show_multifileinfos_window(fc);
}

//------------------------------------------------------------------------
static unsigned int diskfile_renamebyid3_createoutfilename(struct filecopy_t *fc, struct playlist_side_info *psi, struct playlist_entry_info *pei, char *outbuf, unsigned int bufsize, unsigned int cpytype_ctrl)
{
 char *shortfname;
 unsigned int tracknum=0,digits=2;
 char timestampstr[64]="";
#ifdef MPXPLAY_UTF8
 char *ext,artist[256],title[256];
#else
 char *ext,artist[128],title[128];
#endif

 if((pei<psi->firstentry) || (pei>psi->lastentry))
  return 0;

 shortfname=pds_getfilename_any_from_fullname(pei->filename);

 if(GET_HFT(pei->entrytype) == HFT_DFT)
  goto rbcf_use_filename;

 if(!(fc->cpy_ctrl & DISKFILE_CPYCTRL_STREAMSRC)){
  if(!(pei->infobits&PEIF_INDEXED) && ((pei->entrytype==DFT_NOTCHECKED) || ((loadid3tag&ID3LOADMODE_ALL)!=ID3LOADMODE_ALL))){
   playlist_chkentry_get_onefileinfos_allagain(psi,pei,&fc->frp_src,(ID3LOADMODE_ALL|(loadid3tag&ID3LOADMODE_PREFER_LIST)),MPXPLAY_INFILE_OPENMODE_CHECK);
   mpxplay_infile_close(&fc->frp_src);
  }

  if(!pei->id3info[I3I_ARTIST] && !pei->id3info[I3I_TITLE])
   goto rbcf_use_filename;

  tracknum=pds_atol(shortfname);
  if((fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE) && (!(psi->editsidetype&PLT_DIRECTORY) || psi->sublistlevel)){
   if(pei->id3info[I3I_TRACKNUM])
    tracknum=pds_atol(pei->id3info[I3I_TRACKNUM]);
   if(!tracknum)
    tracknum=pei-psi->firstsong+1;
  }
  if(tracknum){
   digits=pds_log10((long)(psi->lastentry-psi->firstsong)+1);
   if(digits<2)
    digits=2;
   else if(digits>6)
    digits=6;
  }
 }

 ext=pds_filename_get_extension_from_shortname(shortfname);
 if(!ext){
  void *mdfd = mpxplay_diskdrive_file_open(NULL, pei->filename, (O_RDONLY|O_BINARY), NULL);  // try to get file extension (content-type) from low level driver (ie:http)
  if(mdfd){
   mpxplay_diskdrive_file_config(mdfd,MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENTTYPEEXT,&ext,NULL);
   mpxplay_diskdrive_file_close(mdfd);
  }
  if(!ext)
   ext="MPG";
 }

 if(pei->id3info[I3I_ARTIST]){
  pds_strncpy(artist,pei->id3info[I3I_ARTIST],sizeof(artist)-5);
  artist[sizeof(artist)-5]=0;
  pds_strcutspc(artist);
  artist[pds_utf8_strpos(artist,60)]=0;
  pds_filename_conv_forbidden_chars(artist);
#ifndef MPXPLAY_UTF8
  mpxplay_playlist_textconv_by_texttypes(
   MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
   artist,-1,artist,sizeof(artist)-5);
#endif
 }else
  artist[0]=0;
 if(pei->id3info[I3I_TITLE] &&
    ( !(fc->cpy_ctrl & DISKFILE_CPYCTRL_STREAMSRC)
    || (cpytype_ctrl & (DISKFILE_CPYTYPE_NEWOUTBYID3 | DISKFILE_CPYTYPE_OUTCUEBYID3))
    || ((pei == fc->mvp->aktfilenum) && !(fc->cpy_type&DISKFILE_CPYTYPE_OUTCUEBYID3)) )
 ){
  pds_strncpy(title,pei->id3info[I3I_TITLE],sizeof(title)-5);
  title[sizeof(title)-5]=0;
  pds_strcutspc(title);
  title[pds_utf8_strpos(title,60)]=0;
  pds_filename_conv_forbidden_chars(title);
#ifndef MPXPLAY_UTF8
  mpxplay_playlist_textconv_by_texttypes(
   MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
   title,-1,title,sizeof(title)-5);
#endif
 }else
  title[0]=0;

 if(!artist[0] && !title[0]){
  pds_strncpy(artist, shortfname, sizeof(artist) - 1);
  artist[sizeof(artist) - 1] = 0;
 }

 if(tracknum){
  if(digits<=2)
   snprintf(outbuf,bufsize,"%2.2d. %s%s%s.%s",tracknum,&artist[0],((artist[0] && title[0])? " - ":""), &title[0], ext);
  else
   snprintf(outbuf,bufsize,"%d. %s%s%s.%s",tracknum,&artist[0],((artist[0] && title[0])? " - ":""), &title[0], ext);
 }else{
  if(cpytype_ctrl & DISKFILE_CPYTYPE_OUTTIMESTAMP){
   mpxp_uint32_t date_val = pds_getdate();
   mpxp_uint32_t time_val = pds_gettime();
   sprintf(timestampstr,"%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", (date_val >> 16), ((date_val >> 8) & 0xff), (date_val & 0xff),
		  	  	  	  	  	  	  	  	  	  	  	  	  (time_val >> 16), ((time_val >> 8) & 0xff), (time_val & 0xff));
  }
  snprintf(outbuf,bufsize,"%s%s%s%s%s.%s",&artist[0], ((cpytype_ctrl & DISKFILE_CPYTYPE_OUTTIMESTAMP)? " - ":""),
     &timestampstr[0], ((artist[0] && title[0])? " - ":""), &title[0], ext);
 }

 return 1;

rbcf_use_filename:
 pds_strncpy(outbuf,shortfname,bufsize);
 outbuf[bufsize-1]=0;
 return 1;
}

void playlist_diskfile_rename_by_id3(struct mainvars *mvp)
{
 struct filecopy_t *fc;
 struct playlist_side_info *psi=mvp->psie;
 struct playlist_entry_info *pei=psi->editorhighline;
 void *tw;
 char msg[MAX_PATHNAMELEN+64];

 if(!(displaymode&DISP_FULLSCREEN))
  return;

#ifdef __DOS__
 if(!is_lfn_support || !(uselfn&USELFN_ENABLED)){
  display_textwin_openwindow_errormsg_head_ok("Rename by ID3","Long filenames (LFN) support is missing or disabled!\n(this function cannot work without it)");
  return;
 }
#endif

 fc=diskfile_filecopy_alloc(mvp);
 if(!fc)
  return;

 fc->cpy_type=DISKFILE_CPYTYPE_MOVE|DISKFILE_CPYTYPE_RENAME; // move flag needed for correct start/init

 fc->selected_filename=pei->filename;
 if(psi->selected_files){
  funcbit_enable(fc->cpy_ctrl,DISKFILE_CPYCTRL_MULTIFILE);
  fc->pei_selected=psi->firstentry;
  fc->pei_last=psi->lastentry;
 }else
  fc->pei_selected=fc->pei_last=pei;

 if(!mpxplay_infile_frame_alloc(&fc->frp_src)){
  fc->last_error=MPXPLAY_ERROR_FILEHAND_MEMORY;
  playlist_diskfile_show_errmsg(fc);
  return;
 }

 pds_strcpy(fc->source_filtermask,PDS_DIRECTORY_ALLFILE_STR);

 if((psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel)
  pds_strcpy(fc->path_src,psi->currdir);
 pds_strcpy(fc->path_dest,fc->path_src);
 pds_strcpy(fc->outpath_root,fc->path_dest);
 fc->psi_dest=psi;
 fc->mdds_dest=psi->mdds;

 diskfile_count_files_start(fc);

 pds_strcpy(fc->headtext," Rename (by ID3) ");

 if(psi->selected_files)
  sprintf(msg,"Rename the selected %d file%s \n to \"NN. Artist - Title\" format by ID3-info %s",psi->selected_files,((psi->selected_files>1)? "s":""),((fc->path_src[0])? "in ":""));
 else
  sprintf(msg,"Rename \"%s\" to",pds_getfilename_any_from_fullname(fc->pei_selected->filename));
 pds_strcpy(fc->buttontext,"[ !!! Rename !!! ]");
 buttons_copymove_confirm[0].text=&fc->buttontext[0];
 buttons_copymove_confirm[4].extkey=0x1205; // !!! ctrl-'e' hardwired

 tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_MSGCENTERALIGN,fc->headtext,diskfile_copymove_startwindow_keyhand,fc);
 display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,msg);

 if(fc->cpy_ctrl&DISKFILE_CPYCTRL_MULTIFILE){
  funcbit_enable(fc->cpy_type,DISKFILE_CPYTYPE_RENBYID3);
  pds_strcpy(fc->outpath_argument,PDS_DIRECTORY_ALLFILE_STR);
  if(fc->path_src[0])
   display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,fc->path_src);
  else
   display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,"\n Warning! Using this function in playlist, \n if the tracknumber is missing or unknown \n the program will not write the leading NN. ! ");
 }else{
  diskfile_renamebyid3_createoutfilename(fc,psi,pei,&fc->outpath_argument[0],sizeof(fc->outpath_argument),fc->cpy_type);
  display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,1,50,fc->outpath_argument,MAX_PATHNAMELEN-2);
 }

 display_textwin_additem_separatorline(tw,-1);
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,&buttons_copymove_confirm[0],NULL);
 display_textwin_openwindow_items(tw,0,0,0);
}
