summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
authorDaniel Wilhelm <shieldwed@outlook.com>2017-01-09 10:54:11 +0100
committerDaniel Wilhelm <shieldwed@outlook.com>2017-01-09 10:54:11 +0100
commit6011596a2f247bde24095e985f6e2547e4b191ef (patch)
tree296003a2c8952f41b2998653ba02911ba2b83b0a /zen
parent8.7 (diff)
downloadFreeFileSync-6011596a2f247bde24095e985f6e2547e4b191ef.tar.gz
FreeFileSync-6011596a2f247bde24095e985f6e2547e4b191ef.tar.bz2
FreeFileSync-6011596a2f247bde24095e985f6e2547e4b191ef.zip
8.8
Diffstat (limited to 'zen')
-rw-r--r--zen/dir_watcher.cpp2
-rw-r--r--zen/file_access.cpp620
-rw-r--r--zen/file_access.h42
-rw-r--r--zen/file_error.h2
-rw-r--r--zen/file_traverser.cpp42
-rw-r--r--zen/file_traverser.h15
-rw-r--r--zen/fixed_list.h6
-rw-r--r--zen/recycler.cpp104
-rw-r--r--zen/recycler.h6
-rw-r--r--zen/serialize.h2
-rw-r--r--zen/shell_execute.h2
-rw-r--r--zen/thread.h4
12 files changed, 511 insertions, 336 deletions
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp
index 98190bba..79e8aeb7 100644
--- a/zen/dir_watcher.cpp
+++ b/zen/dir_watcher.cpp
@@ -396,7 +396,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
traverse = [&traverse, &fullFolderList](const Zstring& path)
{
traverseFolder(path, nullptr,
- [&](const DirInfo& di ) { fullFolderList.push_back(di.fullPath); traverse(di.fullPath); },
+ [&](const FolderInfo& fi ) { fullFolderList.push_back(fi.fullPath); traverse(fi.fullPath); },
nullptr, //don't traverse into symlinks (analog to windows build)
[&](const std::wstring& errorMsg) { throw FileError(errorMsg); });
};
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index bad1b60d..ac68330e 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -44,7 +44,140 @@
using namespace zen;
-bool zen::fileExists(const Zstring& filePath)
+Opt<Zstring> zen::getParentFolderPath(const Zstring& itemPath)
+{
+#ifdef ZEN_WIN
+ assert(startsWith(itemPath, L"\\\\") || //we do NOT support relative paths!
+ (itemPath.size() >= 3 && isAlpha(itemPath[0]) && itemPath[1] == L':' && itemPath[2] == L'\\'));
+
+ //remove trailing separator (even for C:\ root directories)
+ const Zstring itemPathFmt = endsWith(itemPath, FILE_NAME_SEPARATOR) ?
+ beforeLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) :
+ itemPath;
+
+ if (startsWith(itemPathFmt, L"\\\\")) //UNC-name, e.g. \\server-name\share
+ if (std::count(itemPathFmt.begin(), itemPathFmt.end(), FILE_NAME_SEPARATOR) <= 3)
+ return NoValue();
+
+ const Zstring parentDir = beforeLast(itemPathFmt, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
+ if (parentDir.empty())
+ return NoValue();
+ if (parentDir.size() == 2 && isAlpha(parentDir[0]) && parentDir[1] == L':')
+ return appendSeparator(parentDir);
+
+#elif defined ZEN_LINUX || defined ZEN_MAC
+ assert(startsWith(itemPath, L"/")); //we do NOT support relative paths!
+
+ if (itemPath == "/")
+ return NoValue();
+
+ const Zstring parentDir = beforeLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
+ if (parentDir.empty())
+ return Zstring("/");
+#endif
+ return parentDir;
+}
+
+
+ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
+{
+#ifdef ZEN_WIN
+ const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str());
+ if (attr == INVALID_FILE_ATTRIBUTES)
+ {
+ const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
+ if (ec == ERROR_PATH_NOT_FOUND || //
+ ec == ERROR_FILE_NOT_FOUND || //perf: short circuit for common "not existing" error codes
+ ec == ERROR_BAD_NETPATH || //
+ ec == ERROR_BAD_NET_NAME) //
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileAttributes");
+ }
+
+ if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
+ return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 ? ItemType::FOLDER : ItemType::FILE;
+
+ //handle obscure file permission problem where ::GetFileAttributes() fails with ERROR_ACCESS_DENIED or ERROR_SHARING_VIOLATION
+ //while parent directory traversal is successful: e.g. "C:\pagefile.sys"
+ WIN32_FIND_DATA itemInfo = {};
+ const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(itemPath).c_str(), &itemInfo);
+ if (searchHandle == INVALID_HANDLE_VALUE)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"FindFirstFile");
+ ::FindClose(searchHandle);
+
+ if (isSymlink(itemInfo)) //not every FILE_ATTRIBUTE_REPARSE_POINT is a symlink!!!
+ return ItemType::SYMLINK;
+ return (itemInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ? ItemType::FOLDER : ItemType::FILE;
+
+#elif defined ZEN_LINUX || defined ZEN_MAC
+ struct ::stat itemInfo = {};
+ if (::lstat(itemPath.c_str(), &itemInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+
+ if (S_ISLNK(itemInfo.st_mode))
+ return ItemType::SYMLINK;
+ if (S_ISDIR(itemInfo.st_mode))
+ return ItemType::FOLDER;
+ return ItemType::FILE; //S_ISREG || S_ISCHR || S_ISBLK || S_ISFIFO || S_ISSOCK
+#endif
+}
+
+
+PathDetails zen::getPathDetails(const Zstring& itemPath) //throw FileError
+{
+ const Opt<Zstring> parentPath = getParentFolderPath(itemPath);
+ try
+ {
+ return { getItemType(itemPath), itemPath, {} }; //throw FileError
+ }
+ catch (FileError&)
+ {
+ if (!parentPath) //device root
+ throw;
+ //else: let's dig deeper... don't bother checking Win32 codes; e.g. not existing item may have the codes:
+ // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE,
+ // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable
+ }
+ const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
+ assert(!itemName.empty());
+
+ PathDetails pd = getPathDetails(*parentPath); //throw FileError
+ if (!pd.relPath.empty())
+ {
+ pd.relPath.push_back(itemName);
+ return { pd.existingType, pd.existingPath, pd.relPath };
+ }
+
+ try
+ {
+ traverseFolder(*parentPath,
+ [&](const FileInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FILE; },
+ [&](const FolderInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FOLDER; },
+ [&](const SymlinkInfo& si) { if (equalFilePath(si.itemName, itemName)) throw ItemType::SYMLINK; },
+ [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
+
+ return { pd.existingType, *parentPath, { itemName } }; //throw FileError
+ }
+ catch (const ItemType& type) { return { type, itemPath, {} }; } //yes, exceptions for control-flow are bad design... but, but...
+ //we're not CPU-bound here and finding the item after getItemType() previously failed is exceptional (even C:\pagefile.sys should be found)
+}
+
+
+Opt<ItemType> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError
+{
+ const PathDetails pd = getPathDetails(itemPath); //throw FileError
+#ifndef NDEBUG
+ Zstring reconstructedPath = pd.existingPath;
+ for (const Zstring& itemName : pd.relPath)
+ reconstructedPath += endsWith(reconstructedPath, FILE_NAME_SEPARATOR) ? itemName : FILE_NAME_SEPARATOR + itemName;
+ assert(itemPath == reconstructedPath);
+#endif
+ if (pd.relPath.empty())
+ return pd.existingType;
+ return NoValue();
+}
+
+
+bool zen::fileAvailable(const Zstring& filePath) //noexcept
{
//symbolic links (broken or not) are also treated as existing files!
#ifdef ZEN_WIN
@@ -61,13 +194,13 @@ bool zen::fileExists(const Zstring& filePath)
}
-bool zen::dirExists(const Zstring& dirPath)
+bool zen::dirAvailable(const Zstring& dirPath) //noexcept
{
//symbolic links (broken or not) are also treated as existing directories!
#ifdef ZEN_WIN
const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirPath).c_str());
if (attr != INVALID_FILE_ATTRIBUTES)
- return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for (dir-)symlinks also
+ return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for ((broken) dir-)symlinks also
#elif defined ZEN_LINUX || defined ZEN_MAC
struct ::stat dirInfo = {};
@@ -78,54 +211,39 @@ bool zen::dirExists(const Zstring& dirPath)
}
-bool zen::symlinkExists(const Zstring& linkPath)
+warn_static("remove following test functions after refactoring")
+
+
+
+bool zen::fileExists(const Zstring& filePath)
{
+ //symbolic links (broken or not) are also treated as existing files!
#ifdef ZEN_WIN
- WIN32_FIND_DATA linkInfo = {};
- const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(linkPath).c_str(), &linkInfo);
- if (searchHandle != INVALID_HANDLE_VALUE)
- {
- ::FindClose(searchHandle);
- return isSymlink(linkInfo);
- }
+ const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str());
+ if (attr != INVALID_FILE_ATTRIBUTES)
+ return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also
#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat linkInfo = {};
- if (::lstat(linkPath.c_str(), &linkInfo) == 0)
- return S_ISLNK(linkInfo.st_mode);
+ struct ::stat fileInfo = {};
+ if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks!
+ return S_ISREG(fileInfo.st_mode);
#endif
return false;
}
-bool zen::somethingExists(const Zstring& itemPath)
+bool zen::dirExists(const Zstring& dirPath)
{
+ //symbolic links (broken or not) are also treated as existing directories!
#ifdef ZEN_WIN
- const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str());
+ const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirPath).c_str());
if (attr != INVALID_FILE_ATTRIBUTES)
- return true;
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
-
- //handle obscure file permission problem where ::GetFileAttributes() fails with ERROR_ACCESS_DENIED or ERROR_SHARING_VIOLATION
- //while parent directory traversal is successful: e.g. "C:\pagefile.sys"
- if (ec != ERROR_PATH_NOT_FOUND && //perf: short circuit for common "not existing" error codes
- ec != ERROR_FILE_NOT_FOUND && //
- ec != ERROR_BAD_NETPATH && //
- ec != ERROR_BAD_NET_NAME) //
- {
- WIN32_FIND_DATA fileInfo = {};
- const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(itemPath).c_str(), &fileInfo);
- if (searchHandle != INVALID_HANDLE_VALUE)
- {
- ::FindClose(searchHandle);
- return true;
- }
- }
+ return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for ((broken) dir-)symlinks also
#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat fileInfo = {};
- if (::lstat(itemPath.c_str(), &fileInfo) == 0)
- return true;
+ struct ::stat dirInfo = {};
+ if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks!
+ return S_ISDIR(dirInfo.st_mode);
#endif
return false;
}
@@ -134,7 +252,15 @@ bool zen::somethingExists(const Zstring& itemPath)
namespace
{
#ifdef ZEN_WIN
-bool isFatDrive(const Zstring& filePath) //noexcept
+enum class FatType
+{
+ NONE,
+ FAT,
+ FAT32,
+ EXFAT,
+};
+
+FatType getFatType(const Zstring& filePath) //noexcept
{
const DWORD bufferSize = MAX_PATH + 1;
std::vector<wchar_t> buffer(bufferSize);
@@ -145,7 +271,7 @@ bool isFatDrive(const Zstring& filePath) //noexcept
bufferSize)) //__in DWORD cchBufferLength
{
assert(false);
- return false;
+ return FatType::NONE;
}
const Zstring volumePath = appendSeparator(&buffer[0]);
@@ -161,16 +287,21 @@ bool isFatDrive(const Zstring& filePath) //noexcept
bufferSize)) //__in DWORD nFileSystemNameSize
{
assert(false);
- return false;
+ return FatType::NONE;
}
- return &buffer[0] == Zstring(L"FAT") ||
- &buffer[0] == Zstring(L"FAT32");
+ if (&buffer[0] == Zstring(L"FAT"))
+ return FatType::FAT;
+ if (&buffer[0] == Zstring(L"FAT32"))
+ return FatType::FAT32;
+ if (&buffer[0] == Zstring(L"exFAT"))
+ return FatType::EXFAT;
+ return FatType::NONE;
}
#ifdef ZEN_WIN_VISTA_AND_LATER
-bool isFatDrive(HANDLE hFile) //noexcept
+FatType getFatType(HANDLE hFile) //noexcept
{
const DWORD bufferSize = MAX_PATH + 1; //"The length of the file system name buffer, in TCHARs. The maximum buffer size is MAX_PATH + 1"
std::vector<wchar_t> buffer(bufferSize);
@@ -185,11 +316,16 @@ bool isFatDrive(HANDLE hFile) //noexcept
bufferSize)) //_In_ DWORD nFileSystemNameSize
{
assert(false);
- return false;
+ return FatType::NONE;
}
- return &buffer[0] == Zstring(L"FAT") ||
- &buffer[0] == Zstring(L"FAT32");
+ if (&buffer[0] == Zstring(L"FAT"))
+ return FatType::FAT;
+ if (&buffer[0] == Zstring(L"FAT32"))
+ return FatType::FAT32;
+ if (&buffer[0] == Zstring(L"exFAT"))
+ return FatType::EXFAT;
+ return FatType::NONE;
}
#endif
#endif
@@ -331,7 +467,7 @@ Zstring zen::getTempFolderPath() //throw FileError
}
-bool zen::removeFile(const Zstring& filePath) //throw FileError
+void zen::removeFilePlain(const Zstring& filePath) //throw FileError
{
#ifdef ZEN_WIN
const wchar_t functionName[] = L"DeleteFile";
@@ -345,19 +481,14 @@ bool zen::removeFile(const Zstring& filePath) //throw FileError
ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
#ifdef ZEN_WIN
if (ec == ERROR_ACCESS_DENIED) //function fails if file is read-only
- {
- ::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), FILE_ATTRIBUTE_NORMAL); //(try to) normalize file attributes
-
- if (::DeleteFile(applyLongPathPrefix(filePath).c_str())) //now try again...
- return true;
- ec = ::GetLastError();
- }
+ if (::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), FILE_ATTRIBUTE_NORMAL)) //(try to) normalize file attributes
+ {
+ if (::DeleteFile(applyLongPathPrefix(filePath).c_str())) //now try again...
+ return;
+ ec = ::GetLastError();
+ }
#endif
- if (!somethingExists(filePath)) //warning: changes global error code!!
- return false; //neither file nor any other object (e.g. broken symlink) with that name existing - caveat: what if "access is denied"!?!??!?!?
-
//begin of "regular" error reporting
- const std::wstring errorMsg = replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath));
std::wstring errorDescr = formatSystemError(functionName, ec);
#ifdef ZEN_WIN_VISTA_AND_LATER
@@ -369,18 +500,29 @@ bool zen::removeFile(const Zstring& filePath) //throw FileError
errorDescr = _("The file is locked by another process:") + L"\n" + procList;
}
#endif
- throw FileError(errorMsg, errorDescr);
+ throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)), errorDescr);
}
- return true;
}
-void zen::removeDirectorySimple(const Zstring& dirPath) //throw FileError
+void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError
{
#ifdef ZEN_WIN
- //(try to) normalize file attributes: actually NEEDED for symbolic links also!
- ::SetFileAttributes(applyLongPathPrefix(dirPath).c_str(), FILE_ATTRIBUTE_NORMAL);
+ const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(linkPath).c_str());
+ if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
+ removeDirectoryPlain(linkPath); //throw FileError
+ else
+ removeFilePlain(linkPath); //throw FileError
+#elif defined ZEN_LINUX || defined ZEN_MAC
+ removeFilePlain(linkPath); //throw FileError
+#endif
+}
+
+
+void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
+{
+#ifdef ZEN_WIN
const wchar_t functionName[] = L"RemoveDirectory";
if (!::RemoveDirectory(applyLongPathPrefix(dirPath).c_str()))
#elif defined ZEN_LINUX || defined ZEN_MAC
@@ -388,13 +530,20 @@ void zen::removeDirectorySimple(const Zstring& dirPath) //throw FileError
if (::rmdir(dirPath.c_str()) != 0)
#endif
{
- const ErrorCode ec = getLastError(); //copy before making other system calls!
-
- if (!somethingExists(dirPath)) //warning: changes global error code!!
- return;
+ ErrorCode ec = getLastError(); //copy before making other system calls!
+#ifdef ZEN_WIN
+ if (ec == ERROR_ACCESS_DENIED) //(try to) normalize file attributes: NEEDED! even folders and symlinks can have FILE_ATTRIBUTE_READONLY set!
+ if (::SetFileAttributes(applyLongPathPrefix(dirPath).c_str(), FILE_ATTRIBUTE_NORMAL))
+ {
+ if (::RemoveDirectory(applyLongPathPrefix(dirPath).c_str())) //now try again...
+ return;
+ ec = ::GetLastError();
+ }
+#elif defined ZEN_LINUX || defined ZEN_MAC
+ bool symlinkExists = false;
+ try { symlinkExists = getItemType(dirPath) == ItemType::SYMLINK; } /*throw FileError*/ catch (FileError&) {} //previous exception is more relevant
-#if defined ZEN_LINUX || defined ZEN_MAC
- if (symlinkExists(dirPath))
+ if (symlinkExists)
{
if (::unlink(dirPath.c_str()) != 0)
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), L"unlink");
@@ -418,48 +567,41 @@ namespace
{
void removeDirectoryImpl(const Zstring& folderPath) //throw FileError
{
- assert(dirExists(folderPath)); //[!] no symlinks in this context!!!
- //attention: check if folderPath is a symlink! Do NOT traverse into it deleting contained files!!!
-
+#ifndef NDEBUG //[!] no symlinks in this context!!! Do NOT traverse into it deleting contained files!!!
+ try { assert(getItemType(folderPath) != ItemType::SYMLINK); /*throw FileError*/ }
+ catch (FileError&) {}
+#endif
std::vector<Zstring> filePaths;
- std::vector<Zstring> folderSymlinkPaths;
+ std::vector<Zstring> symlinkPaths;
std::vector<Zstring> folderPaths;
//get all files and directories from current directory (WITHOUT subdirectories!)
traverseFolder(folderPath,
- [&](const FileInfo& fi) { filePaths.push_back(fi.fullPath); },
- [&](const DirInfo& di) { folderPaths .push_back(di.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
- [&](const SymlinkInfo& si)
- {
-#ifdef ZEN_WIN
- if (dirExists(si.fullPath)) //dir symlink
- folderSymlinkPaths.push_back(si.fullPath);
- else //file symlink, broken symlink
-#endif
- filePaths.push_back(si.fullPath);
- },
+ [&](const FileInfo& fi) { filePaths .push_back(fi.fullPath); },
+ [&](const FolderInfo& fi) { folderPaths .push_back(fi.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
+ [&](const SymlinkInfo& si) { symlinkPaths.push_back(si.fullPath); },
[](const std::wstring& errorMsg) { throw FileError(errorMsg); });
for (const Zstring& filePath : filePaths)
- removeFile(filePath); //throw FileError
+ removeFilePlain(filePath); //throw FileError
- for (const Zstring& symlinkPath : folderSymlinkPaths)
- removeDirectorySimple(symlinkPath); //throw FileError
+ for (const Zstring& symlinkPath : symlinkPaths)
+ removeSymlinkPlain(symlinkPath); //throw FileError
//delete directories recursively
for (const Zstring& subFolderPath : folderPaths)
removeDirectoryImpl(subFolderPath); //throw FileError; call recursively to correctly handle symbolic links
- removeDirectorySimple(folderPath); //throw FileError
+ removeDirectoryPlain(folderPath); //throw FileError
}
}
-void zen::removeDirectoryRecursively(const Zstring& dirPath) //throw FileError
+void zen::removeDirectoryPlainRecursion(const Zstring& dirPath) //throw FileError
{
- if (symlinkExists(dirPath))
- removeDirectorySimple(dirPath); //throw FileError
- else if (somethingExists(dirPath))
+ if (getItemType(dirPath) == ItemType::SYMLINK) //throw FileError
+ removeSymlinkPlain(dirPath); //throw FileError
+ else
removeDirectoryImpl(dirPath); //throw FileError
}
@@ -524,7 +666,6 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
errorDescr = _("The file is locked by another process:") + L"\n" + procList;
}
#endif
-
if (ec == ERROR_NOT_SAME_DEVICE)
throw ErrorDifferentVolume(errorMsg, errorDescr);
if (ec == ERROR_ALREADY_EXISTS || //-> used on Win7 x64
@@ -534,7 +675,8 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
}
#elif defined ZEN_LINUX || defined ZEN_MAC
- //rename() will never fail with EEXIST, but always (atomically) overwrite! => equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists
+ //rename() will never fail with EEXIST, but always (atomically) overwrite!
+ //=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx + MOVEFILE_REPLACE_EXISTING
//=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy
//=> OS X: no solution
@@ -550,9 +692,15 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
throw FileError(errorMsg, errorDescr);
};
- if (!equalFilePath(pathSource, pathTarget)) //OS X: changing file name case is not an "already exists" error!
- if (somethingExists(pathTarget))
+ if (!equalFilePath(pathSource, pathTarget)) //exception for OS X: changing file name case is not an "already exists" situation!
+ {
+ bool alreadyExists = true;
+ try { /*ItemType type = */getItemType(pathTarget); } /*throw FileError*/ catch (FileError&) { alreadyExists = false; }
+
+ if (alreadyExists)
throwException(EEXIST);
+ //else: nothing exists or other error (hopefully ::rename will also fail!)
+ }
if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0)
throwException(errno);
@@ -585,10 +733,11 @@ Zstring getFilenameFmt(const Zstring& filePath, Function fun) //throw(); returns
}
-Zstring findUnused8Dot3Name(const Zstring& filePath) //find a unique 8.3 short name
+Zstring findUnused8Dot3Name(const Zstring& filePath) //throw FileError
{
- const Zstring pathPrefix = contains(filePath, FILE_NAME_SEPARATOR) ?
- (beforeLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) + FILE_NAME_SEPARATOR) : Zstring();
+ Opt<Zstring> parentPath = getParentFolderPath(filePath);
+ assert(parentPath);
+ const Zstring pathPrefix = parentPath ? appendSeparator(*parentPath) : Zstring();
//extension needn't contain reasonable data
Zstring extension = getFileExtension(filePath);
@@ -599,35 +748,35 @@ Zstring findUnused8Dot3Name(const Zstring& filePath) //find a unique 8.3 short n
for (int index = 0; index < 100000000; ++index) //filePath must be representable by <= 8 characters
{
- const Zstring output = pathPrefix + numberTo<Zstring>(index) + Zchar('.') + extension;
- if (!somethingExists(output)) //ensure uniqueness
- return output;
+ const Zstring testPath = pathPrefix + numberTo<Zstring>(index) + Zchar('.') + extension;
+ if (!getItemTypeIfExists(testPath)) //throw FileError
+ return testPath;
}
throw std::runtime_error(std::string("100,000,000 files, one for each number, exist in this directory? You're kidding...") + utfCvrtTo<std::string>(pathPrefix) +
"\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
}
-bool have8dot3NameClash(const Zstring& filePath)
+bool have8dot3NameClash(const Zstring& itemPath)
{
- if (!contains(filePath, FILE_NAME_SEPARATOR))
- return false;
+ try
+ {
+ /*ItemType type =*/ getItemType(itemPath); //throw FileError
+ }
+ catch (FileError&) { return false; }
- if (somethingExists(filePath)) //name OR directory!
+ const Zstring nameOrig = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
+ const Zstring nameShort = afterLast(getFilenameFmt(itemPath, ::GetShortPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error
+ const Zstring nameLong = afterLast(getFilenameFmt(itemPath, ::GetLongPathName ), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //
+
+ if (!nameShort.empty() &&
+ !nameLong .empty() &&
+ equalFilePath(nameOrig, nameShort) &&
+ !equalFilePath(nameShort, nameLong))
{
- const Zstring origName = afterLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
- const Zstring shortName = afterLast(getFilenameFmt(filePath, ::GetShortPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error
- const Zstring longName = afterLast(getFilenameFmt(filePath, ::GetLongPathName ), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //
-
- if (!shortName.empty() &&
- !longName .empty() &&
- equalFilePath(origName, shortName) &&
- !equalFilePath(shortName, longName))
- {
- //for filePath short and long file name are equal and another unrelated file happens to have the same short name
- //e.g. filePath == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1"
- return true;
- }
+ //for itemPath short and long file name are equal and another unrelated file happens to have the same short name
+ //e.g. itemPath == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1"
+ return true;
}
return false;
}
@@ -639,16 +788,16 @@ public:
{
const Zstring longName = afterLast(getFilenameFmt(filePath, ::GetLongPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error
- unrelatedFile = beforeLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
- if (!unrelatedFile.empty())
- unrelatedFile += FILE_NAME_SEPARATOR;
- unrelatedFile += longName;
+ Opt<Zstring> parentPath = getParentFolderPath(filePath);
+ assert(parentPath);
+ unrelatedFilePath_ = parentPath ? appendSeparator(*parentPath) : Zstring();
+ unrelatedFilePath_ += longName;
//find another name in short format: this ensures the actual short name WILL be renamed as well!
- unrelatedFileParked = findUnused8Dot3Name(filePath);
+ parkedFilePath_ = findUnused8Dot3Name(filePath); //throw FileError
//move already existing short name out of the way for now
- renameFile_sub(unrelatedFile, unrelatedFileParked); //throw FileError, ErrorDifferentVolume
+ renameFile_sub(unrelatedFilePath_, parkedFilePath_); //throw FileError, ErrorDifferentVolume
//DON'T call renameFile() to avoid reentrance!
}
@@ -657,13 +806,13 @@ public:
//the file system should assign this unrelated file a new (unique) short name
try
{
- renameFile_sub(unrelatedFileParked, unrelatedFile); //throw FileError, ErrorDifferentVolume
+ renameFile_sub(parkedFilePath_, unrelatedFilePath_); //throw FileError, ErrorDifferentVolume
}
catch (FileError&) {}
}
private:
- Zstring unrelatedFile;
- Zstring unrelatedFileParked;
+ Zstring unrelatedFilePath_;
+ Zstring parkedFilePath_;
};
#endif
}
@@ -752,51 +901,52 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError
std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath));
//add more meaningful message: FAT accepts only a subset of the NTFS date range
+ if (ec == ERROR_INVALID_PARAMETER)
#ifdef ZEN_WIN_VISTA_AND_LATER
- if (ec == ERROR_INVALID_PARAMETER && isFatDrive(hFile))
+ if (getFatType(hFile) != FatType::NONE) //exFAT has the same date range like FAT/FAT32: http://www.freefilesync.org/forum/viewtopic.php?t=4051
#else
- if (ec == ERROR_INVALID_PARAMETER && isFatDrive(filePath))
+ if (getFatType(filePath) != FatType::NONE)
#endif
- {
- //let's not fail due to an invalid creation time on FAT: http://www.freefilesync.org/forum/viewtopic.php?t=2278
- if (creationTime) //retry (single-level recursion at most!)
- return setFileTimeByHandle(hFile, nullptr, lastWriteTime, filePath); //throw FileError
-
- //if the ERROR_INVALID_PARAMETER is due to an invalid date, enhance message:
- const LARGE_INTEGER writeTimeInt = toLargeInteger(lastWriteTime);
- if (writeTimeInt.QuadPart < 0x01a8e79fe1d58000 || //1980-01-01 https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2100
- writeTimeInt.QuadPart >= 0x022f716377640000) //2100-01-01
{
- errorMsg += L"\nA FAT volume can only store dates from 1980 to 2099:\n" "\twrite time (UTC):";
- SYSTEMTIME st = {};
- if (::FileTimeToSystemTime(&lastWriteTime, //__in const FILETIME *lpFileTime,
- &st)) //__out LPSYSTEMTIME lpSystemTime
+ //let's not fail due to an invalid creation time on FAT: http://www.freefilesync.org/forum/viewtopic.php?t=2278
+ if (creationTime) //retry (single-level recursion at most!)
+ return setFileTimeByHandle(hFile, nullptr, lastWriteTime, filePath); //throw FileError
+
+ //if the ERROR_INVALID_PARAMETER is due to an invalid date, enhance message:
+ const LARGE_INTEGER writeTimeInt = toLargeInteger(lastWriteTime);
+ if (writeTimeInt.QuadPart < 0x01a8e79fe1d58000 || //1980-01-01 https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2100
+ writeTimeInt.QuadPart >= 0x022f716377640000) //2100-01-01
{
- //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!!
- int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
- if (bufferSize > 0)
- {
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale,
- 0, //_In_ DWORD dwFlags,
- &st, //_In_opt_ const SYSTEMTIME *lpDate,
- nullptr, //_In_opt_ LPCTSTR lpFormat,
- &buffer[0], //_Out_opt_ LPTSTR lpDateStr,
- bufferSize) > 0) //_In_ int cchDate
- errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
- }
-
- bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
- if (bufferSize > 0)
+ errorMsg += L"\nA FAT volume can only store dates from 1980 to 2099:\n" "\twrite time (UTC):";
+ SYSTEMTIME st = {};
+ if (::FileTimeToSystemTime(&lastWriteTime, //__in const FILETIME *lpFileTime,
+ &st)) //__out LPSYSTEMTIME lpSystemTime
{
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0)
- errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
+ //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!!
+ int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
+ if (bufferSize > 0)
+ {
+ std::vector<wchar_t> buffer(bufferSize);
+ if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale,
+ 0, //_In_ DWORD dwFlags,
+ &st, //_In_opt_ const SYSTEMTIME *lpDate,
+ nullptr, //_In_opt_ LPCTSTR lpFormat,
+ &buffer[0], //_Out_opt_ LPTSTR lpDateStr,
+ bufferSize) > 0) //_In_ int cchDate
+ errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
+ }
+
+ bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
+ if (bufferSize > 0)
+ {
+ std::vector<wchar_t> buffer(bufferSize);
+ if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0)
+ errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
+ }
}
+ errorMsg += L" (" + numberTo<std::wstring>(writeTimeInt.QuadPart) + L")"; //just in case the above date formatting fails
}
- errorMsg += L" (" + numberTo<std::wstring>(writeTimeInt.QuadPart) + L")"; //just in case the above date formatting fails
}
- }
throw FileError(errorMsg, formatSystemError(L"SetFileTime", ec));
}
@@ -1200,9 +1350,12 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P
//in contrast to ::SetSecurityInfo(), ::SetFileSecurity() seems to honor the "inherit DACL/SACL" flags
//CAVEAT: if a file system does not support ACLs, GetFileSecurity() will return successfully with a *valid* security descriptor containing *no* ACL entries!
+ const bool isSymlinkSource = getItemType(sourcePath) == ItemType::SYMLINK; //throw FileError
+ const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
+
//NOTE: ::GetFileSecurity()/::SetFileSecurity() do NOT follow Symlinks! getResolvedSymlinkPath() requires Vista or later!
- const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(sourcePath) ? getResolvedSymlinkPath(sourcePath) : sourcePath; //throw FileError
- const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(targetPath) ? getResolvedSymlinkPath(targetPath) : targetPath; //
+ const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && isSymlinkSource ? getResolvedSymlinkPath(sourcePath) : sourcePath; //throw FileError
+ const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && isSymlinkTarget ? getResolvedSymlinkPath(targetPath) : targetPath; //
//setting privileges requires admin rights!
try
@@ -1371,7 +1524,8 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P
if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown");
- if (!symlinkExists(targetPath) && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
+ const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
+ if (!isSymlinkTarget && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
}
@@ -1405,66 +1559,46 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P
}
#endif
}
+}
-void makeDirectoryRecursivelyImpl(const Zstring& dirPath) //FileError
+void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError
{
- assert(!endsWith(dirPath, FILE_NAME_SEPARATOR)); //even "C:\" should be "C:" as input!
+ if (!getParentFolderPath(dirPath)) //device root
+ return static_cast<void>(/*ItemType =*/ getItemType(dirPath)); //throw FileError
try
{
- copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+ copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting
}
- catch (const ErrorTargetExisting&) {} //*something* existing: folder or FILE!
- catch (const ErrorTargetPathMissing&)
+ catch (FileError&)
{
- //we need to create parent directories first
- const Zstring parentPath = beforeLast(dirPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
- if (!parentPath.empty())
- {
- //recurse...
- makeDirectoryRecursivelyImpl(parentPath); //throw FileError
+ Opt<PathDetails> pd;
+ try { pd = getPathDetails(dirPath); /*throw FileError*/ }
+ catch (FileError&) {} //previous exception is more relevant
- //now try again...
- copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing)
+ if (pd && pd->existingType != ItemType::FILE)
+ {
+ Zstring intermediatePath = pd->existingPath;
+ for (const Zstring& itemName : pd->relPath)
+ {
+ intermediatePath = appendSeparator(intermediatePath) + itemName;
+ copyNewDirectory(Zstring(), intermediatePath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting)
+ }
return;
}
throw;
}
}
-}
-
-
-void zen::makeDirectoryRecursively(const Zstring& dirPath) //throw FileError
-{
- //remove trailing separator (even for C:\ root directories)
- const Zstring dirFormatted = endsWith(dirPath, FILE_NAME_SEPARATOR) ?
- beforeLast(dirPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) :
- dirPath;
- makeDirectoryRecursivelyImpl(dirFormatted); //FileError
-}
//source path is optional (may be empty)
-void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting
bool copyFilePermissions)
{
#ifdef ZEN_WIN
auto getErrorMsg = [](const Zstring& path) { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(path)); };
- //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS!
- const Zstring pathPf = appendSeparator(targetPath); //we do not support "C:" to represent a relative path!
- if (pathPf.size() == 3 &&
- isAlpha(pathPf[0]) && pathPf[1] == L':')
- {
- const DWORD ec = somethingExists(pathPf) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; //don't use dirExists() => harmonize with ErrorTargetExisting!
- const std::wstring errorDescr = formatSystemError(L"CreateDirectory", ec);
-
- if (ec == ERROR_ALREADY_EXISTS)
- throw ErrorTargetExisting(getErrorMsg(pathPf), errorDescr);
- throw FileError(getErrorMsg(pathPf), errorDescr); //[!] this is NOT a ErrorTargetPathMissing case!
- }
-
//deliberately don't support creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/
if (endsWith(targetPath, L' ') ||
endsWith(targetPath, L'.'))
@@ -1498,8 +1632,9 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
if (ec == ERROR_ALREADY_EXISTS)
throw ErrorTargetExisting(getErrorMsg(targetPath), errorDescr);
- else if (ec == ERROR_PATH_NOT_FOUND)
- throw ErrorTargetPathMissing(getErrorMsg(targetPath), errorDescr);
+ //else if (ec == ERROR_PATH_NOT_FOUND || //the ususal suspect
+ // ec == ERROR_FILE_NOT_FOUND) //Webdav incorrectly returns this one: http://www.freefilesync.org/forum/viewtopic.php?t=4053
+ // throw ErrorTargetPathMissing(getErrorMsg(targetPath), errorDescr);
throw FileError(getErrorMsg(targetPath), errorDescr);
}
}
@@ -1524,8 +1659,8 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
if (lastError == EEXIST)
throw ErrorTargetExisting(errorMsg, errorDescr);
- else if (lastError == ENOENT)
- throw ErrorTargetPathMissing(errorMsg, errorDescr);
+ //else if (lastError == ENOENT)
+ // throw ErrorTargetPathMissing(errorMsg, errorDescr);
throw FileError(errorMsg, errorDescr);
}
#endif
@@ -1610,7 +1745,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
catch (FileError&) {}
#endif
- ZEN_ON_SCOPE_FAIL(try { removeDirectorySimple(targetPath); }
+ ZEN_ON_SCOPE_FAIL(try { removeDirectoryPlain(targetPath); }
catch (FileError&) {}); //ensure cleanup:
//enforce copying file permissions: it's advertized on GUI...
@@ -1625,11 +1760,13 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
const Zstring linkPath = getSymlinkTargetRaw(sourceLink); //throw FileError; accept broken symlinks
#ifdef ZEN_WIN
- const bool isDirLink = [&]() -> bool
- {
- const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(sourceLink).c_str());
- return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY);
- }();
+ WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {};
+ if (!::GetFileAttributesEx(applyLongPathPrefix(sourceLink).c_str(), //__in LPCTSTR lpFileName,
+ GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ &sourceAttr)) //__out LPVOID lpFileInformation
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"GetFileAttributesEx");
+
+ const bool isDirLink = (sourceAttr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
using CreateSymbolicLinkFunc = BOOLEAN (WINAPI*)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags);
const SysDllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW");
@@ -1649,30 +1786,11 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), functionName);
//allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist!
-
- auto cleanUp = [&]
- {
- try
- {
-#ifdef ZEN_WIN
- if (isDirLink)
- removeDirectorySimple(targetLink); //throw FileError
- else
-#endif
- removeFile(targetLink); //throw FileError
- }
- catch (FileError&) {}
- };
- ZEN_ON_SCOPE_FAIL(cleanUp());
+ ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetLink); /*throw FileError*/ }
+ catch (FileError&) {});
//file times: essential for sync'ing a symlink: enforce this! (don't just try!)
#ifdef ZEN_WIN
- WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {};
- if (!::GetFileAttributesEx(applyLongPathPrefix(sourceLink).c_str(), //__in LPCTSTR lpFileName,
- GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
- &sourceAttr)) //__out LPVOID lpFileInformation
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"GetFileAttributesEx");
-
setWriteTimeNative(targetLink, sourceAttr.ftLastWriteTime, &sourceAttr.ftCreationTime, ProcSymlink::DIRECT); //throw FileError
#else
@@ -1924,8 +2042,6 @@ InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileEr
ec == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6
throw ErrorTargetExisting(errorMsg, errorDescr);
- //if (ec == ERROR_PATH_NOT_FOUND) throw ErrorTargetPathMissing(errorMsg, errorDescr);
-
throw FileError(errorMsg, errorDescr);
}
#ifdef ZEN_WIN_VISTA_AND_LATER
@@ -1943,8 +2059,8 @@ InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileEr
assert(false);
);
#else
- ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); }
- catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget
+ ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); }
+ catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget
FileOutput fileOut(hFileTarget, targetFile); //pass ownership
#endif
@@ -2071,7 +2187,7 @@ InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileEr
}
//time needs to be set at the end: WriteFile/BackupWrite() change modification time
- setFileTimeByHandle(fileOut.getHandle(), &sourceInfo.ftCreationTime,sourceInfo.ftLastWriteTime, targetFile); //throw FileError
+ setFileTimeByHandle(fileOut.getHandle(), &sourceInfo.ftCreationTime, sourceInfo.ftLastWriteTime, targetFile); //throw FileError
return newAttrib;
}
@@ -2206,7 +2322,7 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
try { activatePrivilege(PrivilegeName::RESTORE); }
catch (const FileError&) { backupPrivilegesActive = false; }
- auto guardTarget = zen::makeGuard<ScopeGuardRunMode::ON_FAIL>([&] { try { removeFile(targetFile); } catch (FileError&) {} });
+ auto guardTarget = zen::makeGuard<ScopeGuardRunMode::ON_FAIL>([&] { try { removeFilePlain(targetFile); } catch (FileError&) {} });
//transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we? ;)
DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS;
@@ -2277,16 +2393,20 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
throw ErrorTargetExisting(errorMsg, errorDescr);
}
- //if (lastError == ERROR_PATH_NOT_FOUND) throw ErrorTargetPathMissing(errorMsg, errorDescr); //could this also be source path missing!?
+ //lastError == ERROR_PATH_NOT_FOUND: could this also be source path missing!?
try //add more meaningful message
{
//trying to copy > 4GB file to FAT/FAT32 volume gives obscure ERROR_INVALID_PARAMETER (FAT can indeed handle files up to 4 Gig, tested!)
- if (ec == ERROR_INVALID_PARAMETER &&
- isFatDrive(targetFile) &&
- getFilesize(sourceFile) >= 4U * std::uint64_t(1024U * 1024 * 1024)) //throw FileError
- errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabytes.";
- //see "Limitations of the FAT32 File System": http://support.microsoft.com/kb/314463/en-us
+ if (ec == ERROR_INVALID_PARAMETER)
+ {
+ const FatType fatType = getFatType(targetFile);
+ if ((fatType == FatType::FAT ||
+ fatType == FatType::FAT32) && //no problem for exFAT (limit ca. 128 PB)
+ getFilesize(sourceFile) >= 4U * std::uint64_t(1024U * 1024 * 1024)) //throw FileError
+ errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabytes.";
+ //see "Limitations of the FAT32 File System": http://support.microsoft.com/kb/314463/en-us
+ }
//note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target file path is of a restricted type.
}
@@ -2378,7 +2498,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError
throw FileError(errorMsg, errorDescr);
}
- ZEN_ON_SCOPE_FAIL( try { removeFile(targetFile); }
+ ZEN_ON_SCOPE_FAIL( try { removeFilePlain(targetFile); }
catch (FileError&) {} );
//transactional behavior: place guard after ::open() and before lifetime of FileOutput:
//=> don't delete file that existed previously!!!
@@ -2459,7 +2579,7 @@ InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targ
const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked
//at this point we know we created a new file, so it's fine to delete it for cleanup!
- ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); }
+ ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); }
catch (FileError&) {});
if (copyFilePermissions)
diff --git a/zen/file_access.h b/zen/file_access.h
index fdb787bd..0586ea8f 100644
--- a/zen/file_access.h
+++ b/zen/file_access.h
@@ -17,17 +17,40 @@ namespace zen
{
//note: certain functions require COM initialization! (vista_file_op.h)
+Opt<Zstring> getParentFolderPath(const Zstring& itemPath);
+
+//POSITIVE existence checks; if negative: 1. item not existing 2. different type 3.access error or similar
+bool fileAvailable(const Zstring& filePath); //noexcept
+bool dirAvailable (const Zstring& dirPath ); //
+
+
bool fileExists (const Zstring& filePath); //noexcept; check whether file or file-symlink exists
bool dirExists (const Zstring& dirPath ); //noexcept; check whether directory or dir-symlink exists
-bool symlinkExists (const Zstring& linkPath); //noexcept; check whether a symbolic link exists
-bool somethingExists(const Zstring& itemPath); //noexcept; check whether any object with this name exists
+
+enum class ItemType
+{
+ FILE,
+ FOLDER,
+ SYMLINK,
+};
+//(hopefully) fast: does not distinguish between error/not existing
+ItemType getItemType (const Zstring& itemPath); //throw FileError
+//execute potentially SLOW folder traversal but distinguish error/not existing
+Opt<ItemType> getItemTypeIfExists(const Zstring& itemPath); //throw FileError
+
+struct PathDetails
+{
+ ItemType existingType;
+ Zstring existingPath; //itemPath =: existingPath + relPath
+ std::vector<Zstring> relPath; //
+};
+PathDetails getPathDetails(const Zstring& itemPath); //throw FileError
enum class ProcSymlink
{
DIRECT,
FOLLOW
};
-
void setFileTime(const Zstring& filePath, std::int64_t modificationTime, ProcSymlink procSl); //throw FileError
//symlink handling: always evaluate target
@@ -37,11 +60,10 @@ VolumeId getVolumeId(const Zstring& itemPath); //throw FileError
//get per-user directory designated for temporary files:
Zstring getTempFolderPath(); //throw FileError
-bool removeFile(const Zstring& filePath); //throw FileError; return "false" if file is not existing
-
-void removeDirectorySimple(const Zstring& dirPath); //throw FileError
-
-void removeDirectoryRecursively(const Zstring& dirPath); //throw FileError
+void removeFilePlain (const Zstring& filePath); //throw FileError; ERROR if not existing
+void removeSymlinkPlain (const Zstring& linkPath); //throw FileError; ERROR if not existing
+void removeDirectoryPlain(const Zstring& dirPath ); //throw FileError; ERROR if not existing
+void removeDirectoryPlainRecursion(const Zstring& dirPath); //throw FileError; ERROR if not existing
//rename file or directory: no copying!!!
void renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
@@ -50,11 +72,11 @@ bool supportsPermissions(const Zstring& dirPath); //throw FileError, dereference
//- no error if already existing
//- create recursively if parent directory is not existing
-void makeDirectoryRecursively(const Zstring& dirPath); //throw FileError
+void createDirectoryIfMissingRecursion(const Zstring& dirPath); //throw FileError
//fail if already existing or parent directory not existing:
//source path is optional (may be empty)
-void copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, bool copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+void copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, bool copyFilePermissions); //throw FileError, ErrorTargetExisting
void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError
diff --git a/zen/file_error.h b/zen/file_error.h
index 374d0b2c..aa41040d 100644
--- a/zen/file_error.h
+++ b/zen/file_error.h
@@ -31,7 +31,7 @@ private:
#define DEFINE_NEW_FILE_ERROR(X) struct X : public FileError { X(const std::wstring& msg) : FileError(msg) {} X(const std::wstring& msg, const std::wstring& descr) : FileError(msg, descr) {} };
DEFINE_NEW_FILE_ERROR(ErrorTargetExisting);
-DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing);
+//DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing);
DEFINE_NEW_FILE_ERROR(ErrorFileLocked);
DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume);
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index 3eb284e1..ef6d255c 100644
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -28,8 +28,8 @@ using namespace zen;
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 FolderInfo& fi)>& onFolder,
+ const std::function<void (const SymlinkInfo& si)>& onSymlink,
const std::function<void (const std::wstring& errorMsg)>& onError)
{
try
@@ -41,12 +41,15 @@ void zen::traverseFolder(const Zstring& dirPath,
{
const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
if (ec == 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;
- }
+ try
+ {
+ //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 violating the API design principle of single responsibility
+ if (getItemType(dirPath) == ItemType::FOLDER) //throw FileError
+ return;
+ }
+ catch (FileError&) {} //previous exception is more relevant
+
throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(L"FindFirstFile", ec));
}
ZEN_ON_SCOPE_EXIT(::FindClose(hDir));
@@ -75,22 +78,23 @@ void zen::traverseFolder(const Zstring& dirPath,
if (itemNameRaw[0] == 0)
throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item with empty name.");
- const Zstring& itemPath = appendSeparator(dirPath) + itemNameRaw;
+ const Zstring& itemName = itemNameRaw;
+ const Zstring& itemPath = appendSeparator(dirPath) + itemName;
if (zen::isSymlink(findData)) //check first!
{
- if (onLink)
- onLink({ itemPath, filetimeToTimeT(findData.ftLastWriteTime) });
+ if (onSymlink)
+ onSymlink({ itemName, itemPath, filetimeToTimeT(findData.ftLastWriteTime) });
}
else if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
- if (onDir)
- onDir({ itemPath });
+ if (onFolder)
+ onFolder({ itemName, itemPath });
}
else //a file
{
if (onFile)
- onFile({ itemPath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) });
+ onFile({ itemName, itemPath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) });
}
}
@@ -159,18 +163,18 @@ void zen::traverseFolder(const Zstring& dirPath,
if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks!
{
- if (onLink)
- onLink({ itemPath, statData.st_mtime});
+ if (onSymlink)
+ onSymlink({ itemName, itemPath, statData.st_mtime});
}
else if (S_ISDIR(statData.st_mode)) //a directory
{
- if (onDir)
- onDir({ itemPath });
+ if (onFolder)
+ onFolder({ itemName, itemPath });
}
else //a file or named pipe, ect.
{
if (onFile)
- onFile({ itemPath, makeUnsigned(statData.st_size), statData.st_mtime });
+ onFile({ itemName, 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:
diff --git a/zen/file_traverser.h b/zen/file_traverser.h
index 5fffe0e2..1badddf9 100644
--- a/zen/file_traverser.h
+++ b/zen/file_traverser.h
@@ -16,19 +16,22 @@ namespace zen
{
struct FileInfo
{
- const Zstring& fullPath;
+ Zstring itemName;
+ Zstring fullPath;
std::uint64_t fileSize; //[bytes]
std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
};
-struct DirInfo
+struct FolderInfo
{
- const Zstring& fullPath;
+ Zstring itemName;
+ Zstring fullPath;
};
struct SymlinkInfo
{
- const Zstring& fullPath;
+ Zstring itemName;
+ Zstring fullPath;
std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
};
@@ -36,8 +39,8 @@ struct SymlinkInfo
//- 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 FolderInfo& fi)>& onFolder, //optional
+ const std::function<void (const SymlinkInfo& si)>& onSymlink, //
const std::function<void (const std::wstring& errorMsg)>& onError); //
}
diff --git a/zen/fixed_list.h b/zen/fixed_list.h
index 81197eb4..27eb488c 100644
--- a/zen/fixed_list.h
+++ b/zen/fixed_list.h
@@ -139,8 +139,8 @@ public:
void swap(FixedList& other)
{
std::swap(firstInsert_, other.firstInsert_);
- std::swap(lastInsert_ , other.lastInsert_);
- std::swap(sz_ , other.sz_);
+ std::swap(lastInsert_, other.lastInsert_);
+ std::swap(sz_, other.sz_);
}
private:
@@ -194,7 +194,7 @@ public:
};
using value_type = T;
- using iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::iterator , T>;
+ using iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::iterator, T>;
using const_iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::const_iterator, const T>;
using reference = T&;
using const_reference = const T&;
diff --git a/zen/recycler.cpp b/zen/recycler.cpp
index 7f9e0a01..d8ee58c4 100644
--- a/zen/recycler.cpp
+++ b/zen/recycler.cpp
@@ -29,9 +29,8 @@ using namespace zen;
#ifdef ZEN_WIN
-void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::function<void (const std::wstring& displayPath)>& onRecycleItem)
+void zen::recycleOrDeleteIfExists(const std::vector<Zstring>& itemPaths, const std::function<void (const std::wstring& displayPath)>& onRecycleItem)
{
- if (itempaths.empty()) return;
//warning: moving long file paths to recycler does not work!
//both ::SHFileOperation() and ::IFileOperation cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success
//both ::SHFileOperation() and ::IFileOperation can't handle \\?\-prefix!
@@ -48,21 +47,25 @@ void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::func
Nevertheless, let's use IFileOperation for better error reporting (including details on locked files)!
*/
-#ifdef ZEN_WIN_VISTA_AND_LATER
- vista::moveToRecycleBin(itempaths, onRecycleItem); //throw FileError
+
+#ifdef ZEN_WIN_VISTA_AND_LATER //Win Vista recycle bin usage: 1. good error reporting 2. late failure
+ vista::moveToRecycleBinIfExists(itemPaths, onRecycleItem); //throw FileError
#else //regular recycle bin usage: available since XP: 1. bad error reporting 2. early failure
- Zstring itempathsDoubleNull;
- for (const Zstring& itempath : itempaths)
+ //TODO: this XP version fails if any item is not existing violating this function's API
+ if (itemPaths.empty()) return;
+
+ Zstring itemPathsDoubleNull;
+ for (const Zstring& itemPath : itemPaths)
{
- itempathsDoubleNull += itempath;
- itempathsDoubleNull += L'\0';
+ itemPathsDoubleNull += itemPath;
+ itemPathsDoubleNull += L'\0';
}
SHFILEOPSTRUCT fileOp = {};
fileOp.hwnd = nullptr;
fileOp.wFunc = FO_DELETE;
- fileOp.pFrom = itempathsDoubleNull.c_str();
+ fileOp.pFrom = itemPathsDoubleNull.c_str();
fileOp.pTo = nullptr;
fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
fileOp.fAnyOperationsAborted = false;
@@ -70,10 +73,10 @@ void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::func
fileOp.lpszProgressTitle = nullptr;
//"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe."
- if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted)
+ if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) //fails if items are not existing!
{
- std::wstring itempathFmt = fmtPath(itempaths[0]); //probably not the correct file name for file lists larger than 1!
- if (itempaths.size() > 1)
+ std::wstring itempathFmt = fmtPath(itemPaths[0]); //probably not the correct file name for file lists larger than 1!
+ if (itemPaths.size() > 1)
itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one
throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt));
}
@@ -82,16 +85,37 @@ void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::func
#endif
-bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError
+bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError
{
- if (!somethingExists(itempath)) //[!] do not optimize away, OS X needs this for reliable detection of "recycle bin missing"
- return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it!
-
#ifdef ZEN_WIN
- recycleOrDelete({ itempath }, nullptr); //throw FileError
+#ifdef ZEN_WIN_VISTA_AND_LATER
+ return vista::moveToRecycleBinIfExists({ itemPath }, nullptr) != 0; //throw FileError
+
+#else
+ Zstring itemPathDoubleNull = itemPath;
+ itemPathDoubleNull += L'\0';
+
+ SHFILEOPSTRUCT fileOp = {};
+ fileOp.wFunc = FO_DELETE;
+ fileOp.pFrom = itemPathDoubleNull.c_str();
+ fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
+
+ if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) //fails if item is not existing!
+ {
+ try
+ {
+ if (!getItemTypeIfExists(itemPath)) //throw FileError
+ return false;
+ }
+ catch (FileError&) {} //previous exception is more relevant
+
+ throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)));
+ }
+ return true;
+#endif
#elif defined ZEN_LINUX
- GFile* file = ::g_file_new_for_path(itempath.c_str()); //never fails according to docu
+ GFile* file = ::g_file_new_for_path(itemPath.c_str()); //never fails according to docu
ZEN_ON_SCOPE_EXIT(g_object_unref(file);)
GError* error = nullptr;
@@ -99,38 +123,38 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError
if (!::g_file_trash(file, nullptr, &error))
{
- const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itempath));
+ const Opt<ItemType> type = getItemTypeIfExists(itemPath); //throw FileError
+ if (!type)
+ return false;
+ const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath));
if (!error)
throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this
//implement same behavior as in Windows: if recycler is not existing, delete permanently
if (error->code == G_IO_ERROR_NOT_SUPPORTED)
{
- struct ::stat fileInfo = {};
- if (::lstat(itempath.c_str(), &fileInfo) != 0)
- return false;
-
- if (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode))
- removeFile(itempath); //throw FileError
- else if (S_ISDIR(fileInfo.st_mode))
- removeDirectoryRecursively(itempath); //throw FileError
+ if (*type == ItemType::FOLDER)
+ removeDirectoryPlainRecursion(itemPath); //throw FileError
+ else
+ removeFilePlain(itemPath); //throw FileError
return true;
}
throw FileError(errorMsg, replaceCpy<std::wstring>(L"Glib Error Code %x:", L"%x", numberTo<std::wstring>(error->code)) + L" " + utfCvrtTo<std::wstring>(error->message));
//g_quark_to_string(error->domain)
}
+ return true;
#elif defined ZEN_MAC
//we cannot use FSPathMoveObjectToTrashSync directly since it follows symlinks!
static_assert(sizeof(Zchar) == sizeof(char), "");
- const UInt8* itempathUtf8 = reinterpret_cast<const UInt8*>(itempath.c_str());
+ const UInt8* itemPathUtf8 = reinterpret_cast<const UInt8*>(itemPath.c_str());
auto throwFileError = [&](OSStatus oss)
{
- const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itempath));
+ const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath));
std::wstring errorDescr = L"OSStatus Code " + numberTo<std::wstring>(oss);
if (const char* description = ::GetMacOSStatusCommentString(oss)) //found no documentation for proper use of GetMacOSStatusCommentString
@@ -138,8 +162,14 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError
throw FileError(errorMsg, errorDescr);
};
+ //[!] do not optimize away, OS X needs this for reliable detection of "recycle bin unsupported"
+ //both "not supported" and "item missing" let FSMoveObjectToTrashSync fail with -120
+ const Opt<ItemType> type = getItemTypeIfExists(itemPath); //throw FileError
+ if (!type)
+ return false;
+
FSRef objectRef = {}; //= POD structure not a pointer type!
- OSStatus rv = ::FSPathMakeRefWithOptions(itempathUtf8, //const UInt8 *path,
+ OSStatus rv = ::FSPathMakeRefWithOptions(itemPathUtf8, //const UInt8 *path,
kFSPathMakeRefDoNotFollowLeafSymlink, //OptionBits options,
&objectRef, //FSRef *ref,
nullptr); //Boolean *isDirectory
@@ -155,21 +185,17 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError
//implement same behavior as in Windows: if recycler is not existing, delete permanently
if (rv2 == -120) //=="Directory not found or incomplete pathname." but should really be "recycle bin directory not found"!
{
- struct ::stat fileInfo = {};
- if (::lstat(itempath.c_str(), &fileInfo) != 0)
- return false;
-
- if (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode))
- removeFile(itempath); //throw FileError
- else if (S_ISDIR(fileInfo.st_mode))
- removeDirectoryRecursively(itempath); //throw FileError
+ if (*type == ItemType::FOLDER)
+ removeDirectoryPlainRecursion(itemPath); //throw FileError
+ else
+ removeFilePlain(itemPath); //throw FileError
return true;
}
throwFileError(rv2);
}
-#endif
return true;
+#endif
}
diff --git a/zen/recycler.h b/zen/recycler.h
index 4a5f4b2b..ec2a8672 100644
--- a/zen/recycler.h
+++ b/zen/recycler.h
@@ -35,7 +35,7 @@ Already included in package "gtk+-2.0"!
//move a file or folder to Recycle Bin (deletes permanently if recycler is not available) -> crappy semantics, but we have no choice thanks to Windows' design
-bool recycleOrDelete(const Zstring& itemPath); //throw FileError, return "true" if file/dir was actually deleted
+bool recycleOrDeleteIfExists(const Zstring& itemPath); //throw FileError, return "true" if file/dir was actually deleted
#ifdef ZEN_WIN
@@ -43,8 +43,8 @@ bool recycleOrDelete(const Zstring& itemPath); //throw FileError, return "true"
//Vista and later: dirPath must exist for a valid check!
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 std::wstring& displayPath)>& onRecycleItem); //optional; currentItem may be empty
+void recycleOrDeleteIfExists(const std::vector<Zstring>& filePaths, //throw FileError
+ const std::function<void (const std::wstring& displayPath)>& onRecycleItem); //optional; currentItem may be empty
#endif
}
diff --git a/zen/serialize.h b/zen/serialize.h
index 7322cb07..290d9200 100644
--- a/zen/serialize.h
+++ b/zen/serialize.h
@@ -126,7 +126,7 @@ struct MemoryStreamIn
return bytesRead;
}
- size_t pos() const { return pos_; }
+ size_t pos() const { return pos_; }
private:
const BinContainer buffer_;
diff --git a/zen/shell_execute.h b/zen/shell_execute.h
index 2f73fc38..b5e04469 100644
--- a/zen/shell_execute.h
+++ b/zen/shell_execute.h
@@ -68,7 +68,7 @@ void shellExecute(const void* /*PCIDLIST_ABSOLUTE*/ shellItemPidl, const std::ws
execInfo.lpIDList = const_cast<void*>(shellItemPidl); //lpIDList is documented as PCIDLIST_ABSOLUTE!
};
- if (!shellExecuteImpl(fillExecInfo , type)) //throw FileError
+ if (!shellExecuteImpl(fillExecInfo, type)) //throw FileError
THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L"\n" + fmtPath(displayPath), L"ShellExecuteEx");
}
#endif
diff --git a/zen/thread.h b/zen/thread.h
index 5bb02a0e..f6c3ae01 100644
--- a/zen/thread.h
+++ b/zen/thread.h
@@ -398,8 +398,8 @@ InterruptibleThread::InterruptibleThread(Function&& f) : intStatus_(std::make_sh
threadCompleted = pFinished.get_future();
stdThread = std::thread([f = std::forward<Function>(f),
- intStatus = this->intStatus_,
- pFinished = std::move(pFinished)]() mutable
+ intStatus = this->intStatus_,
+ pFinished = std::move(pFinished)]() mutable
{
assert(!impl::refThreadLocalInterruptionStatus());
impl::refThreadLocalInterruptionStatus() = intStatus.get();
bgstack15