// FSFolderCopy.cpp

#include "StdAfx.h"

#include "../../../Common/MyWindows.h"

#include "../../../Common/Defs.h"
#include "../../../Common/StringConvert.h"
#include "../../../Common/Wildcard.h"

#include "../../../Windows/DLL.h"
#include "../../../Windows/ErrorMsg.h"
#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileName.h"

#include "../../Common/FilePathAutoRename.h"

#include "FSFolder.h"

using namespace NWindows;
using namespace NFile;
using namespace NDir;
using namespace NName;
using namespace NFind;

#ifndef _UNICODE
extern bool g_IsNT;
#endif

namespace NFsFolder {

static const char * const k_CannotCopyDirToAltStream = "Cannot copy folder as alternate stream";


HRESULT CCopyStateIO::MyCopyFile(CFSTR inPath, CFSTR outPath, DWORD attrib)
{
  ErrorFileIndex = -1;
  ErrorMessage.Empty();
  CurrentSize = 0;

  {
    const size_t kBufSize = 1 << 16;
    CByteArr buf(kBufSize);
    
    NIO::CInFile inFile;
    NIO::COutFile outFile;
    
    if (!inFile.Open(inPath))
    {
      ErrorFileIndex = 0;
      return S_OK;
    }
    
    if (!outFile.Create_ALWAYS(outPath))
    {
      ErrorFileIndex = 1;
      return S_OK;
    }
    
    for (;;)
    {
      UInt32 num;
      if (!inFile.Read(buf, kBufSize, num))
      {
        ErrorFileIndex = 0;
        return S_OK;
      }
      if (num == 0)
        break;
      
      UInt32 written = 0;
      if (!outFile.Write(buf, num, written))
      {
        ErrorFileIndex = 1;
        return S_OK;
      }
      if (written != num)
      {
        ErrorMessage = "Write error";
        return S_OK;
      }
      CurrentSize += num;
      if (Progress)
      {
        UInt64 completed = StartPos + CurrentSize;
        RINOK(Progress->SetCompleted(&completed))
      }
    }
  }

  /* SetFileAttrib("path:alt_stream_name") sets attributes for main file "path".
     But we don't want to change attributes of main file, when we write alt stream.
     So we need INVALID_FILE_ATTRIBUTES for alt stream here */
  
  if (attrib != INVALID_FILE_ATTRIBUTES)
    SetFileAttrib(outPath, attrib);

  if (DeleteSrcFile)
  {
    if (!DeleteFileAlways(inPath))
    {
      ErrorFileIndex = 0;
      return S_OK;
    }
  }
  
  return S_OK;
}


/*
static bool IsItWindows2000orHigher()
{
  OSVERSIONINFO versionInfo;
  versionInfo.dwOSVersionInfoSize = sizeof(versionInfo);
  if (!::GetVersionEx(&versionInfo))
    return false;
  return (versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) &&
      (versionInfo.dwMajorVersion >= 5);
}
*/

struct CProgressInfo
{
  UInt64 TotalSize;
  UInt64 StartPos;
  UInt64 FileSize;
  IProgress *Progress;
  HRESULT ProgressResult;

