#define UNICODE /* for TCHAR, ::MessageBox() -> ::MessageBoxW() */
#include <windows.h>
#include <iostream>
#include <stack>
#include <cassert>
#include <cmath>
#include <set>
#include <sstream>
static bool flag_nondelete = false;
static_assert(sizeof(TCHAR) == sizeof(char16_t));
using stringU16 = std::basic_string<char16_t>;
std::ostream &operator<<(std::ostream &os, stringU16 pUnicode) {
int len = ::WideCharToMultiByte(CP_THREAD_ACP, 0, (TCHAR *)pUnicode.c_str(), -1, 0, 0, 0, 0);
char *pAnsi = new char [len + 2];
if (pAnsi) {
int actualLen = ::WideCharToMultiByte(CP_THREAD_ACP, 0, (TCHAR *)pUnicode.c_str(), -1, pAnsi, len + 2, 0, 0);
if (actualLen == len) {
os << pAnsi; /* output */
}
delete pAnsi;
}
return os;
}
class deletingDest {
struct PathAndFileP {
public:
stringU16 path;
bool isFileP;
PathAndFileP(stringU16 path, bool isFileP) : path(path), isFileP(isFileP) {}
friend bool operator<(PathAndFileP a, PathAndFileP b) {
return a.path > b.path;
}
};
private:
std::set<PathAndFileP> deletingDestSet;
public:
deletingDest() {
(this->deletingDestSet).clear();
}
void accumulatingAllDestPath(stringU16 path) {
if (path.find_last_of(stringU16(u"\\")) != path.length() - 1) path += u"\\"; /* */
stringU16 path_findfirst = path + u"*.*";
WIN32_FIND_DATA FindFileData;
HANDLE hFindFirstNext = ::FindFirstFile((TCHAR *)path_findfirst.c_str(), &FindFileData);
bool bInLoop = (hFindFirstNext != INVALID_HANDLE_VALUE);
while (bInLoop) {
stringU16 path_filename;
stringU16 filename = stringU16((char16_t *)FindFileData.cFileName);
if (filename == u"." || filename == u"..") goto FINDNEXTFILE;
path_filename = path + filename;
if (::GetFileAttributes((TCHAR *)path_filename.c_str()) & FILE_ATTRIBUTE_DIRECTORY) { /* when now_path_filename is a directory */
PathAndFileP pathAndFileP(path_filename, false);
(this->deletingDestSet).insert(pathAndFileP);
this->accumulatingAllDestPath(path_filename);
} else { /* path_filename is file */
PathAndFileP pathAndFileP(path_filename, true);
(this->deletingDestSet).insert(pathAndFileP);
}
FINDNEXTFILE:
bInLoop = ::FindNextFile(hFindFirstNext, &FindFileData);
} /* while (bInLoop) { */
::FindClose(hFindFirstNext);
return;
}
void checkingPExitDestPath(stringU16 path) {
std::set<PathAndFileP>::iterator p = deletingDestSet.find(PathAndFileP(path, false));
if (p != deletingDestSet.end()) {
deletingDestSet.erase(p);
}
}
stringU16 popFromSetRemainDestPath(bool *isFileP) {
stringU16 returnValue;
std::set<PathAndFileP>::iterator p = deletingDestSet.begin();
if (p == deletingDestSet.end())
return u"";
returnValue = p->path;
*isFileP = p->isFileP;
deletingDestSet.erase(p); /* for c++11 */
return returnValue;
}
};
static deletingDest deletingDestS;
std::string printSizeApproximately(uint64_t n) {
/* 0 */ /* 1 */ /* 2 */ /* 3 */ /* 4 */
static std::string Suffix[] = { std::string("bytes"), std::string("KiB"), std::string("MiB"), std::string("GiB"), std::string("TiB") };
uint64_t t = n;
uint64_t s = 1;
uint64_t r;
int i = 0;
for (;;) {
r = t;
t >>= 10;
/* out of loop : 2 conditions */
if (t == 0)
break;
if (i == 4)
break;
s <<= 10;
i++;
}
std::stringstream ss;
if (i == 0) {
ss << n << " " << Suffix[i];
} else {
if (r >= 1 && r < (int)(1024 / 100.0)) { /* 1.52KiB */
ss << (int)(n * 100.0 / s) / 100.0 << ' ' << Suffix[i];
} else if (r >= (int)(1024 / 100.0) && r < (int)(1024 / 10.0)) { /* 15.2KiB */
ss << (int)(n * 10.0 / s) / 10.0 << ' ' << Suffix[i];
} else { /* r >= (int)(1024 / 10.0 152KiB */
ss << (int)((double)n / s) << ' ' << Suffix[i];
}
}
std::string output_s = ss.str();
return output_s;
}
int const BuffSize = 32 * 1024 * 1024;
bool MyCopyFile(stringU16 src, stringU16 dest, uint64_t fileSize) {
static char Buff[BuffSize];
DWORD ActualReadBytes, ActualWriteBytes;
HANDLE hFileSrc, hFileDest;
hFileSrc = ::CreateFile((TCHAR *)src.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hFileSrc == INVALID_HANDLE_VALUE) {
std::cerr << "Cannot open(read) :" << src << std::endl;
return false;
}
FILETIME ftSrcCreate, ftSrcLastAccess, ftSrcLastWrite;
GetFileTime(hFileSrc, &ftSrcCreate, &ftSrcLastAccess, &ftSrcLastWrite);
hFileDest = ::CreateFile((TCHAR *)dest.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hFileSrc == INVALID_HANDLE_VALUE) {
std::cerr << "Cannot open(write) :" << dest << std::endl;
::CloseHandle(hFileSrc);
return false;
}
SetFileTime(hFileDest, &ftSrcCreate, &ftSrcLastAccess, &ftSrcLastWrite);
uint64_t nReadTotalBytes = 0;
uint64_t nWriteTotalBytes = 0;
/* process display */
std::cout << std::endl;
for (;;) {
if (!::ReadFile(hFileSrc, Buff, BuffSize, &ActualReadBytes, 0)) {
std::cerr << "Opend but cannot read: " << src << " : " << nReadTotalBytes << "/" << nWriteTotalBytes << std::endl;
::CloseHandle(hFileSrc);
::CloseHandle(hFileDest);
return false;
}
nReadTotalBytes += ActualReadBytes;
/* out of loop */
if (ActualReadBytes == 0)
break;
if(!::WriteFile(hFileDest, Buff, ActualReadBytes, &ActualWriteBytes, 0)) {
std::cerr << "Opend and read but cannot write: " << dest << " : " << nReadTotalBytes << "/" << nWriteTotalBytes << std::endl;
::CloseHandle(hFileSrc);
::CloseHandle(hFileDest);
return false;
}
nWriteTotalBytes += ActualWriteBytes;
putchar('*');
}
putchar('\n');
if (nReadTotalBytes != nWriteTotalBytes) {
std::cout << nReadTotalBytes << "/" << nWriteTotalBytes << " : differ!!" << std::endl;
::CloseHandle(hFileSrc);
::CloseHandle(hFileDest);
return false;
}
std::cout << "Size: " << printSizeApproximately(nWriteTotalBytes) << std::endl;
::CloseHandle(hFileSrc);
::CloseHandle(hFileDest);
return true;
}
struct PathPair {
stringU16 src;
stringU16 dest;
PathPair(stringU16 src, stringU16 dest) : src(src), dest(dest) { }
};
std::stack<PathPair> path_stack;
std::stack<PathPair> path_created_directory;
static int availableFileNumS = 0;
static int copiedFileNumS = 0;
static uint64_t totalSizeIncremental = 0;
void copy_body(stringU16 original_dest, deletingDest *deletingDestP) {
while (!path_stack.empty()) {
PathPair path_pair = path_stack.top(); path_stack.pop();
stringU16 now_path = path_pair.src;
stringU16 now_dest_path = path_pair.dest;
if (now_path.find_last_of(stringU16(u"\\")) != now_path.length() - 1) now_path += u"\\"; /* */
if (now_dest_path.find_last_of(stringU16(u"\\")) != now_dest_path.length() - 1) now_dest_path += u"\\"; /* */
stringU16 now_path_findfirst = now_path + u"*.*";
WIN32_FIND_DATA FindFileData;
HANDLE hFindFirstNext = ::FindFirstFile((TCHAR *)now_path_findfirst.c_str(), &FindFileData);
bool bInLoop = (hFindFirstNext != INVALID_HANDLE_VALUE);
while (bInLoop) {
stringU16 now_path_filename;
stringU16 now_dest_path_filename;
stringU16 filename = stringU16((char16_t *)FindFileData.cFileName);
if (filename == u"." || filename == u"..") goto FINDNEXTFILE;
now_path_filename = now_path + filename;
now_dest_path_filename = now_dest_path + filename;
/* prevent recursively-endless-loop */
if (now_path_filename == original_dest)
goto FINDNEXTFILE;
if (::GetFileAttributes((TCHAR *)now_path_filename.c_str()) & FILE_ATTRIBUTE_DIRECTORY) { /* when now_path_filename is a directory */
if ((signed int)::GetFileAttributes((TCHAR *)now_dest_path_filename.c_str()) == -1) {
if(::CreateDirectory((TCHAR *)now_dest_path_filename.c_str(), 0)) {
std::cout << "Create directory: " << now_dest_path_filename << std::endl;
path_created_directory.push(PathPair(now_path_filename, now_dest_path_filename));
} else {
std::cerr << "Failure to create directory: " << now_dest_path_filename << std::endl;
break;
}
}
deletingDestP->checkingPExitDestPath(now_dest_path_filename); /* remove from set::set<> */
path_stack.push(PathPair(now_path_filename, now_dest_path_filename));
} else { /* when now_path_filename is a file */
FILETIME ftSrc, ftDest;
uint64_t timeSrc, timeDest;
uint64_t srcFileSize, destFileSize;
uint64_t tmp;
availableFileNumS++;
bool flagExcecFileCopy = false;
HANDLE hSrc = ::CreateFile((TCHAR *)now_path_filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
HANDLE hDest = ::CreateFile((TCHAR *)now_dest_path_filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
/* when src's file and dest's file size are different, do copy */
tmp = 0;
srcFileSize = ::GetFileSize(hSrc, (LPDWORD)&tmp);
srcFileSize |= (tmp << 32);
destFileSize = 0;
if (hDest != (HANDLE *)0xffffffffffffffff) {
destFileSize = ::GetFileSize(hSrc, (LPDWORD)&tmp);
destFileSize |= (tmp << 32);
deletingDestP->checkingPExitDestPath(now_dest_path_filename); /* remove from set::set<> */
}
if (srcFileSize != destFileSize)
flagExcecFileCopy |= true;
/* when (now_path_filename(Src)'s time) > (now_dest_path_filename(Dest)'s time), do copy */
ftSrc = {0, 0}; ftDest = {0, 0};
::GetFileTime(hSrc, 0, 0, &ftSrc);
::GetFileTime(hDest, 0, 0, &ftDest);
timeSrc = ftSrc.dwHighDateTime; timeSrc <<= 32, timeSrc |= ftSrc.dwLowDateTime; /* Src's time */
timeDest = ftDest.dwHighDateTime; timeDest <<= 32, timeDest |= ftDest.dwLowDateTime; /* Dest's time */
::CloseHandle(hSrc); ::CloseHandle(hDest);
assert(hDest != (HANDLE *)0xffffffffffffffff || timeDest == 0); /* important assumption */
if (timeSrc > timeDest)
flagExcecFileCopy |= true;
if (flagExcecFileCopy) {
std::cout << now_path_filename << " -> " << now_dest_path_filename << " ";
if (MyCopyFile(now_path_filename, now_dest_path_filename, srcFileSize)) {
copiedFileNumS++;
std::cout << "(" << copiedFileNumS << ") : OK ";
totalSizeIncremental += ((srcFileSize > destFileSize) ? srcFileSize - destFileSize : srcFileSize);
std::cout << srcFileSize << ":" << destFileSize << ":" << printSizeApproximately(totalSizeIncremental) << std::endl;
} /* if */
} /* if */
} /* if-else */
FINDNEXTFILE:
bInLoop = ::FindNextFile(hFindFirstNext, &FindFileData);
} /* while(bInLoop) */
if (hFindFirstNext != INVALID_HANDLE_VALUE)
::FindClose(hFindFirstNext);
} /* while(!path_stack.empty()) */
}
void setWideChar_fromAnsi(stringU16 &unicodeString, const char *ansiString) {
int len = ::MultiByteToWideChar(CP_THREAD_ACP, 0, ansiString, -1, 0, 0);
char16_t *pUnicode = new char16_t [len + 2];
if (!pUnicode) { unicodeString = stringU16(u""); delete pUnicode; return; }
int actualLen = ::MultiByteToWideChar(CP_THREAD_ACP, 0, ansiString, -1, (TCHAR *)pUnicode, len + 2);
if (actualLen != len) { unicodeString = stringU16(u""); delete pUnicode; return; }
unicodeString = stringU16(pUnicode);
delete pUnicode;
}
int main(int argc, char *argv[]) {
if (argc != 3 && argc != 4) {
label_usage:
std::cerr << argv[0] << " : copy files and directory recursively." << std::endl;
std::cerr << "usage: " << argv[0] << " <src-path> <dest-path> " << std::endl;
std::cerr << "option: /nondelete : not deleting." << std::endl;
return 0;
}
int const n = 1024;
char16_t buff[n];
GetCurrentDirectory(n, (TCHAR *)buff);
stringU16 current_directory(buff);
stringU16 src;
stringU16 dest;
int nowstep = 0;
for (int i = 1; i < argc; i++) {
if (strstr(argv[i], "/nondelete") == argv[i]) {
flag_nondelete = true;
continue;
}
switch (nowstep) {
case 0:
setWideChar_fromAnsi(src, argv[i]);
nowstep++;
continue;
case 1:
setWideChar_fromAnsi(dest, argv[2]);
nowstep++;
continue;
default:
goto label_usage;
}
}
if (nowstep != 2) {
std::cout << "argc > 3" << std::endl;
goto label_usage;
}
if (dest == u".") {
std::cerr << argv[0] << ": cannot specify current directory for the destination path" << std::endl;
return 0;
}
if (src == u".") {
src.clear();
}
if (src == u".." || dest == u".." ) {
std::cerr << argv[0] << ": cannot use the parent directory's expression \"..\". " << std::endl;
return 0;
}
/* translate to abusolute path */
if (src.find(u":") == stringU16::npos) { /* src is relative path */
if (src.empty()) {
src = current_directory;
} else {
src = current_directory + u"\\" + src;
}
}
if (dest.find(u":") == stringU16::npos) { /* src is relative path */
dest = current_directory + u"\\" + dest;
}
/* cf. https://docs.microsoft.com/ja-jp/windows/desktop/FileIO/naming-a-file */
src = u"\\\\?\\" + src;
dest = u"\\\\?\\" + dest;
int attributes = ::GetFileAttributes((TCHAR *)dest.c_str());
if (attributes == -1) {
std::cerr << "Directory: " << dest << " is not exists, aborted." << std::endl;
return 0;
}
/* */
deletingDestS.accumulatingAllDestPath(dest);
/* */
path_stack.push(PathPair(src, dest));
copy_body(dest, &deletingDestS);
std::cout << "available: " << availableFileNumS << ", copied: " << copiedFileNumS << "." << std::endl;
/* */
bool isFileP;
stringU16 forDeleteDestPath;
while ((forDeleteDestPath = deletingDestS.popFromSetRemainDestPath(&isFileP)) != u"") {
if (isFileP) {
if (::GetFileAttributes((TCHAR *)forDeleteDestPath.c_str()) != INVALID_FILE_ATTRIBUTES) {
if (!flag_nondelete) {
::DeleteFile((TCHAR *)forDeleteDestPath.c_str());
std::cout << "delete(file): " << forDeleteDestPath << std::endl;
}
}
} else {
if (::GetFileAttributes((TCHAR *)forDeleteDestPath.c_str()) != INVALID_FILE_ATTRIBUTES) {
if (!flag_nondelete) {
::RemoveDirectory((TCHAR *)forDeleteDestPath.c_str()); /* now dir to be empty */
std::cout << "delete:(dir) " << forDeleteDestPath << std::endl;
}
}
}
}
/* set created directory(= dest)'s timestamp as src's directory is */
/* */
while (!path_created_directory.empty()) {
PathPair path_created = path_created_directory.top(); path_created_directory.pop();
stringU16 src_directory = path_created.src;
stringU16 dest_directory = path_created.dest;
HANDLE hSrc = ::CreateFile((TCHAR *)src_directory.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
HANDLE hDest = ::CreateFile((TCHAR *)dest_directory.c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
FILETIME ftSrcCreate, ftSrcLastAccess, ftSrcLastWrite;
::GetFileTime(hSrc, &ftSrcCreate, &ftSrcLastAccess, &ftSrcLastWrite);
::SetFileTime(hDest, &ftSrcCreate, &ftSrcLastAccess, &ftSrcLastWrite);
::CloseHandle(hSrc); ::CloseHandle(hDest);
}
return 0;
}
/* end */