// AltStreamsFolder.cpp

#include "StdAfx.h"

#ifdef __MINGW32_VERSION
// #if !defined(_MSC_VER) && (__GNUC__) && (__GNUC__ < 10)
// for old mingw
#include <ddk/ntddk.h>
#else
#ifndef Z7_OLD_WIN_SDK
  #if !defined(_M_IA64)
    #include <winternl.h>
  #endif
#else
typedef LONG NTSTATUS;
typedef struct _IO_STATUS_BLOCK {
    union {
        NTSTATUS Status;
        PVOID Pointer;
    };
    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
#endif
#endif

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

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

#include "../Common/ExtractingFilePath.h"

#include "../Agent/IFolderArchive.h"

#include "AltStreamsFolder.h"
#include "FSDrives.h"
#include "FSFolder.h"

#include "SysIconUtils.h"

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

#ifndef USE_UNICODE_FSTRING
int CompareFileNames_ForFolderList(const FChar *s1, const FChar *s2);
#endif

#ifndef UNDER_CE

namespace NFsFolder
{
bool MyGetCompressedFileSizeW(CFSTR path, UInt64 &size);
}

#endif

namespace NAltStreamsFolder {

static const Byte kProps[] =
{
  kpidName,
  kpidSize,
  kpidPackSize
};

static unsigned GetFsParentPrefixSize(const FString &path)
{
  if (IsNetworkShareRootPath(path))
    return 0;
  const unsigned prefixSize = GetRootPrefixSize(path);
  if (prefixSize == 0 || prefixSize >= path.Len())
    return 0;
  FString parentPath = path;
  int pos = parentPath.ReverseFind_PathSepar();
  if (pos < 0)
    return 0;
  if (pos == (int)parentPath.Len() - 1)
  {
    parentPath.DeleteBack();
    pos = parentPath.ReverseFind_PathSepar();
    if (pos < 0)
      return 0;
  }
  if ((unsigned)pos + 1 < prefixSize)
    return 0;
  return (unsigned)pos + 1;
}

HRESULT CAltStreamsFolder::Init(const FString &path /* , IFolderFolder *parentFolder */)
{
   // _parentFolder = parentFolder;
   if (path.Back() != ':')
     return E_FAIL;

  _pathPrefix = path;
  _pathBaseFile = path;
  _pathBaseFile.DeleteBack();

  {
    CFileInfo fi;
    if (!fi.Find(_pathBaseFile))
      return GetLastError_noZero_HRESULT();
  }

  unsigned prefixSize = GetFsParentPrefixSize(_pathBaseFile);
  if (prefixSize == 0)
    return S_OK;
  FString parentPath = _pathBaseFile;
  parentPath.DeleteFrom(prefixSize);

  _findChangeNotification.FindFirst(parentPath, false,
        FILE_NOTIFY_CHANGE_FILE_NAME
      | FILE_NOTIFY_CHANGE_DIR_NAME
      | FILE_NOTIFY_CHANGE_ATTRIBUTES
      | FILE_NOTIFY_CHANGE_SIZE
      | FILE_NOTIFY_CHANGE_LAST_WRITE
      /*
      | FILE_NOTIFY_CHANGE_LAST_ACCESS
      | FILE_NOTIFY_CHANGE_CREATION
      | FILE_NOTIFY_CHANGE_SECURITY
      */
      );
  /*
  if (_findChangeNotification.IsHandleAllocated())
    return S_OK;
  return GetLastError();
  */
  return S_OK;
}

Z7_COM7F_IMF(CAltStreamsFolder::LoadItems())
{
  Int32 dummy;
  WasChanged(&dummy);
  Clear();

  CStreamEnumerator enumerator(_pathBaseFile);

  CStreamInfo si;
  for (;;)
  {
    bool found;
    if (!enumerator.Next(si, found))
    {
      // if (GetLastError() == ERROR_ACCESS_DENIED)
      //   break;
      // return E_FAIL;
      break;
    }
    if (!found)
      break;
    if (si.IsMainStream())
      continue;
    CAltStream ss;
    ss.Name = si.GetReducedName();
    if (!ss.Name.IsEmpty() && ss.Name[0] == ':')
      ss.Name.Delete(0);

    ss.Size = si.Size;
    ss.PackSize_Defined = false;
    ss.PackSize = si.Size;
    Streams.Add(ss);
  }

  return S_OK;
}

Z7_COM7F_IMF(CAltStreamsFolder::GetNumberOfItems(UInt32 *numItems))
{
  *numItems = Streams.Size();
  return S_OK;
}

#ifdef USE_UNICODE_FSTRING

Z7_COM7F_IMF(CAltStreamsFolder::GetItemPrefix(UInt32 /* index */, const wchar_t **name, unsigned *len))
{
  *name = NULL;
  *len = 0;
  return S_OK;
}

Z7_COM7F_IMF(CAltStreamsFolder::GetItemName(UInt32 index, const wchar_t **name, unsigned *len))
{
  *name = NULL;
  *len = 0;
  {
    const CAltStream &ss = Streams[index];
    *name = ss.Name;
    *len = ss.Name.Len();
  }
  return S_OK;
}

Z7_COM7F_IMF2(UInt64, CAltStreamsFolder::GetItemSize(UInt32 index))
{
  return Streams[index].Size;
}

#endif


Z7_COM7F_IMF(CAltStreamsFolder::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
{
  NCOM::CPropVariant prop;
  {
    CAltStream &ss = Streams[index];
    switch (propID)
    {
      case kpidIsDir: prop = false; break;
      case kpidIsAltStream: prop = true; break;
      case kpidName: prop = ss.Name; break;
      case kpidSize: prop = ss.Size; break;
      case kpidPackSize:
        #ifdef UNDER_CE
        prop = ss.Size;
        #else
        if (!ss.PackSize_Defined)
        {
          ss.PackSize_Defined = true;
          if (!NFsFolder::MyGetCompressedFileSizeW(_pathPrefix + us2fs(ss.Name), ss.PackSize))
            ss.PackSize = ss.Size;
        }
        prop = ss.PackSize;
        #endif
        break;
    }
  }

  prop.Detach(value);
  return S_OK;
}


// returns Position of extension including '.'

static inline const wchar_t *GetExtensionPtr(const UString &name)
{
  const int dotPos = name.ReverseFind_Dot();
  return name.Ptr(dotPos < 0 ? name.Len() : (unsigned)dotPos);
}

Z7_COM7F_IMF2(Int32, CAltStreamsFolder::CompareItems(UInt32 index1, UInt32 index2, PROPID propID, Int32 /* propIsRaw */))
{
  const CAltStream &ss1 = Streams[index1];
  const CAltStream &ss2 = Streams[index2];

  switch (propID)
  {
    case kpidName:
    {
      return CompareFileNames_ForFolderList(ss1.Name, ss2.Name);
      // return MyStringCompareNoCase(ss1.Name, ss2.Name);
    }
    case kpidSize:
      return MyCompare(ss1.Size, ss2.Size);
    case kpidPackSize:
    {
      #ifdef UNDER_CE
      return MyCompare(ss1.Size, ss2.Size);
      #else
      // PackSize can be undefined here
      return MyCompare(
          ss1.PackSize,
          ss2.PackSize);
      #endif
    }
    
    case kpidExtension:
      return CompareFileNames_ForFolderList(
          GetExtensionPtr(ss1.Name),
          GetExtensionPtr(ss2.Name));
  }
  
  return 0;
}

Z7_COM7F_IMF(CAltStreamsFolder::BindToFolder(UInt32 /* index */, IFolderFolder **resultFolder))
{
  *resultFolder = NULL;
  return E_INVALIDARG;
}

Z7_COM7F_IMF(CAltStreamsFolder::BindToFolder(const wchar_t * /* name */, IFolderFolder **resultFolder))
{
  *resultFolder = NULL;
  return E_INVALIDARG;
}

// static CFSTR const kSuperPrefix = FTEXT("\\\\?\\");

Z7_COM7F_IMF(CAltStreamsFolder::BindToParentFolder(IFolderFolder **resultFolder))
{
  *resultFolder = NULL;
  /*
  if (_parentFolder)
  {
    CMyComPtr<IFolderFolder> parentFolder = _parentFolder;
    *resultFolder = parentFolder.Detach();
    return S_OK;
  }
  */

  if (IsDriveRootPath_SuperAllowed(_pathBaseFile))
  {
    CFSDrives *drivesFolderSpec = new CFSDrives;
    CMyComPtr<IFolderFolder> drivesFolder = drivesFolderSpec;
    drivesFolderSpec->Init();
    *resultFolder = drivesFolder.Detach();
    return S_OK;
  }
  
  /*
  parentPath.DeleteFrom(pos + 1);
  
  if (parentPath == kSuperPrefix)
  {
    #ifdef UNDER_CE
    *resultFolder = 0;
    #else
    CFSDrives *drivesFolderSpec = new CFSDrives;
    CMyComPtr<IFolderFolder> drivesFolder = drivesFolderSpec;
    drivesFolderSpec->Init(false, true);
    *resultFolder = drivesFolder.Detach();
    #endif
    return S_OK;
  }

  FString parentPathReduced = parentPath.Left(pos);
  
  #ifndef UNDER_CE
  pos = parentPathReduced.ReverseFind_PathSepar();
  if (pos == 1)
  {
    if (!IS_PATH_SEPAR_CHAR(parentPath[0]))
      return E_FAIL;
    CNetFolder *netFolderSpec = new CNetFolder;
    CMyComPtr<IFolderFolder> netFolder = netFolderSpec;
    netFolderSpec->Init(fs2us(parentPath));
    *resultFolder = netFolder.Detach();
    return S_OK;
  }
  #endif
  
  CFSFolder *parentFolderSpec = new CFSFolder;
  CMyComPtr<IFolderFolder> parentFolder = parentFolderSpec;
  RINOK(parentFolderSpec->Init(parentPath, 0));
  *resultFolder = parentFolder.Detach();
  */

  return S_OK;
}

IMP_IFolderFolder_Props(CAltStreamsFolder)

Z7_COM7F_IMF(CAltStreamsFolder::GetFolderProperty(PROPID propID, PROPVARIANT *value))
{
  COM_TRY_BEGIN
  NWindows::NCOM::CPropVariant prop;
  switch (propID)
  {
    case kpidType: prop = "AltStreamsFolder"; break;
    case kpidPath: prop = fs2us(_pathPrefix); break;
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

Z7_COM7F_IMF(CAltStreamsFolder::WasChanged(Int32 *wasChanged))
{
  bool wasChangedMain = false;
  for (;;)
  {
    if (!_findChangeNotification.IsHandleAllocated())
    {
      *wasChanged = BoolToInt(false);
      return S_OK;
    }

    DWORD waitResult = ::WaitForSingleObject(_findChangeNotification, 0);
    bool wasChangedLoc = (waitResult == WAIT_OBJECT_0);
    if (wasChangedLoc)
    {
      _findChangeNotification.FindNext();
      wasChangedMain = true;
    }
    else
      break;
  }
  *wasChanged = BoolToInt(wasChangedMain);
  return S_OK;
}
 
Z7_COM7F_IMF(CAltStreamsFolder::Clone(IFolderFolder **resultFolder))
{
  CAltStreamsFolder *folderSpec = new CAltStreamsFolder;
  CMyComPtr<IFolderFolder> folderNew = folderSpec;
  folderSpec->Init(_pathPrefix);
  *resultFolder = folderNew.Detach();
  return S_OK;
}

void CAltStreamsFolder::GetAbsPath(const wchar_t *name, FString &absPath)
{
  absPath.Empty();
  if (!IsAbsolutePath(name))
    absPath += _pathPrefix;
  absPath += us2fs(name);
}



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(IFolderArchiveUpdateCallback *callback,
    const wchar_t *message, const FString &fileName)
{
  UString s = message;
  s += " : ";
  s += fs2us(fileName);
  return callback->UpdateErrorMessage(s);
}

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

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

Z7_COM7F_IMF(CAltStreamsFolder::CreateFolder(const wchar_t * /* name */, IProgress * /* progress */))
{
  return E_NOTIMPL;
}

Z7_COM7F_IMF(CAltStreamsFolder::CreateFile(const wchar_t *name, IProgress * /* progress */))
{
  FString absPath;
  GetAbsPath(name, absPath);
  NIO::COutFile outFile;
  if (!outFile.Create_NEW(absPath))
    return GetLastError_noZero_HRESULT();
  return S_OK;
}

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

static HRESULT UpdateFile(NFsFolder::CCopyStateIO &state, CFSTR inPath, CFSTR outPath, IFolderArchiveUpdateCallback *callback)
{
  if (NFind::DoesFileOrDirExist(outPath))
  {
    RINOK(SendMessageError(callback, NError::MyFormatMessage(ERROR_ALREADY_EXISTS), FString(outPath)))
    CFileInfo fi;
    if (fi.Find(inPath))
    {
      if (state.TotalSize >= fi.Size)
        state.TotalSize -= fi.Size;
    }
    return S_OK;
  }

  {
    if (callback)
      RINOK(callback->CompressOperation(fs2us(inPath)))
    RINOK(state.MyCopyFile(inPath, outPath))
    if (state.ErrorFileIndex >= 0)
    {
      if (state.ErrorMessage.IsEmpty())
        state.ErrorMessage = GetLastErrorMessage();
      FString errorName;
      if (state.ErrorFileIndex == 0)
        errorName = inPath;
      else
        errorName = outPath;
      if (callback)
        RINOK(SendMessageError(callback, state.ErrorMessage, errorName))
    }
    if (callback)
      RINOK(callback->OperationResult(0))
  }

  return S_OK;
}

EXTERN_C_BEGIN

typedef enum
{
  Z7_WIN_FileRenameInformation = 10
}
Z7_WIN_FILE_INFORMATION_CLASS;


typedef struct
{
  // #if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS1)
  union
  {
    BOOLEAN ReplaceIfExists;  // FileRenameInformation
    ULONG Flags;              // FileRenameInformationEx
  } DUMMYUNIONNAME;
  // #else
  // BOOLEAN ReplaceIfExists;
  // #endif
  HANDLE RootDirectory;
  ULONG FileNameLength;
  WCHAR FileName[1];
} Z7_WIN_FILE_RENAME_INFORMATION;

#if (_WIN32_WINNT >= 0x0500) && !defined(_M_IA64)
#define Z7_WIN_NTSTATUS  NTSTATUS
#define Z7_WIN_IO_STATUS_BLOCK  IO_STATUS_BLOCK
#else
typedef LONG Z7_WIN_NTSTATUS;
typedef struct
{
  union
  {
    Z7_WIN_NTSTATUS Status;
    PVOID Pointer;
  } DUMMYUNIONNAME;
  ULONG_PTR Information;
} Z7_WIN_IO_STATUS_BLOCK;
#endif

typedef Z7_WIN_NTSTATUS (WINAPI *Func_NtSetInformationFile)(
    HANDLE FileHandle,
    Z7_WIN_IO_STATUS_BLOCK *IoStatusBlock,
    PVOID FileInformation,
    ULONG Length,
    Z7_WIN_FILE_INFORMATION_CLASS FileInformationClass);

// NTAPI
typedef ULONG (WINAPI *Func_RtlNtStatusToDosError)(Z7_WIN_NTSTATUS Status);

#define MY_STATUS_SUCCESS 0

EXTERN_C_END

// static Func_NtSetInformationFile f_NtSetInformationFile;
// static bool g_NtSetInformationFile_WasRequested = false;
Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION

Z7_COM7F_IMF(CAltStreamsFolder::Rename(UInt32 index, const wchar_t *newName, IProgress *progress))
{
  const CAltStream &ss = Streams[index];
  const FString srcPath = _pathPrefix + us2fs(ss.Name);

  const HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
  // if (!g_NtSetInformationFile_WasRequested) {
  // g_NtSetInformationFile_WasRequested = true;
  const
   Func_NtSetInformationFile
      f_NtSetInformationFile = Z7_GET_PROC_ADDRESS(
   Func_NtSetInformationFile, ntdll,
       "NtSetInformationFile");
  if (f_NtSetInformationFile)
  {
    NIO::CInFile inFile;
    if (inFile.Open_for_FileRenameInformation(srcPath))
    {
      UString destPath (':');
      destPath += newName;
      const ULONG len = (ULONG)sizeof(wchar_t) * destPath.Len();
      CByteBuffer buffer(sizeof(Z7_WIN_FILE_RENAME_INFORMATION) + len);
      // buffer is 4 bytes larger than required.
      Z7_WIN_FILE_RENAME_INFORMATION *fri = (Z7_WIN_FILE_RENAME_INFORMATION *)(void *)(Byte *)buffer;
      memset(fri, 0, sizeof(Z7_WIN_FILE_RENAME_INFORMATION));
      /* DOCS: If ReplaceIfExists is set to TRUE, the rename operation will succeed only
         if a stream with the same name does not exist or is a zero-length data stream. */
      fri->ReplaceIfExists = FALSE;
      fri->RootDirectory = NULL;
      fri->FileNameLength = len;
      memcpy(fri->FileName, destPath.Ptr(), len);
      Z7_WIN_IO_STATUS_BLOCK iosb;
      const Z7_WIN_NTSTATUS status = f_NtSetInformationFile (inFile.GetHandle(),
          &iosb, fri, (ULONG)buffer.Size(), Z7_WIN_FileRenameInformation);
      if (status != MY_STATUS_SUCCESS)
      {
        const
         Func_RtlNtStatusToDosError
            f_RtlNtStatusToDosError = Z7_GET_PROC_ADDRESS(
         Func_RtlNtStatusToDosError, ntdll,
             "RtlNtStatusToDosError");
        if (f_RtlNtStatusToDosError)
        {
          const ULONG res = f_RtlNtStatusToDosError(status);
          if (res != ERROR_MR_MID_NOT_FOUND)
            return HRESULT_FROM_WIN32(res);
        }
      }
      return status;
    }
  }

  CMyComPtr<IFolderArchiveUpdateCallback> callback;
  if (progress)
  {
    RINOK(progress->QueryInterface(IID_IFolderArchiveUpdateCallback, (void **)&callback))
  }

  if (callback)
  {
    RINOK(callback->SetNumFiles(1))
    RINOK(callback->SetTotal(ss.Size))
  }

  NFsFolder::CCopyStateIO state;
  state.Progress = progress;
  state.TotalSize = 0;
  state.DeleteSrcFile = true;

  const FString destPath = _pathPrefix + us2fs(newName);
  return UpdateFile(state, srcPath, destPath, callback);
}

Z7_COM7F_IMF(CAltStreamsFolder::Delete(const UInt32 *indices, UInt32 numItems,IProgress *progress))
{
  RINOK(progress->SetTotal(numItems))
  for (UInt32 i = 0; i < numItems; i++)
  {
    const CAltStream &ss = Streams[indices[i]];
    const FString fullPath = _pathPrefix + us2fs(ss.Name);
    const bool result = DeleteFileAlways(fullPath);
    if (!result)
      return GetLastError_noZero_HRESULT();
    const UInt64 completed = i;
    RINOK(progress->SetCompleted(&completed))
  }
  return S_OK;
}

Z7_COM7F_IMF(CAltStreamsFolder::SetProperty(UInt32 /* index */, PROPID /* propID */,
    const PROPVARIANT * /* value */, IProgress * /* progress */))
{
  return E_NOTIMPL;
}

Z7_COM7F_IMF(CAltStreamsFolder::GetSystemIconIndex(UInt32 index, Int32 *iconIndex))
{
  const CAltStream &ss = Streams[index];
  *iconIndex = 0;
  int iconIndexTemp;
  if (GetRealIconIndex(_pathPrefix + us2fs(ss.Name),
    0 // fi.Attrib
    , iconIndexTemp) != 0)
  {
    *iconIndex = iconIndexTemp;
    return S_OK;
  }
  return GetLastError_noZero_HRESULT();
}

/*
Z7_CLASS_IMP_COM_1(
  CGetProp
  , IGetProp
)
public:
  // const CArc *Arc;
  // UInt32 IndexInArc;
  UString Name; // relative path
  UInt64 Size;
};

Z7_COM7F_IMF(CGetProp::GetProp(PROPID propID, PROPVARIANT *value))
{
  if (propID == kpidName)
  {
    COM_TRY_BEGIN
    NCOM::CPropVariant prop;
    prop = Name;
    prop.Detach(value);
    return S_OK;
    COM_TRY_END
  }
  if (propID == kpidSize)
  {
    NCOM::CPropVariant prop = Size;
    prop.Detach(value);
    return S_OK;
  }
  NCOM::CPropVariant prop;
  prop.Detach(value);
  return S_OK;
}
*/

static HRESULT CopyStream(
    NFsFolder::CCopyStateIO &state,
    const FString &srcPath,
    const CFileInfo &srcFileInfo,
    const CAltStream &srcAltStream,
    const FString &destPathSpec,
    IFolderOperationsExtractCallback *callback)
{
  FString destPath = destPathSpec;
  if (CompareFileNames(destPath, srcPath) == 0)
  {
    RINOK(SendMessageError(callback, "Cannot copy file onto itself", destPath))
    return E_ABORT;
  }

  Int32 writeAskResult;
  CMyComBSTR destPathResult;
  RINOK(callback->AskWrite(
      fs2us(srcPath),
      BoolToInt(false),
      &srcFileInfo.MTime, &srcAltStream.Size,
      fs2us(destPath),
      &destPathResult,
      &writeAskResult))

  if (IntToBool(writeAskResult))
  {
    RINOK(callback->SetCurrentFilePath(fs2us(srcPath)))
    FString destPathNew (us2fs((LPCOLESTR)destPathResult));
    RINOK(state.MyCopyFile(srcPath, destPathNew))
    if (state.ErrorFileIndex >= 0)
    {
      if (state.ErrorMessage.IsEmpty())
        state.ErrorMessage = GetLastErrorMessage();
      FString errorName;
      if (state.ErrorFileIndex == 0)
        errorName = srcPath;
      else
        errorName = destPathNew;
      RINOK(SendMessageError(callback, state.ErrorMessage, errorName))
      return E_ABORT;
    }
    state.StartPos += state.CurrentSize;
  }
  else
  {
    if (state.TotalSize >= srcAltStream.Size)
    {
      state.TotalSize -= srcAltStream.Size;
      RINOK(state.Progress->SetTotal(state.TotalSize))
    }
  }
  return S_OK;
}

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

  /*
  Z7_DECL_CMyComPtr_QI_FROM(
      IFolderExtractToStreamCallback,
      ExtractToStreamCallback, callback)
  if (ExtractToStreamCallback)
  {
    Int32 useStreams = 0;
    if (ExtractToStreamCallback->UseExtractToStream(&useStreams) != S_OK)
      useStreams = 0;
    if (useStreams == 0)
      ExtractToStreamCallback.Release();
  }
  */

  UInt64 totalSize = 0;
  {
    UInt32 i;
    for (i = 0; i < numItems; i++)
    {
      totalSize += Streams[indices[i]].Size;
    }
    RINOK(callback->SetTotal(totalSize))
    RINOK(callback->SetNumFiles(numItems))
  }

  /*
  if (ExtractToStreamCallback)
  {
    CGetProp *GetProp_Spec = new CGetProp;
    CMyComPtr<IGetProp> GetProp= GetProp_Spec;

    for (UInt32 i = 0; i < numItems; i++)
    {
      UInt32 index = indices[i];
      const CAltStream &ss = Streams[index];
      GetProp_Spec->Name = ss.Name;
      GetProp_Spec->Size = ss.Size;
      CMyComPtr<ISequentialOutStream> outStream;
      RINOK(ExtractToStreamCallback->GetStream7(GetProp_Spec->Name, BoolToInt(false), &outStream,
        NArchive::NExtract::NAskMode::kExtract, GetProp)); // isDir
      FString srcPath;
      GetFullPath(ss, srcPath);
      RINOK(ExtractToStreamCallback->PrepareOperation7(NArchive::NExtract::NAskMode::kExtract));
      RINOK(ExtractToStreamCallback->SetOperationResult7(NArchive::NExtract::NOperationResult::kOK, BoolToInt(false))); // _encrypted
      // RINOK(CopyStream(state, srcPath, fi, ss, destPath2, callback, completedSize));
    }
    return S_OK;
  }
  */

  FString destPath (us2fs(path));
  if (destPath.IsEmpty() /* && !ExtractToStreamCallback */)
    return E_INVALIDARG;

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

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

  CFileInfo fi;
  if (!fi.Find(_pathBaseFile))
    return GetLastError_noZero_HRESULT();

  NFsFolder::CCopyStateIO state;
  state.Progress = callback;
  state.DeleteSrcFile = IntToBool(moveMode);
  state.TotalSize = totalSize;

  for (UInt32 i = 0; i < numItems; i++)
  {
    const UInt32 index = indices[i];
    const CAltStream &ss = Streams[index];
    FString destPath2 = destPath;
    if (!isDirectPath)
      destPath2 += us2fs(Get_Correct_FsFile_Name(ss.Name));
    FString srcPath;
    GetFullPath(ss, srcPath);
    RINOK(CopyStream(state, srcPath, fi, ss, destPath2, callback))
  }

  return S_OK;
}

Z7_COM7F_IMF(CAltStreamsFolder::CopyFrom(Int32 /* moveMode */, const wchar_t * /* fromFolderPath */,
    const wchar_t * const * /* itemsPaths */, UInt32 /* numItems */, IProgress * /* progress */))
{
  /*
  if (numItems == 0)
    return S_OK;

  CMyComPtr<IFolderArchiveUpdateCallback> callback;
  if (progress)
  {
    RINOK(progress->QueryInterface(IID_IFolderArchiveUpdateCallback, (void **)&callback));
  }

  if (CompareFileNames(fromFolderPath, fs2us(_pathPrefix)) == 0)
  {
    RINOK(SendMessageError(callback, "Cannot copy file onto itself", _pathPrefix));
    return E_ABORT;
  }

  if (callback)
    RINOK(callback->SetNumFiles(numItems));

  UInt64 totalSize = 0;
  
  UInt32 i;
  
  FString path;
  for (i = 0; i < numItems; i++)
  {
    path = us2fs(fromFolderPath);
    path += us2fs(itemsPaths[i]);

    CFileInfo fi;
    if (!fi.Find(path))
      return ::GetLastError();
    if (fi.IsDir())
      return E_NOTIMPL;
    totalSize += fi.Size;
  }
  
  RINOK(progress->SetTotal(totalSize));

  // UInt64 completedSize = 0;
   
  NFsFolder::CCopyStateIO state;
  state.Progress = progress;
  state.DeleteSrcFile = IntToBool(moveMode);
  state.TotalSize = totalSize;

  // we need to clear READ-ONLY of parent before creating alt stream
  {
    DWORD attrib = GetFileAttrib(_pathBaseFile);
    if (attrib != INVALID_FILE_ATTRIBUTES
        && (attrib & FILE_ATTRIBUTE_READONLY) != 0)
    {
      if (!SetFileAttrib(_pathBaseFile, attrib & ~FILE_ATTRIBUTE_READONLY))
      {
        if (callback)
        {
          RINOK(SendMessageError(callback, GetLastErrorMessage(), _pathBaseFile));
          return S_OK;
        }
        return Return_LastError_or_FAIL();
      }
    }
  }
  
  for (i = 0; i < numItems; i++)
  {
    path = us2fs(fromFolderPath);
    path += us2fs(itemsPaths[i]);

    FString destPath = _pathPrefix + us2fs(itemsPaths[i]);

    RINOK(UpdateFile(state, path, destPath, callback));
  }

  return S_OK;
  */
  return E_NOTIMPL;
}

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

}