  void Init() { ProgressResult = S_OK; }
};

#ifndef PROGRESS_CONTINUE

#define PROGRESS_CONTINUE 0
#define PROGRESS_CANCEL 1

#define COPY_FILE_FAIL_IF_EXISTS 0x00000001

typedef
DWORD
(WINAPI* LPPROGRESS_ROUTINE)(
    LARGE_INTEGER TotalFileSize,
    LARGE_INTEGER TotalBytesTransferred,
    LARGE_INTEGER StreamSize,
    LARGE_INTEGER StreamBytesTransferred,
    DWORD dwStreamNumber,
    DWORD dwCallbackReason,
    HANDLE hSourceFile,
    HANDLE hDestinationFile,
    LPVOID lpData
    );

#endif

static DWORD CALLBACK CopyProgressRoutine(
  LARGE_INTEGER TotalFileSize,          // file size
  LARGE_INTEGER TotalBytesTransferred,  // bytes transferred
  LARGE_INTEGER /* StreamSize */,             // bytes in stream
  LARGE_INTEGER /* StreamBytesTransferred */, // bytes transferred for stream
  DWORD /* dwStreamNumber */,                 // current stream
  DWORD /* dwCallbackReason */,               // callback reason
  HANDLE /* hSourceFile */,                   // handle to source file
  HANDLE /* hDestinationFile */,              // handle to destination file
  LPVOID lpData                         // from CopyFileEx
)
{
  // StreamSize = StreamSize;
  // StreamBytesTransferred = StreamBytesTransferred;
  // dwStreamNumber = dwStreamNumber;
  // dwCallbackReason = dwCallbackReason;

  CProgressInfo &pi = *(CProgressInfo *)lpData;

  if ((UInt64)TotalFileSize.QuadPart > pi.FileSize)
  {
    pi.TotalSize += (UInt64)TotalFileSize.QuadPart - pi.FileSize;
    pi.FileSize = (UInt64)TotalFileSize.QuadPart;
    pi.ProgressResult = pi.Progress->SetTotal(pi.TotalSize);
  }
  const UInt64 completed = pi.StartPos + (UInt64)TotalBytesTransferred.QuadPart;
  pi.ProgressResult = pi.Progress->SetCompleted(&completed);
  return (pi.ProgressResult == S_OK ? PROGRESS_CONTINUE : PROGRESS_CANCEL);
}

#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0500  // win2000
#define Z7_USE_DYN_MoveFileWithProgressW
#endif

#ifdef Z7_USE_DYN_MoveFileWithProgressW
// nt4
typedef BOOL (WINAPI * Func_CopyFileExA)(
    IN LPCSTR lpExistingFileName,
    IN LPCSTR lpNewFileName,
    IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
    IN LPVOID lpData OPTIONAL,
    IN LPBOOL pbCancel OPTIONAL,
    IN DWORD dwCopyFlags
    );

// nt4
typedef BOOL (WINAPI * Func_CopyFileExW)(
    IN LPCWSTR lpExistingFileName,
    IN LPCWSTR lpNewFileName,
    IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
    IN LPVOID lpData OPTIONAL,
    IN LPBOOL pbCancel OPTIONAL,
    IN DWORD dwCopyFlags
    );

// win2000
typedef BOOL (WINAPI * Func_MoveFileWithProgressW)(
    IN LPCWSTR lpExistingFileName,
    IN LPCWSTR lpNewFileName,
    IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
    IN LPVOID lpData OPTIONAL,
    IN DWORD dwFlags
    );
#endif

struct CCopyState
{
  CProgressInfo ProgressInfo;
  IFolderOperationsExtractCallback *Callback;
  bool MoveMode;
  bool UseReadWriteMode;
  bool IsAltStreamsDest;

#ifdef Z7_USE_DYN_MoveFileWithProgressW
private:
  Func_CopyFileExW my_CopyFileExW;
  #ifndef UNDER_CE
  Func_MoveFileWithProgressW my_MoveFileWithProgressW;
  #endif
  #ifndef _UNICODE
  Func_CopyFileExA my_CopyFileExA;
  #endif
public:
  CCopyState();
#endif

  bool CopyFile_NT(const wchar_t *oldFile, const wchar_t *newFile);
  bool CopyFile_Sys(CFSTR oldFile, CFSTR newFile);
  bool MoveFile_Sys(CFSTR oldFile, CFSTR newFile);

  HRESULT CallProgress();

