summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2015-10-02 14:54:34 +0200
committerDaniel Wilhelm <daniel@wili.li>2015-10-02 14:54:34 +0200
commit96e20826f358a32e38c3f052243375982543c05b (patch)
tree691efa86265fbb35cc60a1ce816423bb2a41f17b /zen
parent6.13 (diff)
downloadFreeFileSync-96e20826f358a32e38c3f052243375982543c05b.tar.gz
FreeFileSync-96e20826f358a32e38c3f052243375982543c05b.tar.bz2
FreeFileSync-96e20826f358a32e38c3f052243375982543c05b.zip
6.14
Diffstat (limited to 'zen')
-rw-r--r--zen/basic_math.h11
-rw-r--r--zen/dir_watcher.cpp33
-rw-r--r--zen/file_access.cpp291
-rw-r--r--zen/file_io.cpp7
-rw-r--r--zen/file_traverser.cpp613
-rw-r--r--zen/file_traverser.h75
-rw-r--r--zen/long_path_prefix.h13
-rw-r--r--zen/notify_removal.cpp7
-rw-r--r--zen/recycler.cpp20
-rw-r--r--zen/recycler.h4
-rw-r--r--zen/stl_tools.h2
-rw-r--r--zen/win_ver.h2
12 files changed, 318 insertions, 760 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h
index 9a3d195e..227d1cd4 100644
--- a/zen/basic_math.h
+++ b/zen/basic_math.h
@@ -88,22 +88,13 @@ const double ln2 = 0.693147180559945309417;
-
-
-
-
-
-
-
-
-
//################# inline implementation #########################
template <class T> inline
T abs(T value)
{
static_assert(std::is_signed<T>::value, "");
if (value < 0)
- return -value; // operator "?:" caveat: may be different type than "value"
+ return -value; //operator "?:" caveat: may be different type than "value"
else
return value;
}
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp
index 916f6c69..26645157 100644
--- a/zen/dir_watcher.cpp
+++ b/zen/dir_watcher.cpp
@@ -369,29 +369,6 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()
#elif defined ZEN_LINUX
-namespace
-{
-class DirsOnlyTraverser : public zen::TraverseCallback
-{
-public:
- DirsOnlyTraverser(std::vector<Zstring>& dirs) : dirs_(dirs) {}
-
- void onFile (const Zchar* shortName, const Zstring& filepath, const FileInfo& details ) override {}
- HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override { return LINK_SKIP; }
- TraverseCallback* onDir (const Zchar* shortName, const Zstring& dirpath ) override
- {
- dirs_.push_back(dirpath);
- return this;
- }
- HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); }
- HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override { throw FileError(msg); }
-
-private:
- std::vector<Zstring>& dirs_;
-};
-}
-
-
struct DirWatcher::Pimpl
{
Pimpl() : notifDescr() {}
@@ -411,10 +388,11 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError
dirpathFmt.resize(dirpathFmt.size() - 1);
std::vector<Zstring> fullDirList { dirpathFmt };
- {
- DirsOnlyTraverser traverser(fullDirList); //throw FileError
- zen::traverseFolder(dirpathFmt, traverser); //don't traverse into symlinks (analog to windows build)
- }
+
+traverseFolder(dirpathFmt, nullptr,
+ [&](const DirInfo& di ){ fullDirList.push_back(di.fullPath); },
+ nullptr, //don't traverse into symlinks (analog to windows build)
+[&](const std::wstring& errorMsg){ throw FileError(errorMsg); });
//init
pimpl_->basedirpath = directory;
@@ -545,6 +523,7 @@ void eventCallback(ConstFSEventStreamRef streamRef,
//events are aggregated => it's possible to see a single event with flags
//kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemModified | kFSEventStreamEventFlagItemRemoved
+ //https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags
if (eventFlags[i] & kFSEventStreamEventFlagItemCreated ||
eventFlags[i] & kFSEventStreamEventFlagMount)
changedFiles.emplace_back(DirWatcher::ACTION_CREATE, paths[i]);
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index e32a27a7..ca07e76a 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -206,11 +206,12 @@ Zstring getLockingProcessNames(const Zstring& filepath) //throw(), empty string
const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses);
const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString);
+ const wchar_t* processList = nullptr;
if (getLockingProcesses && freeString)
- if (const wchar_t* procList = getLockingProcesses(filepath.c_str()))
+ if (getLockingProcesses(filepath.c_str(), processList))
{
- ZEN_ON_SCOPE_EXIT(freeString(procList));
- return procList;
+ ZEN_ON_SCOPE_EXIT(freeString(processList));
+ return processList;
}
}
return Zstring();
@@ -249,6 +250,7 @@ std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"CreateFile", getLastError());
ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
+ //why not use ::GetFileSizeEx() instead???
BY_HANDLE_FILE_INFORMATION fileInfoHnd = {};
if (!::GetFileInformationByHandle(hFile, &fileInfoHnd))
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"GetFileInformationByHandle", getLastError());
@@ -548,42 +550,6 @@ void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw Fil
namespace
{
-class CollectFilesFlat : public zen::TraverseCallback
-{
-public:
- CollectFilesFlat(std::vector<Zstring>& files, std::vector<Zstring>& dirs) :
- files_(files),
- dirs_(dirs) {}
-
- void onFile(const Zchar* shortName, const Zstring& filepath, const FileInfo& details) override
- {
- files_.push_back(filepath);
- }
- HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override
- {
- if (dirExists(linkpath)) //dir symlink
- dirs_.push_back(linkpath);
- else //file symlink, broken symlink
- files_.push_back(linkpath);
- return LINK_SKIP;
- }
- TraverseCallback* onDir(const Zchar* shortName, const Zstring& dirpath) override
- {
- dirs_.push_back(dirpath);
- return nullptr; //DON'T traverse into subdirs; removeDirectory works recursively!
- }
- HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); }
- HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override { throw FileError(msg); }
-
-private:
- CollectFilesFlat (const CollectFilesFlat&) = delete;
- CollectFilesFlat& operator=(const CollectFilesFlat&) = delete;
-
- std::vector<Zstring>& files_;
- std::vector<Zstring>& dirs_;
-};
-
-
void removeDirectoryImpl(const Zstring& directory, //throw FileError
const std::function<void (const Zstring& filepath)>& onBeforeFileDeletion,
const std::function<void (const Zstring& dirpath )>& onBeforeDirDeletion)
@@ -615,11 +581,18 @@ void removeDirectoryImpl(const Zstring& directory, //throw FileError
{
std::vector<Zstring> fileList;
std::vector<Zstring> dirList;
- {
- //get all files and directories from current directory (WITHOUT subdirectories!)
- CollectFilesFlat cff(fileList, dirList);
- traverseFolder(directory, cff); //don't follow symlinks
- }
+ //get all files and directories from current directory (WITHOUT subdirectories!)
+traverseFolder(directory,
+ [&](const FileInfo& fi){ fileList.push_back(fi.fullPath); },
+ [&](const DirInfo& di){ dirList .push_back(di.fullPath); },
+ [&](const SymlinkInfo& si)
+{
+ if (dirExists(si.fullPath)) //dir symlink
+ dirList.push_back(si.fullPath);
+ else //file symlink, broken symlink
+ fileList.push_back(si.fullPath);
+},
+[&](const std::wstring& errorMsg){ throw FileError(errorMsg); });
//delete directories recursively
for (const Zstring& dirpath : dirList)
@@ -893,6 +866,74 @@ void setFileTimeRaw(const Zstring& filepath,
//assert(std::abs(filetimeToTimeT(creationTimeDbg ) - filetimeToTimeT(creationTime )) <= 2); -> creation time not available for Linux-hosted Samba shares!
#endif
}
+
+#elif defined ZEN_MAC
+struct AttrBufFileTimes
+{
+ std::uint32_t length;
+ struct ::timespec createTime; //keep order; see docs!
+ struct ::timespec writeTime; //
+} __attribute__((aligned(4), packed));
+
+
+void setFileTimeRaw(const Zstring& filepath,
+ const struct ::timespec* createTime, //optional
+ const struct ::timespec& writeTime,
+ ProcSymlink procSl) //throw FileError
+{
+ //OS X: utime() is obsoleted by utimes()! utimensat() not yet implemented
+ //use ::setattrlist() instead of ::utimes() => 1. set file creation times 2. nanosecond precision
+ //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/setattrlist.2.html
+
+ struct ::attrlist attribs = {};
+ attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
+ attribs.commonattr = (createTime ? ATTR_CMN_CRTIME : 0) | ATTR_CMN_MODTIME;
+
+ AttrBufFileTimes newTimes = {};
+ if (createTime)
+ {
+ newTimes.createTime.tv_sec = createTime->tv_sec;
+ newTimes.createTime.tv_nsec = createTime->tv_nsec;
+ }
+ newTimes.writeTime.tv_sec = writeTime.tv_sec;
+ newTimes.writeTime.tv_nsec = writeTime.tv_nsec;
+
+ const int rv = ::setattrlist(filepath.c_str(), //const char* path,
+ &attribs, //struct ::attrlist* attrList,
+ createTime ? &newTimes.createTime : &newTimes.writeTime, //void* attrBuf,
+ (createTime ? sizeof(newTimes.createTime) : 0) + sizeof(newTimes.writeTime), //size_t attrBufSize,
+ procSl == ProcSymlink::DIRECT ? FSOPT_NOFOLLOW : 0); //unsigned long options
+ if (rv != 0)
+ throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"setattrlist", getLastError());
+}
+
+/*
+void getFileTimeRaw(int fd, //throw FileError
+ const Zstring& filepath, //for error reporting only
+ struct ::timespec& createTime, //out
+ struct ::timespec& writeTime) //
+{
+ //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getattrlist.2.html
+ struct ::attrlist attribs = {};
+ attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
+ attribs.commonattr = ATTR_CMN_CRTIME | ATTR_CMN_MODTIME;
+
+ AttrBufFileTimes fileTimes = {};
+
+ const int rv = ::fgetattrlist(fd, //int fd,
+ &attribs, //struct ::attrlist* attrList,
+ &fileTimes, //void* attrBuf,
+ sizeof(fileTimes), //size_t attrBufSize,
+ 0); //unsigned long options
+ if (rv != 0)
+ throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"getattrlist", getLastError());
+
+ createTime.tv_sec = fileTimes.createTime.tv_sec;
+ createTime.tv_nsec = fileTimes.createTime.tv_nsec;
+ writeTime.tv_sec = fileTimes.writeTime.tv_sec;
+ writeTime.tv_nsec = fileTimes.writeTime.tv_nsec;
+}
+*/
#endif
}
@@ -913,7 +954,7 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink
#ifdef ZEN_WIN
setFileTimeRaw(filepath, nullptr, timetToFileTime(modTime), procSl); //throw FileError
-#elif defined ZEN_LINUX || defined ZEN_MAC
+#elif defined ZEN_LINUX
//sigh, we can't use utimensat on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit???
// struct ::timespec newTimes[2] = {};
@@ -925,8 +966,6 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink
//=> fallback to "retarded-idiot version"! -- DarkByte
- //OS X: utime() is obsoleted by utimes()! utimensat() not yet implemented
-
struct ::timeval newTimes[2] = {};
newTimes[0].tv_sec = ::time(nullptr); //access time (seconds)
newTimes[1].tv_sec = modTime; //modification time (seconds)
@@ -936,6 +975,11 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink
::lutimes(filepath.c_str(), newTimes);
if (rv != 0)
throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimes", getLastError());
+
+#elif defined ZEN_MAC
+ struct ::timespec writeTime = {};
+ writeTime.tv_sec = modTime;
+ setFileTimeRaw(filepath, nullptr, writeTime, procSl); //throw FileError
#endif
}
@@ -1209,7 +1253,7 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym
if (::copyfile(source.c_str(), target.c_str(), 0, flags) != 0)
throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtFileName(source)), L"%y", L"\n" + fmtFileName(target)), L"copyfile", getLastError());
- //owner is *not* copied with ::copyfile():
+ //owner is *not* copied with ::copyfile():
struct ::stat fileInfo = {};
if (procSl == ProcSymlink::FOLLOW)
@@ -1359,16 +1403,16 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
}
#elif defined ZEN_LINUX || defined ZEN_MAC
- mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory
+ mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory
struct ::stat dirInfo = {};
- if (!templateDir.empty())
+ if (!templateDir.empty())
if (::stat(templateDir.c_str(), &dirInfo) == 0)
- {
- mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default
- mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items
- }
- //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions
+ {
+ mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default
+ mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items
+ }
+ //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions
if (::mkdir(directory.c_str(), mode) != 0)
{
@@ -1518,17 +1562,19 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
setFileTimeRaw(targetLink, &sourceAttr.ftCreationTime, sourceAttr.ftLastWriteTime, ProcSymlink::DIRECT); //throw FileError
#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat srcInfo = {};
- if (::lstat(sourceLink.c_str(), &srcInfo) != 0)
+ struct ::stat sourceInfo = {};
+ if (::lstat(sourceLink.c_str(), &sourceInfo) != 0)
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"lstat", getLastError());
- setFileTime(targetLink, srcInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError
-#endif
+#ifdef ZEN_LINUX
+ setFileTime(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError
+#elif defined ZEN_MAC
+ setFileTimeRaw(targetLink, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::DIRECT); //throw FileError
-#ifdef ZEN_MAC
if (::copyfile(sourceLink.c_str(), targetLink.c_str(), 0, COPYFILE_XATTR | COPYFILE_NOFOLLOW) != 0)
throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)), L"copyfile", getLastError());
#endif
+#endif
if (copyFilePermissions)
copyObjectPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError
@@ -1933,8 +1979,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
file time handling:
::CopyFileEx() will (only) copy file modification time over from source file AFTER the last invokation of this callback
=> it is possible to adapt file creation time of target in here, but NOT file modification time!
- CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!!
- see procmon log in: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
+ CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!! (confirmed with Process Monitor)
alternate data stream handling:
CopyFileEx() processes multiple streams one after another, stream 1 is the file data stream and always available!
@@ -2110,10 +2155,9 @@ void copyFileWindowsDefault(const Zstring& sourceFile,
newAttrib->sourceFileId = extractFileId(cbd.fileInfoSrc);
newAttrib->targetFileId = extractFileId(cbd.fileInfoTrg);
}
- warn_static("new perf check + investigate improvements now that DST hcak is gone! =>")
//caveat: - ::CopyFileEx() silently *ignores* failure to set modification time!!! => we always need to set it again but with proper error checking!
- // - perf-loss on USB sticks with many small files of about 30%!
+ // - perf: recent measurements show no slow down at all for buffered USB sticks!
setFileTimeRaw(targetFile, &cbd.fileInfoSrc.ftCreationTime, cbd.fileInfoSrc.ftLastWriteTime, ProcSymlink::FOLLOW); //throw FileError
guardTarget.dismiss(); //target has been created successfully!
@@ -2176,8 +2220,8 @@ void copyFileOsSpecific(const Zstring& sourceFile,
try
{
FileOutputUnbuffered fileOut(targetFile, //throw FileError, ErrorTargetExisting
- sourceInfo.st_mode); //analog to "cp" which copies "mode" (considering umask) by default
- //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions
+ sourceInfo.st_mode); //analog to "cp" which copies "mode" (considering umask) by default
+ //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions
std::vector<char> buffer(128 * 1024); //see comment in FileInputUnbuffered::read
do
@@ -2213,7 +2257,7 @@ void copyFileOsSpecific(const Zstring& sourceFile,
throw;
}
- //we cannot set the target file times while the file descriptor is open and being written:
+ //we cannot set the target file times while the file descriptor is still open after a write operation:
//this triggers bugs on samba shares where the modification time is set to current time instead.
//http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
//http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
@@ -2278,78 +2322,87 @@ void copyFileOsSpecific(const Zstring& sourceFile,
const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting
{
+ struct ::stat sourceInfo = {};
+
//http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/
+ {
+ auto getCopyErrorMessage = [&] { return replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); };
- auto getCopyErrorMessage = [&] { return replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); };
+ copyfile_state_t copyState = ::copyfile_state_alloc();
+ ZEN_ON_SCOPE_EXIT(::copyfile_state_free(copyState));
- copyfile_state_t copyState = ::copyfile_state_alloc();
- ZEN_ON_SCOPE_EXIT(::copyfile_state_free(copyState));
+ CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile);
- CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile);
+ if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CTX, &cbd) != 0)
+ throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CTX", getLastError());
- if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CTX, &cbd) != 0)
- throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CTX", getLastError());
+ if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CB, reinterpret_cast<const void*>(&copyFileCallback)) != 0)
+ throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CB", getLastError());
- if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CB, reinterpret_cast<const void*>(&copyFileCallback)) != 0)
- throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CB", getLastError());
+ zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: docs seem to indicate that copyfile does not clean up
- zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: docs seem to indicate that copyfile does not clean up
+ //http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html
+ if (::copyfile(sourceFile.c_str(), targetFile.c_str(),
+ copyState,
+ COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0)
+ //- even though we don't use COPYFILE_STAT, "mode" (considering umask) is still copied! => harmonized with Linux file copy!
+ //- COPYFILE_STAT does not copy file creation time
+ {
+ //evaluate first! errno is not set for COPYFILE_QUIT!
+ if (cbd.exception)
+ std::rethrow_exception(cbd.exception);
- //http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html
- if (::copyfile(sourceFile.c_str(), targetFile.c_str(),
- copyState,
- COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0) //even though we don't use COPYFILE_STAT, "mode" (considering umask) is still copied! => harmonized with Linux file copy!
- {
- //evaluate first! errno is not set for COPYFILE_QUIT!
- if (cbd.exception)
- std::rethrow_exception(cbd.exception);
+ if (!cbd.errorMsg.first.empty())
+ throw FileError(cbd.errorMsg.first, cbd.errorMsg.second);
- if (!cbd.errorMsg.first.empty())
- throw FileError(cbd.errorMsg.first, cbd.errorMsg.second);
+ const int lastError = errno;
+ std::wstring errorDescr = formatSystemError(L"copyfile", lastError);
- const int lastError = errno;
- std::wstring errorDescr = formatSystemError(L"copyfile", lastError);
+ if (lastError == EEXIST)
+ {
+ guardTarget.dismiss(); //don't delete file that existed previously!
+ throw ErrorTargetExisting(getCopyErrorMessage(), errorDescr);
+ }
- if (lastError == EEXIST)
- {
- guardTarget.dismiss(); //don't delete file that existed previously!
- throw ErrorTargetExisting(getCopyErrorMessage(), errorDescr);
+ throw FileError(getCopyErrorMessage(), errorDescr);
}
- throw FileError(getCopyErrorMessage(), errorDescr);
- }
-
- int fdSource = 0;
- if (::copyfile_state_get(copyState, COPYFILE_STATE_SRC_FD, &fdSource) != 0)
- throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_SRC_FD", getLastError());
+ int fdSource = 0;
+ if (::copyfile_state_get(copyState, COPYFILE_STATE_SRC_FD, &fdSource) != 0)
+ throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_SRC_FD", getLastError());
- int fdTarget = 0;
- if (::copyfile_state_get(copyState, COPYFILE_STATE_DST_FD, &fdTarget) != 0)
- throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_DST_FD", getLastError());
+ int fdTarget = 0;
+ if (::copyfile_state_get(copyState, COPYFILE_STATE_DST_FD, &fdTarget) != 0)
+ throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_DST_FD", getLastError());
- struct ::stat sourceInfo = {};
- if (::fstat(fdSource, &sourceInfo) != 0)
- throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError());
+ if (::fstat(fdSource, &sourceInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError());
- struct ::stat targetInfo = {};
- if (::fstat(fdTarget, &targetInfo) != 0)
- throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError());
+ struct ::stat targetInfo = {};
+ if (::fstat(fdTarget, &targetInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError());
- struct ::timeval newTimes[2] = {};
- newTimes[0].tv_sec = sourceInfo.st_atime; //access time (seconds)
- newTimes[1].tv_sec = sourceInfo.st_mtime; //modification time (seconds)
+ if (newAttrib)
+ {
+ newAttrib->fileSize = sourceInfo.st_size;
+ newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable as setFileTimeRaw() for consistency
+ //newAttrib->modificationTime = sourceInfo.st_mtime; //
+ newAttrib->sourceFileId = extractFileId(sourceInfo);
+ newAttrib->targetFileId = extractFileId(targetInfo);
+ }
- if (::futimes(fdTarget, newTimes) != 0)
- throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)), L"futimes", getLastError());
+ guardTarget.dismiss();
+ } //make sure target file handle is closed before setting modification time!
- if (newAttrib)
- {
- newAttrib->fileSize = sourceInfo.st_size;
- newAttrib->modificationTime = sourceInfo.st_mtime;
- newAttrib->sourceFileId = extractFileId(sourceInfo);
- newAttrib->targetFileId = extractFileId(targetInfo);
- }
+ zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} });
+ //same issue like on Linux: we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
+ //this triggers bugs on samba shares where the modification time is set to current time instead.
+ //https://sourceforge.net/p/freefilesync/discussion/help/thread/881357c0/
+ //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
+ setFileTimeRaw(targetFile, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::FOLLOW); //throw FileError
+ //sourceInfo.st_birthtime; -> only seconds-preicions
+ //sourceInfo.st_mtime; ->
guardTarget.dismiss();
}
#endif
diff --git a/zen/file_io.cpp b/zen/file_io.cpp
index c56d6ac0..00e33a60 100644
--- a/zen/file_io.cpp
+++ b/zen/file_io.cpp
@@ -32,11 +32,12 @@ Zstring getLockingProcessNames(const Zstring& filepath) //throw(), empty string
const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses);
const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString);
+ const wchar_t* processList = nullptr;
if (getLockingProcesses && freeString)
- if (const wchar_t* procList = getLockingProcesses(filepath.c_str()))
+ if (getLockingProcesses(filepath.c_str(), processList))
{
- ZEN_ON_SCOPE_EXIT(freeString(procList));
- return procList;
+ ZEN_ON_SCOPE_EXIT(freeString(processList));
+ return processList;
}
}
return Zstring();
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index f03cf464..2d652d2b 100644
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -5,23 +5,20 @@
// **************************************************************************
#include "file_traverser.h"
-#include "sys_error.h"
-#include "symlink_target.h"
+#include "file_error.h"
#include "int64.h"
#ifdef ZEN_WIN
- #include "win_ver.h"
#include "long_path_prefix.h"
#include "file_access.h"
- #include "dll.h"
- #include "FindFilePlus/find_file_plus.h"
-
+ #include "symlink_target.h"
#elif defined ZEN_MAC
- #include "osx_string.h"
+ #include "osx_string.h"
#endif
#if defined ZEN_LINUX || defined ZEN_MAC
- #include <cstddef> //required by GCC 4.8.1 to find ptrdiff_t
+ #include <cstddef> //offsetof
+ #include <unistd.h> //::pathconf()
#include <sys/stat.h>
#include <dirent.h>
#endif
@@ -29,491 +26,105 @@
using namespace zen;
-namespace
-{
-//implement "retry" in a generic way:
-
-template <class Command> inline //function object expecting to throw FileError if operation fails
-bool tryReportingDirError(Command cmd, zen::TraverseCallback& callback) //return "true" on success, "false" if error was ignored
-{
- for (size_t retryNumber = 0;; ++retryNumber)
- try
- {
- cmd(); //throw FileError
- return true;
- }
- catch (const FileError& e)
- {
- switch (callback.reportDirError(e.toString(), retryNumber))
- {
- case TraverseCallback::ON_ERROR_RETRY:
- break;
- case TraverseCallback::ON_ERROR_IGNORE:
- return false;
- }
- }
-}
-
-template <class Command> inline //function object expecting to throw FileError if operation fails
-bool tryReportingItemError(Command cmd, zen::TraverseCallback& callback, const Zchar* shortName) //return "true" on success, "false" if error was ignored
-{
- for (size_t retryNumber = 0;; ++retryNumber)
- try
- {
- cmd(); //throw FileError
- return true;
- }
- catch (const FileError& e)
- {
- switch (callback.reportItemError(e.toString(), retryNumber, shortName))
- {
- case TraverseCallback::ON_ERROR_RETRY:
- break;
- case TraverseCallback::ON_ERROR_IGNORE:
- return false;
- }
- }
-}
-
-
-#ifdef ZEN_WIN
-TraverseCallback::FileInfo getInfoFromFileSymlink(const Zstring& linkName) //throw FileError
-{
- //open handle to target of symbolic link
- HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(linkName).c_str(), //_In_ LPCTSTR lpFileName,
- 0, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes,
- //needed to open a directory -> keep it even if we expect to open a file! See comment below
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hFile == INVALID_HANDLE_VALUE)
- throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)), L"CreateFile", getLastError());
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
-
- BY_HANDLE_FILE_INFORMATION fileInfo = {};
- if (!::GetFileInformationByHandle(hFile, &fileInfo))
- throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)), L"GetFileInformationByHandle", getLastError());
-
- //a file symlink may incorrectly point to a directory, but both CreateFile() and GetFileInformationByHandle() will succeed and return garbage!
- //- if we did not use FILE_FLAG_BACKUP_SEMANTICS above, CreateFile() would error out with an even less helpful ERROR_ACCESS_DENIED!
- //- reinterpreting the link as a directory symlink would still fail during traversal, so just show an error here
- //- OTOH a directory symlink that points to a file fails immediately in ::FindFirstFile() with ERROR_DIRECTORY! -> nothing to do in this case
- if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)), formatSystemError(L"GetFileInformationByHandle", static_cast<DWORD>(ERROR_FILE_INVALID)));
-
- TraverseCallback::FileInfo output;
- output.fileSize = get64BitUInt(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
- output.lastWriteTime = filetimeToTimeT(fileInfo.ftLastWriteTime);
- output.id = extractFileId(fileInfo); //consider detection of moved files: allow for duplicate file ids, renaming affects symlink, not target, ...
- //output.symlinkInfo -> not filled here
- return output;
-}
-
-
-DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error or if serial is not supported!
-{
- //this works for:
- //- root paths "C:\", "D:\"
- //- network shares: \\share\dirname
- //- indirection: subst S: %USERPROFILE%
- // -> GetVolumePathName() + GetVolumeInformation() OTOH incorrectly resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try...
- const HANDLE hDir = ::CreateFile(zen::applyLongPathPrefix(pathName).c_str(), //_In_ LPCTSTR lpFileName,
- 0, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- // FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks!
- FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes,
- /*needed to open a directory*/
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hDir == INVALID_HANDLE_VALUE)
- return 0;
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir));
-
- BY_HANDLE_FILE_INFORMATION fileInfo = {};
- if (!::GetFileInformationByHandle(hDir, &fileInfo))
- return 0;
-
- return fileInfo.dwVolumeSerialNumber;
-}
-
-
-const bool isXpOrLater = winXpOrLater(); //VS2010 compiled DLLs are not supported on Win 2000: Popup dialog "DecodePointer not found"
-
-#define DEF_DLL_FUN(name) const auto name = isXpOrLater ? DllFun<findplus::FunType_##name>(findplus::getDllName(), findplus::funName_##name) : DllFun<findplus::FunType_##name>();
-DEF_DLL_FUN(openDir); //
-DEF_DLL_FUN(readDir); //load at startup: avoid pre C++11 static initialization MT issues
-DEF_DLL_FUN(closeDir); //
-
-/*
-Common C-style interface for Win32 FindFirstFile(), FindNextFile() and FileFilePlus openDir(), closeDir():
-struct TraverserPolicy //see "policy based design"
-{
-typedef ... DirHandle;
-typedef ... FindData;
-
-static DirHandle create(const Zstring& directory); //throw FileError - don't follow FindFirstFile() design: open handle only, *no* return of data!
-static void destroy(DirHandle hnd); //throw()
-
-static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError, NeedFallbackToWin32Traverser -> fallback to FindFirstFile()/FindNextFile()
-
-//FindData "member" functions
-static TraverseCallback::FileInfo extractFileInfo(const FindData& fileInfo, DWORD volumeSerial); //volumeSerial may be 0 if not available!
-static std::int64_t getModTime (const FindData& fileInfo);
-static const FILETIME& getModTimeRaw (const FindData& fileInfo); //yet another concession to DST hack
-static const FILETIME& getCreateTimeRaw(const FindData& fileInfo); //
-static const wchar_t* getItemName (const FindData& fileInfo);
-static bool isDirectory (const FindData& fileInfo);
-static bool isSymlink (const FindData& fileInfo);
-}
-
-Note: Win32 FindFirstFile(), FindNextFile() is a weaker abstraction than FileFilePlus openDir(), readDir(), closeDir() and Unix opendir(), closedir(), stat()
-*/
-
-
-struct Win32Traverser
+void zen::traverseFolder(const Zstring& dirPath,
+ const std::function<void (const FileInfo& fi)>& onFile,
+ const std::function<void (const DirInfo& di)>& onDir,
+ const std::function<void (const SymlinkInfo& si)>& onLink,
+ const std::function<void (const std::wstring& errorMsg)>& onError)
{
- struct DirHandle
- {
- DirHandle(HANDLE hnd, const WIN32_FIND_DATA& d) : searchHandle(hnd), haveData(true), data(d) {}
- explicit DirHandle(HANDLE hnd) : searchHandle(hnd), haveData(false) {}
-
- HANDLE searchHandle;
- bool haveData;
- WIN32_FIND_DATA data;
- };
-
- typedef WIN32_FIND_DATA FindData;
-
- static DirHandle create(const Zstring& dirpath) //throw FileError
+ try
{
- const Zstring& dirpathPf = appendSeparator(dirpath);
-
- WIN32_FIND_DATA fileData = {};
- HANDLE hnd = ::FindFirstFile(applyLongPathPrefix(dirpathPf + L'*').c_str(), &fileData);
- //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH
- if (hnd == INVALID_HANDLE_VALUE)
+#ifdef ZEN_WIN
+ WIN32_FIND_DATA findData = {};
+ HANDLE hDir = ::FindFirstFile(applyLongPathPrefix(appendSeparator(dirPath) + L'*').c_str(), &findData);
+ if (hDir == INVALID_HANDLE_VALUE)
{
const DWORD lastError = ::GetLastError(); //copy before making other system calls!
if (lastError == ERROR_FILE_NOT_FOUND)
{
//1. directory may not exist *or* 2. it is completely empty: not all directories contain "., .." entries, e.g. a drive's root directory; NetDrive
// -> FindFirstFile() is a nice example of violation of API design principle of single responsibility
- if (dirExists(dirpath)) //yes, a race-condition, still the best we can do
- return DirHandle(hnd);
+ if (dirExists(dirPath)) //yes, a race-condition, still the best we can do
+ return;
}
- throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"FindFirstFile", lastError);
- }
- return DirHandle(hnd, fileData);
- }
-
- static void destroy(const DirHandle& hnd) { ::FindClose(hnd.searchHandle); } //throw()
-
- static bool getEntry(DirHandle& hnd, const Zstring& dirpath, FindData& fileInfo) //throw FileError
- {
- if (hnd.searchHandle == INVALID_HANDLE_VALUE) //handle special case of "truly empty directories"
- return false;
-
- if (hnd.haveData)
- {
- hnd.haveData = false;
- ::memcpy(&fileInfo, &hnd.data, sizeof(fileInfo));
- return true;
- }
-
- if (!::FindNextFile(hnd.searchHandle, &fileInfo))
- {
- const DWORD lastError = ::GetLastError(); //copy before making other system calls!
- if (lastError == ERROR_NO_MORE_FILES) //not an error situation
- return false;
- //else we have a problem... report it:
- throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"FindNextFile", lastError);
- }
- return true;
- }
-
- static TraverseCallback::FileInfo extractFileInfo(const FindData& fileInfo, DWORD volumeSerial)
- {
- TraverseCallback::FileInfo output;
- output.fileSize = get64BitUInt(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
- output.lastWriteTime = getModTime(fileInfo);
- //output.id = FileId();
- //output.symlinkInfo = nullptr;
- return output;
- }
-
- static std::int64_t getModTime (const FindData& fileInfo) { return filetimeToTimeT(fileInfo.ftLastWriteTime); }
- static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.ftLastWriteTime; }
- static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.ftCreationTime; }
- static const wchar_t* getItemName (const FindData& fileInfo) { return fileInfo.cFileName; }
- static bool isDirectory (const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; }
- static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo); } //[!] keep namespace
-};
-
-
-class NeedFallbackToWin32Traverser {}; //special exception class
-
-
-struct FilePlusTraverser
-{
- struct DirHandle
- {
- explicit DirHandle(findplus::FindHandle hnd) : searchHandle(hnd) {}
-
- findplus::FindHandle searchHandle;
- };
-
- typedef findplus::FileInformation FindData;
-
- static DirHandle create(const Zstring& dirpath) //throw FileError
- {
- const findplus::FindHandle hnd = ::openDir(applyLongPathPrefix(dirpath).c_str());
- if (!hnd)
- throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"openDir", getLastError());
-
- return DirHandle(hnd);
- }
-
- static void destroy(DirHandle hnd) { ::closeDir(hnd.searchHandle); } //throw()
-
- static bool getEntry(DirHandle hnd, const Zstring& dirpath, FindData& fileInfo) //throw FileError, NeedFallbackToWin32Traverser
- {
- if (!::readDir(hnd.searchHandle, fileInfo))
- {
- const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls!
- if (lastError == ERROR_NO_MORE_FILES) //not an error situation
- return false;
-
- /*
- fallback to default directory query method, if FileIdBothDirectoryInformation is not properly implemented
- this is required for NetDrive mounted Webdav, e.g. www.box.net and NT4, 2000 remote drives, et al.
- */
- if (lastError == ERROR_NOT_SUPPORTED)
- throw NeedFallbackToWin32Traverser();
- //fallback should apply to whole directory sub-tree! => client needs to handle duplicate file notifications!
-
- //else we have a problem... report it:
- throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"readDir", lastError);
+ throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPath)), L"FindFirstFile", lastError);
}
+ ZEN_ON_SCOPE_EXIT(::FindClose(hDir));
- return true;
- }
-
- static TraverseCallback::FileInfo extractFileInfo(const FindData& fileInfo, DWORD volumeSerial)
- {
- TraverseCallback::FileInfo output;
- output.fileSize = fileInfo.fileSize;
- output.lastWriteTime = getModTime(fileInfo);
- output.id = extractFileId(volumeSerial, fileInfo.fileId);
- //output.symlinkInfo = nullptr;
- return output;
- }
-
- static std::int64_t getModTime (const FindData& fileInfo) { return filetimeToTimeT(fileInfo.lastWriteTime); }
- static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.lastWriteTime; }
- static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.creationTime; }
- static const wchar_t* getItemName (const FindData& fileInfo) { return fileInfo.shortName; }
- static bool isDirectory (const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; }
- static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo.fileAttributes, fileInfo.reparseTag); } //[!] keep namespace
-};
-
-
-class DirTraverser
-{
-public:
- static void execute(const Zstring& baseDirectory, TraverseCallback& sink)
- {
- DirTraverser(baseDirectory, sink);
- }
-
-private:
- DirTraverser(const Zstring& baseDirectory, TraverseCallback& sink);
- DirTraverser (const DirTraverser&) = delete;
- DirTraverser& operator=(const DirTraverser&) = delete;
-
- template <class Trav>
- void traverse(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial);
-
- template <class Trav>
- void traverseWithException(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial /*may be 0!*/); //throw FileError, NeedFallbackToWin32Traverser
-};
-
-
-template <> inline
-void DirTraverser::traverse<Win32Traverser>(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial)
-{
- tryReportingDirError([&]
- {
- traverseWithException<Win32Traverser>(dirpath, sink, 0); //throw FileError
- }, sink);
-}
-
-
-template <> inline
-void DirTraverser::traverse<FilePlusTraverser>(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial)
-{
- try
- {
- tryReportingDirError([&]
- {
- traverseWithException<FilePlusTraverser>(dirpath, sink, volumeSerial); //throw FileError, NeedFallbackToWin32Traverser
- }, sink);
- }
- catch (NeedFallbackToWin32Traverser&) { traverse<Win32Traverser>(dirpath, sink, 0); }
-}
-
-
-inline
-DirTraverser::DirTraverser(const Zstring& baseDirectory, TraverseCallback& sink)
-{
- try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail)
- {
- activatePrivilege(SE_BACKUP_NAME); //throw FileError
- }
- catch (FileError&) {} //don't cause issues in user mode
-
- if (::openDir && ::readDir && ::closeDir)
- traverse<FilePlusTraverser>(baseDirectory, sink, retrieveVolumeSerial(baseDirectory)); //retrieveVolumeSerial returns 0 on error
- else //fallback
- traverse<Win32Traverser>(baseDirectory, sink, 0);
-}
-
-
-template <class Trav>
-void DirTraverser::traverseWithException(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial /*may be 0!*/) //throw FileError, NeedFallbackToWin32Traverser
-{
- //no need to check for endless recursion: Windows seems to have an internal path limit of about 700 chars
-
- typename Trav::DirHandle searchHandle = Trav::create(dirpath); //throw FileError
- ZEN_ON_SCOPE_EXIT(Trav::destroy(searchHandle));
-
- typename Trav::FindData findData = {};
-
- while (Trav::getEntry(searchHandle, dirpath, findData)) //throw FileError, NeedFallbackToWin32Traverser
- //don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx
- {
- //skip "." and ".."
- const Zchar* const shortName = Trav::getItemName(findData);
- if (shortName[0] == L'.' &&
- (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0)))
- continue;
+ bool firstIteration = true;
+ for (;;)
+ {
+ if (firstIteration) //keep ::FindNextFile at the start of the for-loop to support "continue"!
+ firstIteration = false;
+ else
+ if (!::FindNextFile(hDir, &findData))
+ {
+ const DWORD lastError = ::GetLastError();
+ if (lastError == ERROR_NO_MORE_FILES) //not an error situation
+ return;
+ //else we have a problem... report it:
+ throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"FindNextFile", lastError);
+ }
- const Zstring& itempath = appendSeparator(dirpath) + shortName;
+ //skip "." and ".."
+ const Zchar* const shortName = findData.cFileName;
+ if (shortName[0] == L'.' &&
+ (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0)))
+ continue;
- if (Trav::isSymlink(findData)) //check first!
- {
- TraverseCallback::SymlinkInfo linkInfo;
- linkInfo.lastWriteTime = Trav::getModTime (findData);
+ const Zstring& itempath = appendSeparator(dirPath) + shortName;
- switch (sink.onSymlink(shortName, itempath, linkInfo))
+ if (zen::isSymlink(findData)) //check first!
{
- case TraverseCallback::LINK_FOLLOW:
- if (Trav::isDirectory(findData))
- {
- if (TraverseCallback* trav = sink.onDir(shortName, itempath))
- {
- ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav));
- traverse<Trav>(itempath, *trav, retrieveVolumeSerial(itempath)); //symlink may link to different volume => redetermine volume serial!
- }
- }
- else //a file
- {
- TraverseCallback::FileInfo targetInfo;
- const bool validLink = tryReportingItemError([&] //try to resolve symlink (and report error on failure!!!)
- {
- targetInfo = getInfoFromFileSymlink(itempath); //throw FileError
- targetInfo.symlinkInfo = &linkInfo;
- }, sink, shortName);
-
- if (validLink)
- sink.onFile(shortName, itempath, targetInfo);
- // else //broken symlink -> ignore: it's client's responsibility to handle error!
- }
- break;
-
- case TraverseCallback::LINK_SKIP:
- break;
+ if (onLink)
+ onLink({ shortName, itempath, filetimeToTimeT(findData.ftLastWriteTime) });
}
- }
- else if (Trav::isDirectory(findData))
- {
- if (TraverseCallback* trav = sink.onDir(shortName, itempath))
+ else if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
- ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav));
- traverse<Trav>(itempath, *trav, volumeSerial);
+ if (onDir)
+ onDir({ shortName, itempath });
+ }
+ else //a file
+ {
+ if (onFile)
+ onFile({ shortName, itempath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) });
}
}
- else //a file
- {
- const TraverseCallback::FileInfo fileInfo = Trav::extractFileInfo(findData, volumeSerial);
- sink.onFile(shortName, itempath, fileInfo);
- }
- }
-}
-
#elif defined ZEN_LINUX || defined ZEN_MAC
-class DirTraverser
-{
-public:
- static void execute(const Zstring& baseDirectory, TraverseCallback& sink)
- {
- DirTraverser(baseDirectory, sink);
- }
-
-private:
- DirTraverser(const Zstring& baseDirectory, zen::TraverseCallback& sink)
- {
- const Zstring directoryFormatted = //remove trailing slash
- baseDirectory.size() > 1 && endsWith(baseDirectory, FILE_NAME_SEPARATOR) ? //exception: allow '/'
- beforeLast(baseDirectory, FILE_NAME_SEPARATOR) :
- baseDirectory;
+ const Zstring dirPathFmt = //remove trailing slash
+ dirPath.size() > 1 && endsWith(dirPath, FILE_NAME_SEPARATOR) ? //exception: allow '/'
+ beforeLast(dirPath, FILE_NAME_SEPARATOR) :
+ dirPath;
/* quote: "Since POSIX.1 does not specify the size of the d_name field, and other nonstandard fields may precede
that field within the dirent structure, portable applications that use readdir_r() should allocate
the buffer whose address is passed in entry as follows:
- len = offsetof(struct dirent, d_name) + pathconf(dirpath, _PC_NAME_MAX) + 1
+ len = offsetof(struct dirent, d_name) + pathconf(dirPath, _PC_NAME_MAX) + 1
entryp = malloc(len); */
- const size_t nameMax = std::max<long>(::pathconf(directoryFormatted.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1)
- buffer.resize(offsetof(struct ::dirent, d_name) + nameMax + 1);
-
- traverse(directoryFormatted, sink);
- }
-
- DirTraverser (const DirTraverser&) = delete;
- DirTraverser& operator=(const DirTraverser&) = delete;
-
- void traverse(const Zstring& dirpath, TraverseCallback& sink)
- {
- tryReportingDirError([&]
- {
- traverseWithException(dirpath, sink); //throw FileError
- }, sink);
- }
-
- void traverseWithException(const Zstring& dirpath, TraverseCallback& sink) //throw FileError
- {
- //no need to check for endless recursion: Linux has a fixed limit on the number of symbolic links in a path
+ const size_t nameMax = std::max<long>(::pathconf(dirPathFmt.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1)
+ std::vector<char> buffer(offsetof(struct ::dirent, d_name) + nameMax + 1);
+#ifdef ZEN_MAC
+ std::vector<char> bufferUtfDecomposed;
+#endif
- DIR* dirObj = ::opendir(dirpath.c_str()); //directory must NOT end with path separator, except "/"
+ DIR* dirObj = ::opendir(dirPathFmt.c_str()); //directory must NOT end with path separator, except "/"
if (!dirObj)
- throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"opendir", getLastError());
+ throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPathFmt)), L"opendir", getLastError());
ZEN_ON_SCOPE_EXIT(::closedir(dirObj)); //never close nullptr handles! -> crash
for (;;)
{
struct ::dirent* dirEntry = nullptr;
if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0)
- throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"readdir_r", getLastError());
+ throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPathFmt)), L"readdir_r", getLastError());
//don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx
if (!dirEntry) //no more items
return;
//don't return "." and ".."
- const char* shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"!
+ const char* shortName = dirEntry->d_name;
if (shortName[0] == '.' &&
(shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0)))
continue;
@@ -531,83 +142,41 @@ private:
{
bufferUtfDecomposed.resize(lenMax);
if (::CFStringGetFileSystemRepresentation(cfStr, &bufferUtfDecomposed[0], lenMax)) //get decomposed UTF form (verified!) despite ambiguous documentation
- shortName = &bufferUtfDecomposed[0]; //attention: => don't access "shortName" after recursion in "traverse"!
+ shortName = &bufferUtfDecomposed[0];
}
}
//const char* sampleDecomposed = "\x6f\xcc\x81.txt";
//const char* samplePrecomposed = "\xc3\xb3.txt";
#endif
- const Zstring& itempath = appendSeparator(dirpath) + shortName;
+ const Zstring& itempath = appendSeparator(dirPathFmt) + shortName;
struct ::stat statData = {};
- if (!tryReportingItemError([&]
- {
- if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks
- throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(itempath)), L"lstat", getLastError());
- }, sink, shortName))
- continue; //ignore error: skip file
+ try
+ {
+ if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks
+ throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(itempath)), L"lstat", getLastError());
+ }
+ catch (const FileError& e)
+ {
+ if (onError)
+ onError(e.toString());
+ continue; //ignore error: skip file
+ }
if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks!
{
- TraverseCallback::SymlinkInfo linkInfo;
- linkInfo.lastWriteTime = statData.st_mtime; //UTC time (ANSI C format); unit: 1 second
-
- switch (sink.onSymlink(shortName, itempath, linkInfo))
- {
- case TraverseCallback::LINK_FOLLOW:
- {
- //try to resolve symlink (and report error on failure!!!)
- struct ::stat statDataTrg = {};
- bool validLink = tryReportingItemError([&]
- {
- if (::stat(itempath.c_str(), &statDataTrg) != 0)
- throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(itempath)), L"stat", getLastError());
- }, sink, shortName);
-
- if (validLink)
- {
- if (S_ISDIR(statDataTrg.st_mode)) //a directory
- {
- if (TraverseCallback* trav = sink.onDir(shortName, itempath))
- {
- ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav));
- traverse(itempath, *trav);
- }
- }
- else //a file or named pipe, ect.
- {
- TraverseCallback::FileInfo fileInfo;
- fileInfo.fileSize = statDataTrg.st_size;
- fileInfo.lastWriteTime = statDataTrg.st_mtime; //UTC time (time_t format); unit: 1 second
- fileInfo.id = extractFileId(statDataTrg);
- fileInfo.symlinkInfo = &linkInfo;
- sink.onFile(shortName, itempath, fileInfo);
- }
- }
- // else //broken symlink -> ignore: it's client's responsibility to handle error!
- }
- break;
-
- case TraverseCallback::LINK_SKIP:
- break;
- }
+ if (onLink)
+ onLink({ shortName, itempath, statData.st_mtime});
}
else if (S_ISDIR(statData.st_mode)) //a directory
{
- if (TraverseCallback* trav = sink.onDir(shortName, itempath))
- {
- ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav));
- traverse(itempath, *trav);
- }
+ if (onDir)
+ onDir({ shortName, itempath });
}
else //a file or named pipe, ect.
{
- TraverseCallback::FileInfo fileInfo;
- fileInfo.fileSize = statData.st_size;
- fileInfo.lastWriteTime = statData.st_mtime; //UTC time (time_t format); unit: 1 second
- fileInfo.id = extractFileId(statData);
-
- sink.onFile(shortName, itempath, fileInfo);
+ if (onFile)
+ onFile({ shortName, itempath, makeUnsigned(statData.st_size), statData.st_mtime });
}
/*
It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios:
@@ -617,15 +186,11 @@ private:
However an "open" on a pipe will block (https://sourceforge.net/p/freefilesync/bugs/221/), so the copy routines need to be smarter!!
*/
}
- }
-
- std::vector<char> buffer;
-#ifdef ZEN_MAC
- std::vector<char> bufferUtfDecomposed;
-#endif
-};
#endif
+ }
+ catch (const FileError& e)
+ {
+ if (onError)
+ onError(e.toString());
+ }
}
-
-
-void zen::traverseFolder(const Zstring& dirpath, TraverseCallback& sink) { DirTraverser::execute(dirpath, sink); }
diff --git a/zen/file_traverser.h b/zen/file_traverser.h
index 174503b5..9cba9e58 100644
--- a/zen/file_traverser.h
+++ b/zen/file_traverser.h
@@ -4,64 +4,43 @@
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
// **************************************************************************
-#ifndef FILETRAVERSER_H_INCLUDED
-#define FILETRAVERSER_H_INCLUDED
+#ifndef FOLDER_TRAVERSER_H_INCLUDED_3127463214871234
+#define FOLDER_TRAVERSER_H_INCLUDED_3127463214871234
#include <cstdint>
+#include <functional>
#include "zstring.h"
-#include "file_id_def.h"
-
-//advanced file traverser returning metadata and hierarchical information on files and directories
namespace zen
{
-struct TraverseCallback
+struct FileInfo
{
- virtual ~TraverseCallback() {}
-
- struct SymlinkInfo
- {
- SymlinkInfo() : lastWriteTime() {}
-
- std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
- };
-
- struct FileInfo
- {
- FileInfo() : fileSize(), lastWriteTime(), symlinkInfo() {}
-
- std::uint64_t fileSize; //unit: bytes!
- std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
- FileId id; //optional: initial if not supported!
- const SymlinkInfo* symlinkInfo; //only filled if file is a followed symlink
- };
-
- enum HandleLink
- {
- LINK_FOLLOW, //dereferences link, then calls "onDir()" or "onFile()"
- LINK_SKIP
- };
-
- enum HandleError
- {
- ON_ERROR_RETRY,
- ON_ERROR_IGNORE
- };
+ const Zchar* shortName;
+ const Zstring& fullPath;
+ std::uint64_t fileSize; //[bytes]
+ std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
+};
- virtual void onFile (const Zchar* shortName, const Zstring& filepath, const FileInfo& details) = 0;
- virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) = 0;
- virtual TraverseCallback* onDir (const Zchar* shortName, const Zstring& dirpath) = 0;
- //nullptr: ignore directory, non-nullptr: traverse into using the (new) callback => implement releaseDirTraverser() if necessary!
- virtual void releaseDirTraverser(TraverseCallback* trav) {}
+struct DirInfo
+{
+ const Zchar* shortName;
+ const Zstring& fullPath;
+};
- virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) = 0; //failed directory traversal -> consider directory data at current level as incomplete!
- virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) = 0; //failed to get data for single file/dir/symlink only!
+struct SymlinkInfo
+{
+ const Zchar* shortName;
+ const Zstring& fullPath;
+ std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
};
-//custom traverser with detail information about files
-//- client needs to handle duplicate file reports! (FilePlusTraverser fallback, retrying to read directory contents, ...)
-//- directory may end with PATH_SEPARATOR
-void traverseFolder(const Zstring& dirpath, TraverseCallback& sink); //noexcept
+//- non-recursive
+//- directory path may end with PATH_SEPARATOR
+void traverseFolder(const Zstring& dirPath, //noexcept
+ const std::function<void (const FileInfo& fi)>& onFile, //
+ const std::function<void (const DirInfo& di)>& onDir, //optional
+ const std::function<void (const SymlinkInfo& si)>& onLink, //
+ const std::function<void (const std::wstring& errorMsg)>& onError); //
}
-#endif // FILETRAVERSER_H_INCLUDED
+#endif //FOLDER_TRAVERSER_H_INCLUDED_3127463214871234
diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h
index d2289330..19b838ef 100644
--- a/zen/long_path_prefix.h
+++ b/zen/long_path_prefix.h
@@ -47,15 +47,6 @@ As used by GetModuleFileNameEx() and symlinks (FSCTL_GET_REPARSE_POINT):
-
-
-
-
-
-
-
-
-
//################## implementation ##################
//there are two flavors of long path prefix: one for UNC paths, one for regular paths
@@ -128,11 +119,11 @@ Zstring zen::ntPathToWin32Path(const Zstring& path) //noexcept
if (bufSize > 0)
{
std::vector<wchar_t> buf(bufSize);
- DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName,
+ const DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName,
&buf[0], //_Out_opt_ LPTSTR lpBuffer,
bufSize); //_In_ DWORD nSize
- if (charsWritten != 0 && charsWritten < bufSize)
+ if (0 < charsWritten && charsWritten < bufSize)
return replaceCpy(path, L"\\SystemRoot\\", appendSeparator(Zstring(&buf[0], charsWritten)), false);
}
}
diff --git a/zen/notify_removal.cpp b/zen/notify_removal.cpp
index 37f305c9..f528e81b 100644
--- a/zen/notify_removal.cpp
+++ b/zen/notify_removal.cpp
@@ -86,7 +86,7 @@ MessageProvider::MessageProvider() :
if (::RegisterClass(&wc) == 0)
throwFileError(_("Unable to register to receive system messages."), L"RegisterClass", getLastError());
- ScopeGuard guardClass = makeGuard([&] { ::UnregisterClass(dummyClassName, hMainModule); });
+ ScopeGuard guardConstructor = zen::makeGuard([&] { this->~MessageProvider(); });
//create dummy-window
windowHandle = ::CreateWindow(dummyClassName, //_In_opt_ LPCTSTR lpClassName,
@@ -106,14 +106,15 @@ MessageProvider::MessageProvider() :
if (::SetWindowLongPtr(windowHandle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)) == 0 && ::GetLastError() != ERROR_SUCCESS)
throwFileError(_("Unable to register to receive system messages."), L"SetWindowLongPtr", ::GetLastError());
- guardClass.dismiss();
+ guardConstructor.dismiss();
}
MessageProvider::~MessageProvider()
{
//clean-up in reverse order
- ::DestroyWindow(windowHandle);
+ if (windowHandle)
+ ::DestroyWindow(windowHandle);
::UnregisterClass(dummyClassName, //LPCTSTR lpClassName OR ATOM in low-order word!
hMainModule); //HINSTANCE hInstance
}
diff --git a/zen/recycler.cpp b/zen/recycler.cpp
index 649bbb8e..3b5ac421 100644
--- a/zen/recycler.cpp
+++ b/zen/recycler.cpp
@@ -143,9 +143,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError
return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it!
#ifdef ZEN_WIN
- std::vector<Zstring> itempaths;
- itempaths.push_back(itempath);
- recycleOrDelete(itempaths, nullptr); //throw FileError
+ recycleOrDelete({ itempath }, nullptr); //throw FileError
#elif defined ZEN_LINUX
GFile* file = ::g_file_new_for_path(itempath.c_str()); //never fails according to docu
@@ -231,7 +229,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError
#ifdef ZEN_WIN
-bool zen::recycleBinExists(const Zstring& pathName, const std::function<void ()>& onUpdateGui) //throw FileError
+bool zen::recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui) //throw FileError
{
if (vistaOrLater())
{
@@ -240,24 +238,24 @@ bool zen::recycleBinExists(const Zstring& pathName, const std::function<void ()>
const DllFun<FunType_getLastErrorMessage> getLastErrorMessage(getDllName(), funName_getLastErrorMessage);
if (!getRecycleBinStatus || !getLastErrorMessage)
- throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(pathName)),
+ throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(dirpath)),
replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(getDllName())));
bool hasRecycler = false;
- if (!getRecycleBinStatus(pathName.c_str(), hasRecycler))
- throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(pathName)), getLastErrorMessage());
+ if (!getRecycleBinStatus(dirpath.c_str(), hasRecycler))
+ throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(dirpath)), getLastErrorMessage());
return hasRecycler;
}
else
{
//excessive runtime if recycle bin exists, is full and drive is slow:
- auto ft = async([pathName]()
+ auto ft = async([dirpath]()
{
SHQUERYRBINFO recInfo = {};
recInfo.cbSize = sizeof(recInfo);
- return ::SHQueryRecycleBin(pathName.c_str(), //__in_opt LPCTSTR pszRootPath,
- &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo
+ return ::SHQueryRecycleBin(dirpath.c_str(), //__in_opt LPCTSTR pszRootPath,
+ &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo
});
while (!ft.timed_wait(boost::posix_time::milliseconds(50)))
@@ -278,7 +276,7 @@ bool zen::recycleBinExists(const Zstring& pathName, const std::function<void ()>
// -> not upward-compatible, wrong result for subst-alias: recycler assumed existing, although it is not!
//5. alternative approach a'la Raymond Chen: http://blogs.msdn.com/b/oldnewthing/archive/2008/09/18/8956382.aspx
- //caveat: might not be reliable, e.g. "subst"-alias of volume contains "$Recycle.Bin" although it is not available!
+ //caveat: might not be reliable, e.g. "subst"-alias of volume contains "$Recycle.Bin" although recycler is not available!
/*
Zstring rootPathPf = appendSeparator(&buffer[0]);
diff --git a/zen/recycler.h b/zen/recycler.h
index d86fbb12..2319f7b6 100644
--- a/zen/recycler.h
+++ b/zen/recycler.h
@@ -35,8 +35,8 @@ bool recycleOrDelete(const Zstring& itempath); //throw FileError, return "true"
#ifdef ZEN_WIN
-//can take a long time if recycle bin is full and drive is slow!!! => buffer volume ids!
-bool recycleBinExists(const Zstring& pathName, const std::function<void ()>& onUpdateGui); //throw FileError
+//can take a long time if recycle bin is full and drive is slow!!! => buffer!
+bool recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui); //throw FileError
void recycleOrDelete(const std::vector<Zstring>& filepaths, //throw FileError, return "true" if file/dir was actually deleted
const std::function<void (const Zstring& currentItem)>& notifyDeletionStatus); //optional; currentItem may be empty
diff --git a/zen/stl_tools.h b/zen/stl_tools.h
index d00fc732..d2d4ee1a 100644
--- a/zen/stl_tools.h
+++ b/zen/stl_tools.h
@@ -80,7 +80,7 @@ void vector_append(V& vec, const W& vec2)
template <class V, class W> inline
void set_append(V& s, const W& s2)
{
- s.insert(s2.begin(), s2.end());
+ s.insert(s2.begin(), s2.end());
}
diff --git a/zen/win_ver.h b/zen/win_ver.h
index 97b6d7e1..9cf792f2 100644
--- a/zen/win_ver.h
+++ b/zen/win_ver.h
@@ -125,7 +125,7 @@ inline
bool running64BitWindows() //http://blogs.msdn.com/b/oldnewthing/archive/2005/02/01/364563.aspx
{
static_assert(zen::is32BitBuild || zen::is64BitBuild, "");
- return is64BitBuild || runningWOW64(); //should we bother to make this a compile-time check?
+ return is64BitBuild || runningWOW64(); //should we bother to make this a compile-time check for the first case?
}
}
bgstack15