p7zip-rar/CPP/7zip/UI/FileManager/FSFolderCopy.cpp
2017-10-11 12:40:22 +02:00

708 lines
18 KiB
C++

// FSFolderCopy.cpp
#include "StdAfx.h"
#include "../../../Common/MyWindows.h"
#ifdef _WIN32
#include <Winbase.h>
#endif
#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 {
HRESULT CCopyStateIO::MyCopyFile(CFSTR inPath, CFSTR outPath)
{
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(outPath, true))
{
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 = L"Write error";
return S_OK;
}
CurrentSize += num;
if (Progress)
{
UInt64 completed = StartPos + CurrentSize;
RINOK(Progress->SetCompleted(&completed));
}
}
}
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; }
};
#ifdef _WIN32
#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
)
{
TotalFileSize = TotalFileSize;
// TotalBytesTransferred = TotalBytesTransferred;
// 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);
}
UInt64 completed = pi.StartPos + TotalBytesTransferred.QuadPart;
pi.ProgressResult = pi.Progress->SetCompleted(&completed);
return (pi.ProgressResult == S_OK ? PROGRESS_CONTINUE : PROGRESS_CANCEL);
}
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
);
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
);
typedef BOOL (WINAPI * Func_MoveFileWithProgressW)(
IN LPCWSTR lpExistingFileName,
IN LPCWSTR lpNewFileName,
IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
IN LPVOID lpData OPTIONAL,
IN DWORD dwFlags
);
struct CCopyState
{
CProgressInfo ProgressInfo;
IFolderOperationsExtractCallback *Callback;
UInt64 TotalSize;
bool MoveMode;
bool UseReadWriteMode;
Func_CopyFileExW my_CopyFileExW;
#ifndef UNDER_CE
Func_MoveFileWithProgressW my_MoveFileWithProgressW;
#endif
#ifndef _UNICODE
Func_CopyFileExA my_CopyFileExA;
#endif
void Prepare();
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; }
};
#else
struct CCopyState
{
CProgressInfo ProgressInfo;
IFolderOperationsExtractCallback *Callback;
UInt64 TotalSize;
bool MoveMode;
bool UseReadWriteMode;
HRESULT CallProgress();
void Prepare();
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);
bool IsCallbackProgressError() { return ProgressInfo.ProgressResult != S_OK; }
};
#endif
HRESULT CCopyState::CallProgress()
{
return ProgressInfo.Progress->SetCompleted(&ProgressInfo.StartPos);
}
void CCopyState::Prepare()
{
#ifdef _WIN32
my_CopyFileExW = NULL;
#ifndef UNDER_CE
my_MoveFileWithProgressW = NULL;
#endif
#ifndef _UNICODE
my_CopyFileExA = NULL;
if (!g_IsNT)
{
my_CopyFileExA = (Func_CopyFileExA)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "CopyFileExA");
}
else
#endif
{
HMODULE module = ::GetModuleHandleW(
#ifdef UNDER_CE
L"coredll.dll"
#else
L"kernel32.dll"
#endif
);
my_CopyFileExW = (Func_CopyFileExW)My_GetProcAddress(module, "CopyFileExW");
#ifndef UNDER_CE
my_MoveFileWithProgressW = (Func_MoveFileWithProgressW)My_GetProcAddress(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)
{
#ifdef _WIN32
BOOL cancelFlag = FALSE;
if (my_CopyFileExW)
return BOOLToBool(my_CopyFileExW(oldFile, newFile, CopyProgressRoutine,
&ProgressInfo, &cancelFlag, COPY_FILE_FAIL_IF_EXISTS));
return BOOLToBool(::CopyFileW(oldFile, newFile, TRUE));
#else
extern bool wxw_CopyFile(LPCWSTR existingFile, LPCWSTR newFile, bool overwrite);
return wxw_CopyFile(oldFile, newFile, true);
#endif
}
bool CCopyState::CopyFile_Sys(CFSTR oldFile, CFSTR newFile)
{
#ifdef _WIN32
#ifndef _UNICODE
if (!g_IsNT)
{
if (my_CopyFileExA)
{
BOOL cancelFlag = FALSE;
if (my_CopyFileExA(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 WIN_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;
}
#else
extern bool wxw_CopyFile(LPCWSTR existingFile, LPCWSTR newFile, bool overwrite);
return wxw_CopyFile(oldFile, newFile, true);
#endif
}
bool CCopyState::MoveFile_Sys(CFSTR oldFile, CFSTR newFile)
{
#if 0 // FIXME #ifndef UNDER_CE
// if (IsItWindows2000orHigher())
// {
if (my_MoveFileWithProgressW)
{
IF_USE_MAIN_PATH_2(oldFile, newFile)
{
if (my_MoveFileWithProgressW(fs2us(oldFile), fs2us(newFile), CopyProgressRoutine,
&ProgressInfo, MOVEFILE_COPY_ALLOWED))
return true;
}
#ifdef WIN_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 (my_MoveFileWithProgressW(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 += L" : ";
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 ?
"can not move file onto itself" :
"can not 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.TotalSize;
state2.StartPos = state.ProgressInfo.StartPos;
RINOK(state2.MyCopyFile(srcPath, destPathNew));
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)
{
// GetLastError() is ERROR_REQUEST_ABORTED in case of PROGRESS_CANCEL.
RINOK(SendMessageError(state.Callback, GetLastErrorMessage(), destPathNew));
return E_ABORT;
}
state.ProgressInfo.StartPos += state.ProgressInfo.FileSize;
}
}
else
{
if (state.TotalSize >= srcFileInfo.Size)
{
state.TotalSize -= srcFileInfo.Size;
RINOK(state.ProgressInfo.Progress->SetTotal(state.TotalSize));
}
}
return state.CallProgress();
}
static FString CombinePath(const FString &folderPath, const FString &fileName)
{
return folderPath + FCHAR_PATH_SEPARATOR + fileName;
}
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 ?
"can not copy folder onto itself" :
"can not 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, "can not create folder", destPath));
return E_ABORT;
}
CEnumerator enumerator(CombinePath(srcPath, FSTRING_ANY_MASK));
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, "can not remove folder", srcPath));
return E_ABORT;
}
}
return S_OK;
}
STDMETHODIMP 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;
FString destPath = us2fs(path);
if (destPath.IsEmpty())
return E_INVALIDARG;
bool isAltDest = false; // FIXME NName::IsAltPathPrefix(destPath);;
bool isDirectPath = (!isAltDest && !IsPathSepar(destPath.Back()));
if (isDirectPath)
{
if (numItems > 1)
return E_INVALIDARG;
}
CFsFolderStat stat;
stat.Progress = callback;
RINOK(GetItemsFullSize(indices, numItems, stat));
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.UseReadWriteMode = isAltDest;
state.Prepare();
for (UInt32 i = 0; i < numItems; i++)
{
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())
{
RINOK(CopyFolder(state, srcPath, destPath2));
}
else
{
RINOK(CopyFile_Ask(state, srcPath, fi, destPath2));
}
}
return S_OK;
}
STDMETHODIMP CFSFolder::CopyFrom(Int32 /* moveMode */, const wchar_t * /* fromFolderPath */,
const wchar_t * const * /* itemsPaths */, UInt32 /* numItems */, IProgress * /* progress */)
{
/*
UInt64 numFolders, numFiles, totalSize;
numFiles = numFolders = totalSize = 0;
UInt32 i;
for (i = 0; i < numItems; i++)
{
UString path = (UString)fromFolderPath + itemsPaths[i];
CFileInfo fi;
if (!FindFile(path, fi))
return ::GetLastError();
if (fi.IsDir())
{
UInt64 subFolders, subFiles, subSize;
RINOK(GetFolderSize(CombinePath(path, fi.Name), subFolders, subFiles, subSize, progress));
numFolders += subFolders;
numFolders++;
numFiles += subFiles;
totalSize += subSize;
}
else
{
numFiles++;
totalSize += fi.Size;
}
}
RINOK(progress->SetTotal(totalSize));
RINOK(callback->SetNumFiles(numFiles));
for (i = 0; i < numItems; i++)
{
UString path = (UString)fromFolderPath + itemsPaths[i];
}
return S_OK;
*/
return E_NOTIMPL;
}
STDMETHODIMP CFSFolder::CopyFromFile(UInt32 /* index */, const wchar_t * /* fullFilePath */, IProgress * /* progress */)
{
return E_NOTIMPL;
}
}