  bool IsCallbackProgressError() { return ProgressInfo.ProgressResult != S_OK; }
};

HRESULT CCopyState::CallProgress()
{
  return ProgressInfo.Progress->SetCompleted(&ProgressInfo.StartPos);
}

#ifdef Z7_USE_DYN_MoveFileWithProgressW

CCopyState::CCopyState()
{
  my_CopyFileExW = NULL;
  #ifndef UNDER_CE
  my_MoveFileWithProgressW = NULL;
  #endif
  #ifndef _UNICODE
  my_CopyFileExA = NULL;
  if (!g_IsNT)
  {
      my_CopyFileExA = Z7_GET_PROC_ADDRESS(
    Func_CopyFileExA, ::GetModuleHandleA("kernel32.dll"),
        "CopyFileExA");
  }
  else
  #endif
  {
    const HMODULE module = ::GetModuleHandleW(
      #ifdef UNDER_CE
        L"coredll.dll"
      #else
        L"kernel32.dll"
      #endif
        );
      my_CopyFileExW = Z7_GET_PROC_ADDRESS(
    Func_CopyFileExW, module,
        "CopyFileExW");
    #ifndef UNDER_CE
      my_MoveFileWithProgressW = Z7_GET_PROC_ADDRESS(
    Func_MoveFileWithProgressW, module,
        "MoveFileWithProgressW");
    #endif
  }
}

#endif

/* WinXP-64:
  CopyFileW(fromFile, toFile:altStream)
    OK                       - there are NO alt streams in fromFile
    ERROR_INVALID_PARAMETER  - there are    alt streams in fromFile
*/

bool CCopyState::CopyFile_NT(const wchar_t *oldFile, const wchar_t *newFile)
{
  BOOL cancelFlag = FALSE;
#ifdef Z7_USE_DYN_MoveFileWithProgressW
  if (my_CopyFileExW)
#endif
    return BOOLToBool(
#ifdef Z7_USE_DYN_MoveFileWithProgressW
      my_CopyFileExW
#else
         CopyFileExW
#endif
      (oldFile, newFile, CopyProgressRoutine,
        &ProgressInfo, &cancelFlag, COPY_FILE_FAIL_IF_EXISTS));
#ifdef Z7_USE_DYN_MoveFileWithProgressW
  return BOOLToBool(::CopyFileW(oldFile, newFile, TRUE));
#endif
}

bool CCopyState::CopyFile_Sys(CFSTR oldFile, CFSTR newFile)
{
  #ifndef _UNICODE
  if (!g_IsNT)
  {
#ifdef Z7_USE_DYN_MoveFileWithProgressW
    if (my_CopyFileExA)
#endif
    {
      BOOL cancelFlag = FALSE;
      if (
#ifdef Z7_USE_DYN_MoveFileWithProgressW
          my_CopyFileExA
#else
             CopyFileExA
#endif
          (fs2fas(oldFile), fs2fas(newFile),
          CopyProgressRoutine, &ProgressInfo, &cancelFlag, COPY_FILE_FAIL_IF_EXISTS))
        return true;
      if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
        return false;
    }
    return BOOLToBool(::CopyFile(fs2fas(oldFile), fs2fas(newFile), TRUE));
  }
  else
  #endif
  {
    IF_USE_MAIN_PATH_2(oldFile, newFile)
    {
      if (CopyFile_NT(fs2us(oldFile), fs2us(newFile)))
        return true;
    }
    #ifdef Z7_LONG_PATH
    if (USE_SUPER_PATH_2)
    {
      if (IsCallbackProgressError())
        return false;
      UString superPathOld, superPathNew;
      if (!GetSuperPaths(oldFile, newFile, superPathOld, superPathNew, USE_MAIN_PATH_2))
        return false;
      if (CopyFile_NT(superPathOld, superPathNew))
        return true;
    }
    #endif
    return false;
  }
}

bool CCopyState::MoveFile_Sys(CFSTR oldFile, CFSTR newFile)
{
  #ifndef UNDER_CE
  // if (IsItWindows2000orHigher())
  // {
#ifdef Z7_USE_DYN_MoveFileWithProgressW
    if (my_MoveFileWithProgressW)
#endif
    {
      IF_USE_MAIN_PATH_2(oldFile, newFile)
      {
        if (
#ifdef Z7_USE_DYN_MoveFileWithProgressW
          my_MoveFileWithProgressW
#else
             MoveFileWithProgressW
#endif
          (fs2us(oldFile), fs2us(newFile), CopyProgressRoutine,
            &ProgressInfo, MOVEFILE_COPY_ALLOWED))
          return true;
      }
      #ifdef Z7_LONG_PATH
      if ((!(USE_MAIN_PATH_2) || ::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) && USE_SUPER_PATH_2)
      {
        if (IsCallbackProgressError())
          return false;
        UString superPathOld, superPathNew;
        if (!GetSuperPaths(oldFile, newFile, superPathOld, superPathNew, USE_MAIN_PATH_2))
          return false;
        if (
#ifdef Z7_USE_DYN_MoveFileWithProgressW
          my_MoveFileWithProgressW
#else
             MoveFileWithProgressW
#endif
            (superPathOld, superPathNew, CopyProgressRoutine,
            &ProgressInfo, MOVEFILE_COPY_ALLOWED))
          return true;
      }
      #endif
      if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
        return false;
    }
  // }
  // else
  #endif
    return MyMoveFile(oldFile, newFile);
}

static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback,
    const wchar_t *message, const FString &fileName)
{
  UString s = message;
  s += " : ";
  s += fs2us(fileName);
  return callback->ShowMessage(s);
}

static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback,
    const char *message, const FString &fileName)
{
  return SendMessageError(callback, MultiByteToUnicodeString(message), fileName);
}

static DWORD Return_LastError_or_FAIL()
{
  DWORD errorCode = GetLastError();
  if (errorCode == 0)
    errorCode = (DWORD)E_FAIL;
  return errorCode;
}

static UString GetLastErrorMessage()
{
  return NError::MyFormatMessage(Return_LastError_or_FAIL());
}

HRESULT SendLastErrorMessage(IFolderOperationsExtractCallback *callback, const FString &fileName)
{
  return SendMessageError(callback, GetLastErrorMessage(), fileName);
}

static HRESULT CopyFile_Ask(
    CCopyState &state,
    const FString &srcPath,
    const CFileInfo &srcFileInfo,
    const FString &destPath)
{
  if (CompareFileNames(destPath, srcPath) == 0)
  {
    RINOK(SendMessageError(state.Callback,
        state.MoveMode ?
          "Cannot move file onto itself" :
          "Cannot copy file onto itself"
        , destPath))
    return E_ABORT;
  }

  Int32 writeAskResult;
  CMyComBSTR destPathResult;
  RINOK(state.Callback->AskWrite(
      fs2us(srcPath),
      BoolToInt(false),
      &srcFileInfo.MTime, &srcFileInfo.Size,
      fs2us(destPath),
      &destPathResult,
      &writeAskResult))
  
  if (IntToBool(writeAskResult))
  {
    FString destPathNew = us2fs((LPCOLESTR)destPathResult);
    RINOK(state.Callback->SetCurrentFilePath(fs2us(srcPath)))

    if (state.UseReadWriteMode)
    {
      NFsFolder::CCopyStateIO state2;
      state2.Progress = state.Callback;
      state2.DeleteSrcFile = state.MoveMode;
      state2.TotalSize = state.ProgressInfo.TotalSize;
      state2.StartPos = state.ProgressInfo.StartPos;

      RINOK(state2.MyCopyFile(srcPath, destPathNew,
          state.IsAltStreamsDest ? INVALID_FILE_ATTRIBUTES: srcFileInfo.Attrib))
      
      if (state2.ErrorFileIndex >= 0)
      {
        if (state2.ErrorMessage.IsEmpty())
          state2.ErrorMessage = GetLastErrorMessage();
        FString errorName;
        if (state2.ErrorFileIndex == 0)
          errorName = srcPath;
        else
          errorName = destPathNew;
        RINOK(SendMessageError(state.Callback, state2.ErrorMessage, errorName))
        return E_ABORT;
      }
      state.ProgressInfo.StartPos += state2.CurrentSize;
    }
    else
    {
      state.ProgressInfo.FileSize = srcFileInfo.Size;
      bool res;
      if (state.MoveMode)
        res = state.MoveFile_Sys(srcPath, destPathNew);
      else
        res = state.CopyFile_Sys(srcPath, destPathNew);
      RINOK(state.ProgressInfo.ProgressResult)
      if (!res)
      {
        const DWORD errorCode = GetLastError();
        UString errorMessage = NError::MyFormatMessage(Return_LastError_or_FAIL());
        if (errorCode == ERROR_INVALID_PARAMETER)
        {
          NFind::CFileInfo fi;
          if (fi.Find(srcPath) &&
              fi.Size > (UInt32)(Int32)-1)
          {
            // bool isFsDetected = false;
            // if (NSystem::Is_File_LimitedBy_4GB(destPathNew, isFsDetected) || !isFsDetected)
              errorMessage += " File size exceeds 4 GB";
          }
        }

        // GetLastError() is ERROR_REQUEST_ABORTED in case of PROGRESS_CANCEL.
        RINOK(SendMessageError(state.Callback, errorMessage, destPathNew))
        return E_ABORT;
      }
      state.ProgressInfo.StartPos += state.ProgressInfo.FileSize;
    }
  }
  else
  {
    if (state.ProgressInfo.TotalSize >= srcFileInfo.Size)
    {
      state.ProgressInfo.TotalSize -= srcFileInfo.Size;
      RINOK(state.ProgressInfo.Progress->SetTotal(state.ProgressInfo.TotalSize))
    }
  }
  return state.CallProgress();
}

static FString CombinePath(const FString &folderPath, const FString &fileName)
{
  FString s (folderPath);
  s.Add_PathSepar(); // FCHAR_PATH_SEPARATOR
  s += fileName;
  return s;
}

static bool IsDestChild(const FString &src, const FString &dest)
{
  unsigned len = src.Len();
  if (dest.Len() < len)
    return false;
  if (dest.Len() != len && dest[len] != FCHAR_PATH_SEPARATOR)
    return false;
  return CompareFileNames(dest.Left(len), src) == 0;
}

static HRESULT CopyFolder(
    CCopyState &state,
    const FString &srcPath,   // without TAIL separator
    const FString &destPath)  // without TAIL separator
{
  RINOK(state.CallProgress())

  if (IsDestChild(srcPath, destPath))
  {
    RINOK(SendMessageError(state.Callback,
        state.MoveMode ?
          "Cannot copy folder onto itself" :
          "Cannot move folder onto itself"
        , destPath))
    return E_ABORT;
  }

  if (state.MoveMode)
  {
    if (state.MoveFile_Sys(srcPath, destPath))
      return S_OK;

    // MSDN: MoveFile() fails for dirs on different volumes.
  }

  if (!CreateComplexDir(destPath))
  {
    RINOK(SendMessageError(state.Callback, "Cannot create folder", destPath))
    return E_ABORT;
  }

  CEnumerator enumerator;
  enumerator.SetDirPrefix(CombinePath(srcPath, FString()));
  
  for (;;)
  {
    NFind::CFileInfo fi;
    bool found;
    if (!enumerator.Next(fi, found))
    {
      SendLastErrorMessage(state.Callback, srcPath);
      return S_OK;
    }
    if (!found)
      break;
    const FString srcPath2 = CombinePath(srcPath, fi.Name);
    const FString destPath2 = CombinePath(destPath, fi.Name);
    if (fi.IsDir())
    {
      RINOK(CopyFolder(state, srcPath2, destPath2))
    }
    else
    {
      RINOK(CopyFile_Ask(state, srcPath2, fi, destPath2))
    }
  }

  if (state.MoveMode)
  {
    if (!RemoveDir(srcPath))
    {
      RINOK(SendMessageError(state.Callback, "Cannot remove folder", srcPath))
      return E_ABORT;
    }
  }
  
  return S_OK;
}

Z7_COM7F_IMF(CFSFolder::CopyTo(Int32 moveMode, const UInt32 *indices, UInt32 numItems,
    Int32 /* includeAltStreams */, Int32 /* replaceAltStreamColon */,
    const wchar_t *path, IFolderOperationsExtractCallback *callback))
{
  if (numItems == 0)
    return S_OK;

  const FString destPath = us2fs(path);
  if (destPath.IsEmpty())
    return E_INVALIDARG;

  const bool isAltDest = NName::IsAltPathPrefix(destPath);
  const bool isDirectPath = (!isAltDest && !IsPathSepar(destPath.Back()));

  if (isDirectPath)
    if (numItems > 1)
      return E_INVALIDARG;

  CFsFolderStat stat;
  stat.Progress = callback;

  UInt32 i;
  for (i = 0; i < numItems; i++)
  {
    const UInt32 index = indices[i];
    /*
    if (index >= Files.Size())
    {
      size += Streams[index - Files.Size()].Size;
      // numFiles++;
      continue;
    }
    */
    const CDirItem &fi = Files[index];
    if (fi.IsDir())
    {
      if (!isAltDest)
      {
        stat.Path = _path;
        stat.Path += GetRelPath(fi);
        RINOK(stat.Enumerate())
      }
      stat.NumFolders++;
    }
    else
    {
      stat.NumFiles++;
      stat.Size += fi.Size;
    }
  }

  /*
  if (stat.NumFolders != 0 && isAltDest)
    return E_NOTIMPL;
  */

  RINOK(callback->SetTotal(stat.Size))
  RINOK(callback->SetNumFiles(stat.NumFiles))

  UInt64 completedSize = 0;
  RINOK(callback->SetCompleted(&completedSize))

  CCopyState state;
  state.ProgressInfo.TotalSize = stat.Size;
  state.ProgressInfo.StartPos = 0;
  state.ProgressInfo.Progress = callback;
  state.ProgressInfo.Init();
  state.Callback = callback;
  state.MoveMode = IntToBool(moveMode);
  state.IsAltStreamsDest = isAltDest;
  /* CopyFileW(fromFile, toFile:altStream) returns ERROR_INVALID_PARAMETER,
       if there are alt streams in fromFile.
     So we don't use CopyFileW() for alt Streams. */
  state.UseReadWriteMode = isAltDest;

  for (i = 0; i < numItems; i++)
  {
    const UInt32 index = indices[i];
    if (index >= (UInt32)Files.Size())
      continue;
    const CDirItem &fi = Files[index];
    FString destPath2 = destPath;
    if (!isDirectPath)
      destPath2 += fi.Name;
    FString srcPath;
    GetFullPath(fi, srcPath);
  
    if (fi.IsDir())
    {
      if (isAltDest)
      {
        RINOK(SendMessageError(callback, k_CannotCopyDirToAltStream, srcPath))
      }
      else
      {
        RINOK(CopyFolder(state, srcPath, destPath2))
      }
    }
    else
    {
      RINOK(CopyFile_Ask(state, srcPath, fi, destPath2))
    }
  }
  return S_OK;
}



/* we can call CopyFileSystemItems() from CDropTarget::Drop() */

HRESULT CopyFileSystemItems(
    const UStringVector &itemsPaths,
    const FString &destDirPrefix,
    bool moveMode,
    IFolderOperationsExtractCallback *callback)
{
  if (itemsPaths.IsEmpty())
    return S_OK;

  if (destDirPrefix.IsEmpty())
    return E_INVALIDARG;

  const bool isAltDest = NName::IsAltPathPrefix(destDirPrefix);

  CFsFolderStat stat;
  stat.Progress = callback;

 {
  FOR_VECTOR (i, itemsPaths)
  {
    const UString &path = itemsPaths[i];
    CFileInfo fi;
    if (!fi.Find(us2fs(path)))
      continue;
    if (fi.IsDir())
    {
      if (!isAltDest)
      {
        stat.Path = us2fs(path);
        RINOK(stat.Enumerate())
      }
      stat.NumFolders++;
    }
    else
    {
      stat.NumFiles++;
      stat.Size += fi.Size;
    }
  }
 }

  /*
  if (stat.NumFolders != 0 && isAltDest)
    return E_NOTIMPL;
  */

  RINOK(callback->SetTotal(stat.Size))
  // RINOK(progress->SetNumFiles(stat.NumFiles));

  UInt64 completedSize = 0;
  RINOK(callback->SetCompleted(&completedSize))

  CCopyState state;
  state.ProgressInfo.TotalSize = stat.Size;
  state.ProgressInfo.StartPos = 0;
  state.ProgressInfo.Progress = callback;
  state.ProgressInfo.Init();
  state.Callback = callback;
  state.MoveMode = moveMode;
  state.IsAltStreamsDest = isAltDest;
  /* CopyFileW(fromFile, toFile:altStream) returns ERROR_INVALID_PARAMETER,
       if there are alt streams in fromFile.
     So we don't use CopyFileW() for alt Streams. */
  state.UseReadWriteMode = isAltDest;

  FOR_VECTOR (i, itemsPaths)
  {
    const UString path = itemsPaths[i];
    CFileInfo fi;
  
    if (!fi.Find(us2fs(path)))
    {
      RINOK(SendMessageError(callback, "Cannot find the file", us2fs(path)))
      continue;
    }

    FString destPath = destDirPrefix;
    destPath += fi.Name;
  
    if (fi.IsDir())
    {
      if (isAltDest)
      {
        RINOK(SendMessageError(callback, k_CannotCopyDirToAltStream, us2fs(path)))
      }
      else
      {
        RINOK(CopyFolder(state, us2fs(path), destPath))
      }
    }
    else
    {
      RINOK(CopyFile_Ask(state, us2fs(path), fi, destPath))
    }
  }
  return S_OK;
}


/* we don't use CFSFolder::CopyFrom() because the caller of CopyFrom()
   is optimized for IFolderArchiveUpdateCallback interface,
   but we want to use IFolderOperationsExtractCallback interface instead */

Z7_COM7F_IMF(CFSFolder::CopyFrom(Int32 /* moveMode */, const wchar_t * /* fromFolderPath */,
    const wchar_t * const * /* itemsPaths */, UInt32 /* numItems */, IProgress * /* progress */))
{
  /*
  Z7_DECL_CMyComPtr_QI_FROM(
      IFolderOperationsExtractCallback,
      callback, progress)
  if (!callback)
    return E_NOTIMPL;
  return CopyFileSystemItems(_path,
      moveMode, fromDirPrefix,
      itemsPaths, numItems, callback);
  */
  return E_NOTIMPL;
}

Z7_COM7F_IMF(CFSFolder::CopyFromFile(UInt32 /* index */, const wchar_t * /* fullFilePath */, IProgress * /* progress */))
{
  return E_NOTIMPL;
}

}
