summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2015-10-02 14:55:19 +0200
committerDaniel Wilhelm <daniel@wili.li>2015-10-02 14:55:19 +0200
commit46fc289a8776ba253e97d01d6948fb1031ea1973 (patch)
treeb16a99c60f21b04c001f29862bf2ee16ae3a0e00 /zen
parent6.15 (diff)
downloadFreeFileSync-46fc289a8776ba253e97d01d6948fb1031ea1973.tar.gz
FreeFileSync-46fc289a8776ba253e97d01d6948fb1031ea1973.tar.bz2
FreeFileSync-46fc289a8776ba253e97d01d6948fb1031ea1973.zip
7.0
Diffstat (limited to 'zen')
-rw-r--r--zen/dir_watcher.cpp108
-rw-r--r--zen/dir_watcher.h7
-rw-r--r--zen/file_access.cpp375
-rw-r--r--zen/file_access.h32
-rw-r--r--zen/file_id_def.h3
-rw-r--r--zen/file_io.cpp105
-rw-r--r--zen/file_io.h37
-rw-r--r--zen/file_io_base.h64
-rw-r--r--zen/file_traverser.cpp66
-rw-r--r--zen/long_path_prefix.h4
-rw-r--r--zen/notify_removal.cpp212
-rw-r--r--zen/notify_removal.h34
-rw-r--r--zen/perf.h69
-rw-r--r--zen/recycler.cpp34
-rw-r--r--zen/serialize.h137
-rw-r--r--zen/string_base.h4
-rw-r--r--zen/string_tools.h19
-rw-r--r--zen/string_traits.h8
-rw-r--r--zen/symlink_target.h14
-rw-r--r--zen/thread.h2
-rw-r--r--zen/time.h6
-rw-r--r--zen/type_traits.h4
-rw-r--r--zen/win_ver.h10
-rw-r--r--zen/xml_io.cpp28
-rw-r--r--zen/zstring.cpp23
-rw-r--r--zen/zstring.h31
26 files changed, 564 insertions, 872 deletions
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp
index 26645157..a948a5e8 100644
--- a/zen/dir_watcher.cpp
+++ b/zen/dir_watcher.cpp
@@ -11,7 +11,7 @@
#include "scope_guard.h"
#ifdef ZEN_WIN
- #include "notify_removal.h"
+ #include "device_notify.h"
#include "win.h" //includes "windows.h"
#include "long_path_prefix.h"
@@ -161,12 +161,11 @@ public:
//end of constructor, no need to start managing "hDir"
}
- ReadChangesAsync(ReadChangesAsync&& other) :
- hDir(INVALID_HANDLE_VALUE)
+ ReadChangesAsync(ReadChangesAsync&& other) : shared_(std::move(other.shared_)),
+ dirpathPf(std::move(other.dirpathPf)),
+ hDir(other.hDir)
{
- shared_ = std::move(other.shared_);
- dirpathPf = std::move(other.dirpathPf);
- std::swap(hDir, other.hDir);
+ other.hDir = INVALID_HANDLE_VALUE;
}
~ReadChangesAsync()
@@ -271,15 +270,24 @@ private:
};
-class HandleVolumeRemoval : public NotifyRequestDeviceRemoval
+class HandleVolumeRemoval
{
public:
HandleVolumeRemoval(HANDLE hDir,
+ const Zstring& displayPath,
boost::thread& worker) :
- NotifyRequestDeviceRemoval(hDir), //throw FileError
- worker_(worker),
- removalRequested(false),
- operationComplete(false) {}
+ notificationHandle(registerFolderRemovalNotification(hDir, //throw FileError
+ displayPath,
+ [this] { this->onRequestRemoval (); }, //noexcept!
+ [this](bool successful) { this->onRemovalFinished(); })), //
+ worker_(worker),
+ removalRequested(false),
+ operationComplete(false) {}
+
+ ~HandleVolumeRemoval()
+ {
+ unregisterDeviceNotification(notificationHandle);
+ }
//all functions are called by main thread!
@@ -287,7 +295,7 @@ public:
bool finished() const { return operationComplete; }
private:
- void onRequestRemoval(HANDLE hnd) override
+ void onRequestRemoval() //noexcept!
{
//must release hDir immediately => stop monitoring!
if (worker_.joinable()) //= join() precondition: play safe; can't trust Windows to only call-back once
@@ -300,8 +308,9 @@ private:
removalRequested = true;
} //don't throw!
- void onRemovalFinished(HANDLE hnd, bool successful) override { operationComplete = true; } //throw()!
+ void onRemovalFinished() { operationComplete = true; } //noexcept!
+ DeviceNotificationHandle* notificationHandle;
boost::thread& worker_;
bool removalRequested;
bool operationComplete;
@@ -313,20 +322,18 @@ struct DirWatcher::Pimpl
{
boost::thread worker;
std::shared_ptr<SharedData> shared;
-
- Zstring dirpath;
std::unique_ptr<HandleVolumeRemoval> volRemoval;
};
-DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError
+DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
+ baseDirPath(dirPath),
pimpl_(zen::make_unique<Pimpl>())
{
pimpl_->shared = std::make_shared<SharedData>();
- pimpl_->dirpath = directory;
- ReadChangesAsync reader(directory, pimpl_->shared); //throw FileError
- pimpl_->volRemoval = zen::make_unique<HandleVolumeRemoval>(reader.getDirHandle(), pimpl_->worker); //throw FileError
+ ReadChangesAsync reader(dirPath, pimpl_->shared); //throw FileError
+ pimpl_->volRemoval = zen::make_unique<HandleVolumeRemoval>(reader.getDirHandle(), dirPath, pimpl_->worker); //throw FileError
pimpl_->worker = boost::thread(std::move(reader));
}
@@ -347,6 +354,7 @@ DirWatcher::~DirWatcher()
std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>& processGuiMessages) //throw FileError
{
std::vector<Entry> output;
+ pimpl_->shared->fetchChanges(output); //throw FileError
//wait until device removal is confirmed, to prevent locking hDir again by some new watch!
if (pimpl_->volRemoval->requestReceived())
@@ -360,10 +368,9 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()
boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(50));
}
- output.emplace_back(ACTION_DELETE, pimpl_->dirpath); //report removal as change to main directory
+ output.emplace_back(ACTION_DELETE, baseDirPath); //report removal as change to main directory
}
- else //the normal case...
- pimpl_->shared->fetchChanges(output); //throw FileError
+
return output;
}
@@ -373,32 +380,35 @@ struct DirWatcher::Pimpl
{
Pimpl() : notifDescr() {}
- Zstring basedirpath;
int notifDescr;
std::map<int, Zstring> watchDescrs; //watch descriptor and (sub-)directory name (postfixed with separator) -> owned by "notifDescr"
};
-DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError
+DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
+ baseDirPath(dirPath),
pimpl_(zen::make_unique<Pimpl>())
{
//get all subdirectories
- Zstring dirpathFmt = directory;
- if (endsWith(dirpathFmt, FILE_NAME_SEPARATOR))
- dirpathFmt.resize(dirpathFmt.size() - 1);
+ std::vector<Zstring> fullDirList { baseDirPath };
+ {
+ std::function<void (const Zstring& path)> traverse;
- std::vector<Zstring> fullDirList { dirpathFmt };
+ traverse = [&traverse, &fullDirList](const Zstring& path)
+ {
+ traverseFolder(path, nullptr,
+ [&](const DirInfo& di ) { fullDirList.push_back(di.fullPath); traverse(di.fullPath); },
+ nullptr, //don't traverse into symlinks (analog to windows build)
+ [&](const std::wstring& errorMsg) { throw FileError(errorMsg); });
+ };
-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); });
+ traverse(baseDirPath);
+ }
//init
- pimpl_->basedirpath = directory;
- pimpl_->notifDescr = ::inotify_init();
+ pimpl_->notifDescr = ::inotify_init();
if (pimpl_->notifDescr == -1)
- throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"inotify_init", getLastError());
+ throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"inotify_init", getLastError());
zen::ScopeGuard guardDescr = zen::makeGuard([&] { ::close(pimpl_->notifDescr); });
@@ -410,12 +420,12 @@ traverseFolder(dirpathFmt, nullptr,
initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1;
}
if (!initSuccess)
- throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"fcntl", getLastError());
+ throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"fcntl", getLastError());
//add watches
- for (const Zstring& subdir : fullDirList)
+ for (const Zstring& subDirPath : fullDirList)
{
- int wd = ::inotify_add_watch(pimpl_->notifDescr, subdir.c_str(),
+ int wd = ::inotify_add_watch(pimpl_->notifDescr, subDirPath.c_str(),
IN_ONLYDIR | //"Only watch pathname if it is a directory."
IN_DONT_FOLLOW | //don't follow symbolic links
IN_CREATE |
@@ -430,12 +440,13 @@ traverseFolder(dirpathFmt, nullptr,
{
const auto ec = getLastError();
if (ec == ENOSPC) //fix misleading system message "No space left on device"
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)), formatSystemError(L"inotify_add_watch", ec, L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource."));
+ throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subDirPath)),
+ formatSystemError(L"inotify_add_watch", ec, L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource."));
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)), formatSystemError(L"inotify_add_watch", ec));
+ throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subDirPath)), formatSystemError(L"inotify_add_watch", ec));
}
- pimpl_->watchDescrs.emplace(wd, appendSeparator(subdir));
+ pimpl_->watchDescrs.emplace(wd, appendSeparator(subDirPath));
}
guardDescr.dismiss();
@@ -465,7 +476,7 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()
if (errno == EAGAIN) //this error is ignored in all inotify wrappers I found
return std::vector<Entry>();
- throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->basedirpath)), L"read", getLastError());
+ throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"read", getLastError());
}
std::vector<Entry> output;
@@ -523,7 +534,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
+ //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]);
@@ -555,12 +566,13 @@ struct DirWatcher::Pimpl
};
-DirWatcher::DirWatcher(const Zstring& directory) :
+DirWatcher::DirWatcher(const Zstring& dirPath) :
+ baseDirPath(dirPath),
pimpl_(zen::make_unique<Pimpl>())
{
- CFStringRef dirpathCf = osx::createCFString(directory.c_str()); //returns nullptr on error
+ CFStringRef dirpathCf = osx::createCFString(baseDirPath.c_str()); //returns nullptr on error
if (!dirpathCf)
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"Function call failed: createCFString"); //no error code documented!
+ throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"Function call failed: createCFString"); //no error code documented!
ZEN_ON_SCOPE_EXIT(::CFRelease(dirpathCf));
CFArrayRef dirpathCfArray = ::CFArrayCreate(nullptr, //CFAllocatorRef allocator,
@@ -568,7 +580,7 @@ DirWatcher::DirWatcher(const Zstring& directory) :
1, //CFIndex numValues,
nullptr); //const CFArrayCallBacks* callBacks
if (!dirpathCfArray)
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"Function call failed: CFArrayCreate"); //no error code documented!
+ throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"Function call failed: CFArrayCreate"); //no error code documented!
ZEN_ON_SCOPE_EXIT(::CFRelease(dirpathCfArray));
FSEventStreamContext context = {};
@@ -594,7 +606,7 @@ DirWatcher::DirWatcher(const Zstring& directory) :
zen::ScopeGuard guardRunloop = zen::makeGuard([&] { ::FSEventStreamInvalidate(pimpl_->eventStream); });
if (!::FSEventStreamStart(pimpl_->eventStream))
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"Function call failed: FSEventStreamStart"); //no error code documented!
+ throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"Function call failed: FSEventStreamStart"); //no error code documented!
guardCreate .dismiss();
guardRunloop.dismiss();
diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h
index b5255898..cdc80165 100644
--- a/zen/dir_watcher.h
+++ b/zen/dir_watcher.h
@@ -21,7 +21,8 @@ namespace zen
//watch directory including subdirectories
/*
!Note handling of directories!:
- Windows: removal of top watched directory is NOT notified (e.g. brute force usb stick removal)
+ Windows: removal of top watched directory is NOT notified when watching the dir handle, e.g. brute force usb stick removal,
+ (watchting for GUID_DEVINTERFACE_WPD OTOH works fine!)
however manual unmount IS notified (e.g. usb stick removal, then re-insert), but watching is stopped!
Renaming of top watched directory handled incorrectly: Not notified(!) + additional changes in subfolders
now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!)
@@ -36,7 +37,7 @@ namespace zen
class DirWatcher
{
public:
- DirWatcher(const Zstring& directory); //throw FileError
+ DirWatcher(const Zstring& dirPath); //throw FileError
~DirWatcher();
enum ActionType
@@ -62,6 +63,8 @@ private:
DirWatcher (const DirWatcher&) = delete;
DirWatcher& operator=(const DirWatcher&) = delete;
+ const Zstring baseDirPath;
+
struct Pimpl;
std::unique_ptr<Pimpl> pimpl_;
};
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index ffbdc813..b1b781ee 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -12,8 +12,8 @@
#include "file_traverser.h"
#include "scope_guard.h"
#include "symlink_target.h"
-#include "file_io.h"
#include "file_id_def.h"
+#include "serialize.h"
#ifdef ZEN_WIN
#include <Aclapi.h>
@@ -133,67 +133,38 @@ bool zen::somethingExists(const Zstring& objname)
namespace
{
#ifdef ZEN_WIN
-//fast ::GetVolumePathName() clone: let's hope it's not too simple (doesn't honor mount points)
-Zstring getVolumeNameFast(const Zstring& filepath)
+bool isFatDrive(const Zstring& filePath) //throw()
{
- //this call is expensive: ~1.5 ms!
- // if (!::GetVolumePathName(filepath.c_str(), //__in LPCTSTR lpszFileName,
- // fsName, //__out LPTSTR lpszVolumePathName,
- // BUFFER_SIZE)) //__in DWORD cchBufferLength
- // ...
- // Zstring volumePath = appendSeparator(fsName);
-
- const Zstring nameFmt = appendSeparator(removeLongPathPrefix(filepath)); //throw()
+ const DWORD bufferSize = MAX_PATH + 1;
+ std::vector<wchar_t> buffer(bufferSize);
- if (startsWith(nameFmt, Zstr("\\\\"))) //UNC path: "\\ComputerName\SharedFolder\"
- {
- size_t nameSize = nameFmt.size();
- const size_t posFirstSlash = nameFmt.find(Zstr("\\"), 2);
- if (posFirstSlash != Zstring::npos)
- {
- nameSize = posFirstSlash + 1;
- const size_t posSecondSlash = nameFmt.find(Zstr("\\"), posFirstSlash + 1);
- if (posSecondSlash != Zstring::npos)
- nameSize = posSecondSlash + 1;
- }
- return Zstring(nameFmt.c_str(), nameSize); //include trailing backslash!
- }
- else //local path: "C:\Folder\"
+ //this call is expensive: ~1.5 ms!
+ if (!::GetVolumePathName(filePath.c_str(), //__in LPCTSTR lpszFileName,
+ &buffer[0], //__out LPTSTR lpszVolumePathName,
+ bufferSize)) //__in DWORD cchBufferLength
{
- const size_t pos = nameFmt.find(Zstr(":\\"));
- if (pos == 1) //expect single letter volume
- return Zstring(nameFmt.c_str(), 3);
- }
-
- return Zstring();
-}
-
-
-bool isFatDrive(const Zstring& filepath) //throw()
-{
- const Zstring volumePath = getVolumeNameFast(filepath);
- if (volumePath.empty())
+ assert(false);
return false;
+ }
- const DWORD bufferSize = MAX_PATH + 1;
- wchar_t fsName[bufferSize] = {};
+ const Zstring volumePath = appendSeparator(&buffer[0]);
//suprisingly fast: ca. 0.03 ms per call!
- if (!::GetVolumeInformation(appendSeparator(volumePath).c_str(), //__in_opt LPCTSTR lpRootPathName,
+ if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
nullptr, //__out LPTSTR lpVolumeNameBuffer,
0, //__in DWORD nVolumeNameSize,
nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
nullptr, //__out_opt LPDWORD lpMaximumComponentLength,
nullptr, //__out_opt LPDWORD lpFileSystemFlags,
- fsName, //__out LPTSTR lpFileSystemNameBuffer,
+ &buffer[0], //__out LPTSTR lpFileSystemNameBuffer,
bufferSize)) //__in DWORD nFileSystemNameSize
{
- assert(false); //shouldn't happen
+ assert(false);
return false;
}
- //DST hack seems to be working equally well for FAT and FAT32 (in particular creation time has 10^-2 s precision as advertised)
- return fsName == Zstring(L"FAT") ||
- fsName == Zstring(L"FAT32");
+
+ return &buffer[0] == Zstring(L"FAT") ||
+ &buffer[0] == Zstring(L"FAT32");
}
@@ -267,7 +238,7 @@ std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError
}
-std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError
+std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0 if not available
{
#ifdef ZEN_WIN
ULARGE_INTEGER bytesFree = {};
@@ -275,14 +246,15 @@ std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError
&bytesFree, //__out_opt PULARGE_INTEGER lpFreeBytesAvailable,
nullptr, //__out_opt PULARGE_INTEGER lpTotalNumberOfBytes,
nullptr)) //__out_opt PULARGE_INTEGER lpTotalNumberOfFreeBytes
- throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)), L"GetDiskFreeSpaceEx", getLastError());
+ throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"GetDiskFreeSpaceEx", getLastError());
+ //return 0 if info is not available: "The GetDiskFreeSpaceEx function returns zero for lpFreeBytesAvailable for all CD requests"
return get64BitUInt(bytesFree.LowPart, bytesFree.HighPart);
#elif defined ZEN_LINUX || defined ZEN_MAC
struct ::statfs info = {};
if (::statfs(path.c_str(), &info) != 0)
- throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)), L"statfs", getLastError());
+ throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"statfs", getLastError());
return static_cast<std::uint64_t>(info.f_bsize) * info.f_bavail;
#endif
@@ -293,8 +265,8 @@ bool zen::removeFile(const Zstring& filepath) //throw FileError
{
#ifdef ZEN_WIN
const wchar_t functionName[] = L"DeleteFile";
- const Zstring& filepathFmt = applyLongPathPrefix(filepath);
- if (!::DeleteFile(filepathFmt.c_str()))
+
+ if (!::DeleteFile(applyLongPathPrefix(filepath).c_str()))
#elif defined ZEN_LINUX || defined ZEN_MAC
const wchar_t functionName[] = L"unlink";
if (::unlink(filepath.c_str()) != 0)
@@ -304,9 +276,9 @@ bool zen::removeFile(const Zstring& filepath) //throw FileError
#ifdef ZEN_WIN
if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only
{
- ::SetFileAttributes(filepathFmt.c_str(), FILE_ATTRIBUTE_NORMAL); //(try to) normalize file attributes
+ ::SetFileAttributes(applyLongPathPrefix(filepath).c_str(), FILE_ATTRIBUTE_NORMAL); //(try to) normalize file attributes
- if (::DeleteFile(filepathFmt.c_str())) //now try again...
+ if (::DeleteFile(applyLongPathPrefix(filepath).c_str())) //now try again...
return true;
lastError = ::GetLastError();
}
@@ -380,7 +352,7 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File
}
}
}
-
+ //begin of "regular" error reporting
const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(oldName)), L"%y", L"\n" + fmtFileName(newName));
std::wstring errorDescr = formatSystemError(L"MoveFileEx", lastError);
@@ -480,8 +452,8 @@ bool have8dot3NameClash(const Zstring& filepath)
if (!shortName.empty() &&
!longName .empty() &&
- EqualFilename()(origName, shortName) &&
- !EqualFilename()(shortName, longName))
+ 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"
@@ -526,21 +498,21 @@ private:
//rename file: no copying!!!
-void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+void zen::renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
{
try
{
- renameFile_sub(oldName, newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+ renameFile_sub(itemPathOld, itemPathNew); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
}
catch (const ErrorTargetExisting&)
{
#ifdef ZEN_WIN
//try to handle issues with already existing short 8.3 file names on Windows
- if (have8dot3NameClash(newName))
+ if (have8dot3NameClash(itemPathNew))
{
- Fix8Dot3NameClash dummy(newName); //throw FileError; move clashing filepath to the side
+ Fix8Dot3NameClash dummy(itemPathNew); //throw FileError; move clashing filepath to the side
//now try again...
- renameFile_sub(oldName, newName); //throw FileError
+ renameFile_sub(itemPathOld, itemPathNew); //throw FileError
return;
}
#endif
@@ -964,8 +936,8 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink
// - utimensat() is supposed to obsolete utime/utimes and is also used by "touch"
//=> let's give utimensat another chance:
struct ::timespec newTimes[2] = {};
- newTimes[0].tv_nsec = UTIME_OMIT; //omit access time
- newTimes[1].tv_sec = modTime; //modification time (seconds)
+ newTimes[0].tv_sec = modTime; //access time: using UTIME_OMIT for tv_nsec would trigger even more bugs!! https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/218564cf/
+ newTimes[1].tv_sec = modTime; //modification time (seconds)
if (procSl == ProcSymlink::FOLLOW)
{
@@ -1016,13 +988,16 @@ bool zen::supportsPermissions(const Zstring& dirpath) //throw FileError
#ifdef ZEN_WIN
const DWORD bufferSize = MAX_PATH + 1;
std::vector<wchar_t> buffer(bufferSize);
+
if (!::GetVolumePathName(dirpath.c_str(), //__in LPCTSTR lpszFileName,
&buffer[0], //__out LPTSTR lpszVolumePathName,
bufferSize)) //__in DWORD cchBufferLength
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(dirpath)), L"GetVolumePathName", getLastError());
+ const Zstring volumePath = appendSeparator(&buffer[0]);
+
DWORD fsFlags = 0;
- if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName,
+ if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
nullptr, //__out LPTSTR lpVolumeNameBuffer,
0, //__in DWORD nVolumeNameSize,
nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
@@ -1309,7 +1284,7 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget
try
{
- makeDirectoryPlain(directory, Zstring(), false); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+ copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
}
catch (const ErrorTargetPathMissing&)
{
@@ -1322,10 +1297,10 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget
{
makeDirectoryRecursively(dirParent); //throw FileError, (ErrorTargetExisting)
}
- catch (const ErrorTargetExisting&) { /*parent directory created externally in the meantime? => NOT AN ERROR*/ }
+ catch (const ErrorTargetExisting&) {} //parent directory created externally in the meantime? => NOT AN ERROR; not a directory? fail in next step!
//now try again...
- makeDirectoryPlain(directory, Zstring(), false); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing)
+ copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing)
return;
}
throw;
@@ -1334,7 +1309,7 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget
}
-void zen::makeDirectory(const Zstring& directory, bool failIfExists) //throw FileError, ErrorTargetExisting
+void zen::makeNewDirectory(const Zstring& directory) //throw FileError, ErrorTargetExisting
{
//remove trailing separator (even for C:\ root directories)
const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ?
@@ -1345,40 +1320,22 @@ void zen::makeDirectory(const Zstring& directory, bool failIfExists) //throw Fil
{
makeDirectoryRecursively(dirFormatted); //FileError, ErrorTargetExisting
}
- catch (const ErrorTargetExisting&)
- {
- //avoid any file system race-condition by *not* checking directory existence again here!!!
- if (failIfExists)
- throw;
- }
- catch (const FileError&)
+ catch (const ErrorTargetExisting&) //*something* existing: folder or FILE!
{
- /*
- could there be situations where a directory/network path exists,
- but creation fails with error different than "ErrorTargetExisting"??
- - creation of C:\ fails with ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS
- */
- if (somethingExists(directory)) //a file system race-condition! don't use dirExists() => harmonize with ErrorTargetExisting!
- {
- assert(false);
- if (failIfExists)
- throw; //do NOT convert to ErrorTargetExisting: if "failIfExists", not getting a ErrorTargetExisting *atomically* is unexpected!
- }
- else
+ //avoid any file system race-condition by *not* checking existence again here!!!
throw;
}
}
-void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
- const Zstring& templateDir,
+void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
bool copyFilePermissions)
{
#ifdef ZEN_WIN
//special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS!
- Zstring dirTmp = removeLongPathPrefix(endsWith(directory, FILE_NAME_SEPARATOR) ?
- beforeLast(directory, FILE_NAME_SEPARATOR) :
- directory);
+ Zstring dirTmp = removeLongPathPrefix(endsWith(targetPath, FILE_NAME_SEPARATOR) ?
+ beforeLast(targetPath, FILE_NAME_SEPARATOR) :
+ targetPath);
if (dirTmp.size() == 2 &&
std::iswalpha(dirTmp[0]) && dirTmp[1] == L':')
{
@@ -1398,19 +1355,19 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
//- it may fail with "wrong parameter (error code 87)" when source is on mapped online storage
//- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)!
//- it isn't able to copy most junctions because of missing permissions (although target path can be retrieved alternatively!)
- if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), //__in LPCTSTR lpPathName,
+ if (!::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), //__in LPCTSTR lpPathName,
nullptr)) //__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes
{
DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls!
//handle issues with already existing short 8.3 file names on Windows
if (lastError == ERROR_ALREADY_EXISTS)
- if (have8dot3NameClash(directory))
+ if (have8dot3NameClash(targetPath))
{
- Fix8Dot3NameClash dummy(directory); //throw FileError; move clashing object to the side
+ Fix8Dot3NameClash dummy(targetPath); //throw FileError; move clashing object to the side
//now try again...
- if (::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), nullptr))
+ if (::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), nullptr))
lastError = ERROR_SUCCESS;
else
lastError = ::GetLastError();
@@ -1418,7 +1375,7 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
if (lastError != ERROR_SUCCESS)
{
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory));
+ const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(targetPath));
const std::wstring errorDescr = formatSystemError(L"CreateDirectory", lastError);
if (lastError == ERROR_ALREADY_EXISTS)
@@ -1433,18 +1390,18 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory
struct ::stat dirInfo = {};
- if (!templateDir.empty())
- if (::stat(templateDir.c_str(), &dirInfo) == 0)
+ if (!sourcePath.empty())
+ if (::stat(sourcePath.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 copyItemPermissions() only for "chown" and umask-agnostic permissions
- if (::mkdir(directory.c_str(), mode) != 0)
+ if (::mkdir(targetPath.c_str(), mode) != 0)
{
const int lastError = errno; //copy before directly or indirectly making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory));
+ const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(targetPath));
const std::wstring errorDescr = formatSystemError(L"mkdir", lastError);
if (lastError == EEXIST)
@@ -1455,11 +1412,11 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
}
#endif
- if (!templateDir.empty())
+ if (!sourcePath.empty())
{
#ifdef ZEN_WIN
//optional: try to copy file attributes (dereference symlinks and junctions)
- const HANDLE hDirSrc = ::CreateFile(zen::applyLongPathPrefix(templateDir).c_str(), //_In_ LPCTSTR lpFileName,
+ const HANDLE hDirSrc = ::CreateFile(zen::applyLongPathPrefix(sourcePath).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,
@@ -1474,16 +1431,16 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
BY_HANDLE_FILE_INFORMATION dirInfo = {};
if (::GetFileInformationByHandle(hDirSrc, &dirInfo))
{
- ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), dirInfo.dwFileAttributes);
+ ::SetFileAttributes(applyLongPathPrefix(targetPath).c_str(), dirInfo.dwFileAttributes);
//copy "read-only and system attributes": http://blogs.msdn.com/b/oldnewthing/archive/2003/09/30/55100.aspx
const bool isEncrypted = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0;
const bool isCompressed = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
if (isEncrypted)
- ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!)
+ ::EncryptFile(targetPath.c_str()); //seems no long path is required (check passed!)
- HANDLE hDirTrg = ::CreateFile(applyLongPathPrefix(directory).c_str(), //_In_ LPCTSTR lpFileName,
+ HANDLE hDirTrg = ::CreateFile(applyLongPathPrefix(targetPath).c_str(), //_In_ LPCTSTR lpFileName,
GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess,
/*read access required for FSCTL_SET_COMPRESSION*/
FILE_SHARE_READ |
@@ -1521,14 +1478,14 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
}
#elif defined ZEN_MAC
- /*int rv =*/ ::copyfile(templateDir.c_str(), directory.c_str(), 0, COPYFILE_XATTR);
+ /*int rv =*/ ::copyfile(sourcePath.c_str(), targetPath.c_str(), 0, COPYFILE_XATTR);
#endif
- zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(directory); } catch (FileError&) {} }); //ensure cleanup:
+ zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(targetPath); } catch (FileError&) {} }); //ensure cleanup:
//enforce copying file permissions: it's advertized on GUI...
if (copyFilePermissions)
- copyItemPermissions(templateDir, directory, ProcSymlink::FOLLOW); //throw FileError
+ copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError
guardNewDir.dismiss(); //target has been created successfully!
}
@@ -1663,7 +1620,7 @@ bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw ()
return false; //small perf optimization: don't check "targetFile" if not needed
//------------------------------------------------------------------------------------
- const DWORD bufferSize = 10000;
+ const DWORD bufferSize = MAX_PATH + 1;
std::vector<wchar_t> buffer(bufferSize);
//full pathName need not yet exist!
@@ -1672,8 +1629,10 @@ bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw ()
bufferSize)) //__in DWORD cchBufferLength
return false;
+ const Zstring volumePath = appendSeparator(&buffer[0]);
+
DWORD fsFlagsTarget = 0;
- if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName
+ if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName
nullptr, //__out_opt LPTSTR lpVolumeNameBuffer,
0, //__in DWORD nVolumeNameSize,
nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
@@ -1715,10 +1674,9 @@ bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //thr
//precondition: canCopyAsSparse() must return "true"!
-void copyFileWindowsSparse(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting, ErrorFileLocked
+InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
assert(canCopyAsSparse(sourceFile, targetFile));
@@ -1810,13 +1768,11 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"GetFileInformationByHandle", getLastError());
//return up-to-date file attributes
- if (newAttrib)
- {
- newAttrib->fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh);
- newAttrib->modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); //no DST hack (yet)
- newAttrib->sourceFileId = extractFileId(fileInfoSource);
- newAttrib->targetFileId = extractFileId(fileInfoTarget);
- }
+ InSyncAttributes newAttrib = {};
+ newAttrib.fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh);
+ newAttrib.modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); //no DST hack (yet)
+ newAttrib.sourceFileId = extractFileId(fileInfoSource);
+ newAttrib.targetFileId = extractFileId(fileInfoTarget);
//#################### copy NTFS compressed attribute #########################
const bool sourceIsCompressed = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
@@ -1860,7 +1816,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
}
//----------------------------------------------------------------------
- const DWORD BUFFER_SIZE = 128 * 1024; //must be greater than sizeof(WIN32_STREAM_ID)
+ const DWORD BUFFER_SIZE = std::max(128 * 1024, static_cast<int>(sizeof(WIN32_STREAM_ID))); //must be greater than sizeof(WIN32_STREAM_ID)!
std::vector<BYTE> buffer(BUFFER_SIZE);
LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite()
@@ -1886,7 +1842,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead", getLastError()); //better use fine-granular error messages "reading/writing"!
if (bytesRead > BUFFER_SIZE)
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"buffer overflow"); //user should never see this
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this
if (bytesRead < BUFFER_SIZE)
eof = true;
@@ -1902,7 +1858,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)), L"BackupWrite", getLastError());
if (bytesWritten != bytesRead)
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)), L"incomplete write"); //user should never see this
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)), L"BackupWrite: incomplete write."); //user should never see this
//total bytes transferred may be larger than file size! context information + ADS or smaller (sparse, compressed)!
@@ -1918,7 +1874,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
//::BackupRead() silently fails reading encrypted files -> double check!
if (!someBytesWritten && get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U)
//note: there is no guaranteed ordering relation beween bytes transferred and file size! Consider ADS (>) and compressed/sparse files (<)!
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()"
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()"
//time needs to be set at the end: BackupWrite() changes modification time
if (!::SetFileTime(hFileTarget,
@@ -1928,6 +1884,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)), L"SetFileTime", getLastError());
guardTarget.dismiss();
+ return newAttrib;
}
@@ -2049,13 +2006,12 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destination is not supported with Windows 2000
-//caveat: function scope static initialization is not thread-safe in VS 2010!
+//caveat: function scope static initialization is not thread-safe in VS 2010! -> still not sufficient if multiple threads access during static init!!!
-void copyFileWindowsDefault(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse
+InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
//try to get backup read and write privileges: who knows, maybe this helps solve some obscure "access denied" errors
try { activatePrivilege(SE_BACKUP_NAME); }
@@ -2129,7 +2085,7 @@ void copyFileWindowsDefault(const Zstring& sourceFile,
if (lastError == 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 gigabyte.";
+ 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 filepath is of a restricted type.
@@ -2139,47 +2095,45 @@ void copyFileWindowsDefault(const Zstring& sourceFile,
throw FileError(errorMsg, errorDescr);
}
- if (newAttrib)
- {
- newAttrib->fileSize = get64BitUInt(cbd.fileInfoSrc.nFileSizeLow, cbd.fileInfoSrc.nFileSizeHigh);
- newAttrib->modificationTime = filetimeToTimeT(cbd.fileInfoSrc.ftLastWriteTime);
- newAttrib->sourceFileId = extractFileId(cbd.fileInfoSrc);
- newAttrib->targetFileId = extractFileId(cbd.fileInfoTrg);
- }
-
//caveat: - ::CopyFileEx() silently *ignores* failure to set modification time!!! => we always need to set it again but with proper error checking!
// - 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!
+
+ InSyncAttributes newAttrib = {};
+ newAttrib.fileSize = get64BitUInt(cbd.fileInfoSrc.nFileSizeLow, cbd.fileInfoSrc.nFileSizeHigh);
+ newAttrib.modificationTime = filetimeToTimeT(cbd.fileInfoSrc.ftLastWriteTime);
+ newAttrib.sourceFileId = extractFileId(cbd.fileInfoSrc);
+ newAttrib.targetFileId = extractFileId(cbd.fileInfoTrg);
+ return newAttrib;
}
//another layer to support copying sparse files
inline
-void copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus, InSyncAttributes* sourceAttr)
+InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
try
{
- copyFileWindowsDefault(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse
+ return copyFileWindowsDefault(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse
}
catch (ErrorShouldCopyAsSparse&) //we quickly check for this condition within callback of ::CopyFileEx()!
{
- copyFileWindowsSparse(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ return copyFileWindowsSparse(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
}
}
//another layer of indirection solving 8.3 name clashes
inline
-void copyFileOsSpecific(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* sourceAttr)
+InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
try
{
- copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
}
catch (const ErrorTargetExisting&)
{
@@ -2187,8 +2141,7 @@ void copyFileOsSpecific(const Zstring& sourceFile,
if (have8dot3NameClash(targetFile))
{
Fix8Dot3NameClash dummy(targetFile); //throw FileError; move clashing filepath to the side
- copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError; the short filepath name clash is solved, this should work now
- return;
+ return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError; the short filepath name clash is solved, this should work now
}
throw;
}
@@ -2196,10 +2149,9 @@ void copyFileOsSpecific(const Zstring& sourceFile,
#elif defined ZEN_LINUX || defined ZEN_MAC
-void copyFileOsSpecific(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting
+InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
FileInput fileIn(sourceFile); //throw FileError
@@ -2221,40 +2173,31 @@ void copyFileOsSpecific(const Zstring& sourceFile,
throw FileError(errorMsg, errorDescr);
}
+ if (onUpdateCopyStatus) onUpdateCopyStatus(0); //throw X!
+ InSyncAttributes newAttrib = {};
zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} });
//transactional behavior: place guard after ::open() and before lifetime of FileOutput:
//=> don't delete file that existed previously!!!
{
FileOutput fileOut(fdTarget, targetFile); //pass ownership
+ if (onUpdateCopyStatus) onUpdateCopyStatus(0); //throw X!
- std::vector<char> buffer(128 * 1024);
- do
- {
- const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError
-
- fileOut.write(&buffer[0], bytesRead); //throw FileError
-
- if (onUpdateCopyStatus)
- onUpdateCopyStatus(bytesRead); //throw X!
- }
- while (!fileIn.eof());
+ copyStream(fileIn, fileOut, std::min(fileIn .optimalBlockSize(),
+ fileOut.optimalBlockSize()), onUpdateCopyStatus); //throw FileError, X
struct ::stat targetInfo = {};
if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError());
- if (newAttrib) //return file statistics
- {
- newAttrib->fileSize = sourceInfo.st_size;
+ newAttrib.fileSize = sourceInfo.st_size;
#ifdef ZEN_MAC
- newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setFileTimeRaw() for consistency
+ newAttrib.modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setFileTimeRaw() for consistency
#else
- newAttrib->modificationTime = sourceInfo.st_mtime;
+ newAttrib.modificationTime = sourceInfo.st_mtime;
#endif
- newAttrib->sourceFileId = extractFileId(sourceInfo);
- newAttrib->targetFileId = extractFileId(targetInfo);
- }
+ newAttrib.sourceFileId = extractFileId(sourceInfo);
+ newAttrib.targetFileId = extractFileId(targetInfo);
#ifdef ZEN_MAC
//using ::copyfile with COPYFILE_DATA seems to trigger bugs unlike our stream-based copying!
@@ -2265,6 +2208,8 @@ void copyFileOsSpecific(const Zstring& sourceFile,
if (::fcopyfile(fileIn.getHandle(), fileOut.getHandle(), 0, COPYFILE_XATTR) != 0)
throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)), L"copyfile", getLastError());
#endif
+
+fileOut.close(); //throw FileError -> optional, but good place to catch errors when closing stream!
} //close output file handle before setting file time
//we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
@@ -2272,7 +2217,7 @@ void copyFileOsSpecific(const Zstring& sourceFile,
//Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
// http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
//OS X: https://sourceforge.net/p/freefilesync/discussion/help/thread/881357c0/
-#ifdef ZEN_MAC
+#ifdef ZEN_MAC
setFileTimeRaw(targetFile, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::FOLLOW); //throw FileError
//sourceInfo.st_birthtime; -> only seconds-precision
//sourceInfo.st_mtime; ->
@@ -2281,6 +2226,7 @@ void copyFileOsSpecific(const Zstring& sourceFile,
#endif
guardTarget.dismiss(); //target has been created successfully!
+ return newAttrib;
}
#endif
@@ -2288,25 +2234,21 @@ void copyFileOsSpecific(const Zstring& sourceFile,
------------------
|File Copy Layers|
------------------
- copyFile (setup transactional behavior)
+ copyNewFile
|
- copyFileWithPermissions
- |
- copyFileOsSpecific (solve 8.3 issue)
+ copyFileOsSpecific (solve 8.3 issue on Windows)
|
copyFileWindowsSelectRoutine
/ \
copyFileWindowsDefault(::CopyFileEx) copyFileWindowsSparse(::BackupRead/::BackupWrite)
*/
+}
-inline
-void copyFileWithPermissions(const Zstring& sourceFile,
- const Zstring& targetFile,
- bool copyFilePermissions,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* sourceAttr)
+
+InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
- copyFileOsSpecific(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
if (copyFilePermissions)
{
@@ -2317,67 +2259,6 @@ void copyFileWithPermissions(const Zstring& sourceFile,
guardTargetFile.dismiss(); //target has been created successfully!
}
-}
-}
-
-
-void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorFileLocked
- const Zstring& targetFile,
- bool copyFilePermissions,
- bool transactionalCopy,
- const std::function<void()>& onDeleteTargetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* sourceAttr)
-{
- if (transactionalCopy)
- {
- Zstring tmpTarget = targetFile + TEMP_FILE_ENDING;
-
- for (int i = 0;; ++i)
- try
- {
- copyFileWithPermissions(sourceFile, tmpTarget, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
- break;
- }
- catch (const ErrorTargetExisting&) //optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses
- {
- if (i == 10) throw; //avoid endless recursion in pathological cases, e.g. https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/36adac33
- tmpTarget = targetFile + Zchar('_') + numberTo<Zstring>(i) + TEMP_FILE_ENDING;
- }
-
- //transactional behavior: ensure cleanup; not needed before copyFileWithPermissions() which is already transactional
- zen::ScopeGuard guardTempFile = zen::makeGuard([&] { try { removeFile(tmpTarget); } catch (FileError&) {} });
-
- //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite
- if (onDeleteTargetFile)
- onDeleteTargetFile(); //throw X
-
- //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick!
- renameFile(tmpTarget, targetFile); //throw FileError
-
- /*
- CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" does
- NOT PRESERVE the creation time of the .ffs_tmp file, but SILENTLY "reuses" whatever creation time the old "file.txt" had!
- This "feature" is called "File System Tunneling":
- http://blogs.msdn.com/b/oldnewthing/archive/2005/07/15/439261.aspx
- http://support.microsoft.com/kb/172190/en-us
- */
-
- guardTempFile.dismiss();
- }
- else
- {
- /*
- Note: non-transactional file copy solves at least four problems:
- -> skydrive - doesn't allow for .ffs_tmp extension and returns ERROR_INVALID_PARAMETER
- -> network renaming issues
- -> allow for true delete before copy to handle low disk space problems
- -> higher performance on non-buffered drives (e.g. usb sticks)
- */
- if (onDeleteTargetFile)
- onDeleteTargetFile();
-
- copyFileWithPermissions(sourceFile, targetFile, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
- }
+ return attr;
}
diff --git a/zen/file_access.h b/zen/file_access.h
index bd1b0168..0dfb650e 100644
--- a/zen/file_access.h
+++ b/zen/file_access.h
@@ -29,7 +29,7 @@ void setFileTime(const Zstring& filepath, std::int64_t modificationTime, ProcSym
//symlink handling: always evaluate target
std::uint64_t getFilesize(const Zstring& filepath); //throw FileError
-std::uint64_t getFreeDiskSpace(const Zstring& path); //throw FileError
+std::uint64_t getFreeDiskSpace(const Zstring& path); //throw FileError, returns 0 if not available
bool removeFile(const Zstring& filepath); //throw FileError; return "false" if file is not existing
void removeDirectory(const Zstring& directory, //throw FileError
@@ -37,17 +37,18 @@ void removeDirectory(const Zstring& directory, //throw FileError
const std::function<void (const Zstring& dirpath )>& onBeforeDirDeletion = nullptr); //one call for each *existing* object!
//rename file or directory: no copying!!!
-void renameFile(const Zstring& oldName, const Zstring& newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+void renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
bool supportsPermissions(const Zstring& dirpath); //throw FileError, dereferences symlinks
//if parent directory not existing: create recursively:
-void makeDirectory(const Zstring& directory, bool failIfExists = false); //throw FileError, ErrorTargetExisting
+void makeNewDirectory(const Zstring& directory); //throw FileError, ErrorTargetExisting
//fail if already existing or parent directory not existing:
//directory should not end with path separator
-//templateDir may be empty
-void makeDirectoryPlain(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+void copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, bool copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+
+void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError
struct InSyncAttributes
{
@@ -57,24 +58,9 @@ struct InSyncAttributes
FileId targetFileId;
};
-void copyFile(const Zstring& sourceFile, //throw FileError, ErrorFileLocked (Windows-only)
- const Zstring& targetFile, //symlink handling: dereference source
- bool copyFilePermissions,
- bool transactionalCopy,
- //if target is existing user needs to implement deletion: copyFile() NEVER overwrites target if already existing!
- //if transactionalCopy == true, full read access on source had been proven at this point, so it's safe to delete it.
- const std::function<void()>& onDeleteTargetFile, //may be nullptr; may throw!
- //accummulated delta != file size! consider ADS, sparse, compressed files
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus, //may be nullptr; may throw!
-
- InSyncAttributes* newAttrib = nullptr); //return current attributes at the time of copy
-
-//Note: it MAY happen that copyFile() leaves temp files behind, e.g. temporary network drop.
-// => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending:
-
-const Zchar TEMP_FILE_ENDING[] = Zstr(".ffs_tmp"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace!
-
-void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError
+InSyncAttributes copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ //accummulated delta != file size! consider ADS, sparse, compressed files
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus); //may be nullptr; throw X!
}
#endif //FILE_ACCESS_H_8017341345614857
diff --git a/zen/file_id_def.h b/zen/file_id_def.h
index a96e978b..7a2059a6 100644
--- a/zen/file_id_def.h
+++ b/zen/file_id_def.h
@@ -23,7 +23,8 @@ namespace zen
typedef DWORD DeviceId;
typedef ULONGLONG FileIndex;
-typedef std::pair<DeviceId, FileIndex> FileId;
+typedef std::pair<DeviceId, FileIndex> FileId; //optional! (however, always set on Linux, and *generally* available on Windows)
+
inline
FileId extractFileId(const BY_HANDLE_FILE_INFORMATION& fileInfo)
diff --git a/zen/file_io.cpp b/zen/file_io.cpp
index d4bfdd9b..a5412f19 100644
--- a/zen/file_io.cpp
+++ b/zen/file_io.cpp
@@ -5,6 +5,7 @@
// **************************************************************************
#include "file_io.h"
+#include "file_access.h"
#ifdef ZEN_WIN
#include "long_path_prefix.h"
@@ -75,10 +76,10 @@ void checkForUnsupportedType(const Zstring& filepath) //throw FileError
}
-FileInput::FileInput(FileHandle handle, const Zstring& filepath) : FileInputBase(filepath), fileHandle(handle) {}
+FileInput::FileInput(FileHandle handle, const Zstring& filepath) : FileBase(filepath), fileHandle(handle) {}
-FileInput::FileInput(const Zstring& filepath) : FileInputBase(filepath) //throw FileError
+FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileError, ErrorFileLocked
{
#ifdef ZEN_WIN
auto createHandle = [&](DWORD dwShareMode)
@@ -135,6 +136,7 @@ FileInput::FileInput(const Zstring& filepath) : FileInputBase(filepath) //throw
const Zstring procList = getLockingProcessNames(filepath); //throw()
if (!procList.empty())
errorDescr = _("The file is locked by another process:") + L"\n" + procList;
+ throw ErrorFileLocked(errorMsg, errorDescr);
}
throw FileError(errorMsg, errorDescr);
}
@@ -169,62 +171,50 @@ FileInput::~FileInput()
size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError; returns actual number of bytes read
{
- assert(!eof() || bytesToRead == 0);
-#ifdef ZEN_WIN
- if (bytesToRead == 0) return 0;
-
- DWORD bytesRead = 0;
- if (!::ReadFile(fileHandle, //__in HANDLE hFile,
- buffer, //__out LPVOID lpBuffer,
- static_cast<DWORD>(bytesToRead), //__in DWORD nNumberOfBytesToRead,
- &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead,
- nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped
- throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"ReadFile", getLastError());
-
- if (bytesRead < bytesToRead) //verify only!
- setEof(); //
-
- if (bytesRead > bytesToRead)
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this
-
- return bytesRead;
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-8.23.tar.xz
size_t bytesReadTotal = 0;
- while (bytesToRead > 0 && !eof()) //"read() with a count of 0 returns zero" => indistinguishable from eof! => check!
+ while (bytesToRead > 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
{
+#ifdef ZEN_WIN
+ //test for end of file: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365690%28v=vs.85%29.aspx
+ DWORD bytesRead = 0;
+ if (!::ReadFile(fileHandle, //__in HANDLE hFile,
+ buffer, //__out LPVOID lpBuffer,
+ static_cast<DWORD>(bytesToRead), //__in DWORD nNumberOfBytesToRead,
+ &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead,
+ nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped
+ throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilePath())), L"ReadFile", getLastError());
+
+#elif defined ZEN_LINUX || defined ZEN_MAC
ssize_t bytesRead = 0;
do
{
bytesRead = ::read(fileHandle, buffer, bytesToRead);
}
- while (bytesRead < 0 && errno == EINTR);
+ while (bytesRead < 0 && errno == EINTR); //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-8.23.tar.xz
if (bytesRead < 0)
- throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"read", getLastError());
- else if (bytesRead == 0) //"zero indicates end of file"
- setEof();
- else if (bytesRead > static_cast<ssize_t>(bytesToRead)) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this
+ throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilePath())), L"read", getLastError());
+#endif
+ if (bytesRead == 0) //"zero indicates end of file"
+ return bytesReadTotal;
+ else if (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilePath())), L"ReadFile: buffer overflow."); //user should never see this
//if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead" => loop!
buffer = static_cast<char*>(buffer) + bytesRead; //suppress warning about pointer arithmetics on void*
bytesToRead -= bytesRead;
bytesReadTotal += bytesRead;
}
-
return bytesReadTotal;
-#endif
}
//----------------------------------------------------------------------------------------------------
-FileOutput::FileOutput(FileHandle handle, const Zstring& filepath) : FileOutputBase(filepath), fileHandle(handle) {}
+FileOutput::FileOutput(FileHandle handle, const Zstring& filepath) : FileBase(filepath), fileHandle(handle) {}
-FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileOutputBase(filepath) //throw FileError, ErrorTargetExisting
+FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(filepath) //throw FileError, ErrorTargetExisting
{
#ifdef ZEN_WIN
const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW;
@@ -307,12 +297,45 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileOutputB
}
+namespace
+{
+inline
+FileHandle getInvalidHandle()
+{
+#ifdef ZEN_WIN
+ return INVALID_HANDLE_VALUE;
+#elif defined ZEN_LINUX || defined ZEN_MAC
+ return -1;
+#endif
+}
+}
+
+
FileOutput::~FileOutput()
{
+ if (fileHandle != getInvalidHandle())
+ try
+ {
+ close(); //throw FileError
+ }
+ catch (FileError&) { assert(false); }
+}
+
+
+void FileOutput::close() //throw FileError
+{
+ if (fileHandle == getInvalidHandle())
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"Contract error: close() called more than once.");
+ ZEN_ON_SCOPE_EXIT(fileHandle = getInvalidHandle());
+
+ //no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional!
+
#ifdef ZEN_WIN
- ::CloseHandle(fileHandle);
+ if (!::CloseHandle(fileHandle))
+ throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"CloseHandle", getLastError());
#elif defined ZEN_LINUX || defined ZEN_MAC
- ::close(fileHandle);
+ if (::close(fileHandle) != 0)
+ throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"close", getLastError());
#endif
}
@@ -326,10 +349,10 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro
static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite,
&bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten,
nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped
- throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"WriteFile", getLastError());
+ throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"WriteFile", getLastError());
if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes!
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"Incomplete write."); //user should never see this
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"WriteFile: incomplete write."); //user should never see this
#elif defined ZEN_LINUX || defined ZEN_MAC
while (bytesToWrite > 0)
@@ -346,10 +369,10 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro
if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers
errno = ENOSPC;
- throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"write", getLastError());
+ throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"write", getLastError());
}
if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"write: buffer overflow."); //user should never see this
//if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"!
buffer = static_cast<const char*>(buffer) + bytesWritten; //suppress warning about pointer arithmetics on void*
diff --git a/zen/file_io.h b/zen/file_io.h
index ee7841ca..648bafe8 100644
--- a/zen/file_io.h
+++ b/zen/file_io.h
@@ -7,7 +7,6 @@
#ifndef FILEIO_89578342758342572345
#define FILEIO_89578342758342572345
-#include "file_io_base.h"
#include "file_error.h"
#ifdef ZEN_WIN
@@ -31,30 +30,56 @@ namespace zen
typedef int FileHandle;
#endif
-class FileInput : public FileInputBase
+class FileBase
{
public:
- FileInput(const Zstring& filepath); //throw FileError
+ const Zstring& getFilePath() const { return filename_; }
+
+protected:
+ FileBase(const Zstring& filename) : filename_(filename) {}
+
+private:
+ FileBase (const FileBase&) = delete;
+ FileBase& operator=(const FileBase&) = delete;
+
+ const Zstring filename_;
+};
+
+//-----------------------------------------------------------------------------------------------
+
+class FileInput : public FileBase
+{
+public:
+ FileInput(const Zstring& filepath); //throw FileError, ErrorFileLocked
FileInput(FileHandle handle, const Zstring& filepath); //takes ownership!
~FileInput();
- size_t read(void* buffer, size_t bytesToRead) override; //throw FileError; returns actual number of bytes read
+ size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns "bytesToRead", unless end of file!
FileHandle getHandle() { return fileHandle; }
+ size_t optimalBlockSize() const { return 128 * 1024; }
private:
FileHandle fileHandle;
};
-class FileOutput : public FileOutputBase
+class FileOutput : public FileBase
{
public:
+ enum AccessFlag
+ {
+ ACC_OVERWRITE,
+ ACC_CREATE_NEW
+ };
+
FileOutput(const Zstring& filepath, AccessFlag access); //throw FileError, ErrorTargetExisting
FileOutput(FileHandle handle, const Zstring& filepath); //takes ownership!
~FileOutput();
+ void close(); //throw FileError -> optional, but good place to catch errors when closing stream!
- void write(const void* buffer, size_t bytesToWrite) override; //throw FileError
+ void write(const void* buffer, size_t bytesToWrite); //throw FileError
FileHandle getHandle() { return fileHandle; }
+ size_t optimalBlockSize() const { return 128 * 1024; }
private:
FileHandle fileHandle;
diff --git a/zen/file_io_base.h b/zen/file_io_base.h
deleted file mode 100644
index 9b6b27ca..00000000
--- a/zen/file_io_base.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// **************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
-// **************************************************************************
-
-#ifndef FILEIO_BASE_H_INCLUDED_23432431789615314
-#define FILEIO_BASE_H_INCLUDED_23432431789615314
-
-#include "zstring.h"
-
-namespace zen
-{
-class FileBase
-{
-public:
- const Zstring& getFilename() const { return filename_; }
-
-protected:
- FileBase(const Zstring& filename) : filename_(filename) {}
- ~FileBase() {}
-
-private:
- FileBase (const FileBase&) = delete;
- FileBase& operator=(const FileBase&) = delete;
-
- const Zstring filename_;
-};
-
-
-class FileInputBase : public FileBase
-{
-public:
- virtual size_t read(void* buffer, size_t bytesToRead) = 0; //throw FileError; returns actual number of bytes read
- bool eof() const { return eofReached; } //end of file reached
-
-protected:
- FileInputBase(const Zstring& filename) : FileBase(filename), eofReached(false) {}
- ~FileInputBase() {}
- void setEof() { eofReached = true; }
-
-private:
- bool eofReached;
-};
-
-
-class FileOutputBase : public FileBase
-{
-public:
- enum AccessFlag
- {
- ACC_OVERWRITE,
- ACC_CREATE_NEW
- };
- virtual void write(const void* buffer, size_t bytesToWrite) = 0; //throw FileError
-
-protected:
- FileOutputBase(const Zstring& filename) : FileBase(filename) {}
- ~FileOutputBase() {}
-};
-
-}
-
-#endif //FILEIO_BASE_H_INCLUDED_23432431789615314
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index baf40e9e..66dcd198 100644
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -13,12 +13,12 @@
#include "file_access.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> //offsetof
- #include <unistd.h> //::pathconf()
+ #include <unistd.h> //::pathconf()
#include <sys/stat.h>
#include <dirent.h>
#endif
@@ -27,9 +27,9 @@ 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 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)
{
try
@@ -51,13 +51,12 @@ void zen::traverseFolder(const Zstring& dirPath,
}
ZEN_ON_SCOPE_EXIT(::FindClose(hDir));
- bool firstIteration = true;
+ 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))
+ {
+ 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
@@ -69,7 +68,7 @@ void zen::traverseFolder(const Zstring& dirPath,
//skip "." and ".."
const Zchar* const shortName = findData.cFileName;
- if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"Data corruption: Found item without name.");
+ if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"FindNextFile: Data corruption, found item without name.");
if (shortName[0] == L'.' &&
(shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0)))
continue;
@@ -94,32 +93,27 @@ void zen::traverseFolder(const Zstring& dirPath,
}
#elif defined ZEN_LINUX || defined ZEN_MAC
- 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
entryp = malloc(len); */
- 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);
+ const size_t nameMax = std::max<long>(::pathconf(dirPath.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;
+ std::vector<char> bufferUtfDecomposed;
#endif
- DIR* dirObj = ::opendir(dirPathFmt.c_str()); //directory must NOT end with path separator, except "/"
+ DIR* dirObj = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/"
if (!dirObj)
- throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPathFmt)), L"opendir", getLastError());
+ throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPath)), 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(dirPathFmt)), L"readdir_r", getLastError());
+ throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), 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
@@ -128,7 +122,7 @@ void zen::traverseFolder(const Zstring& dirPath,
//don't return "." and ".."
const char* shortName = dirEntry->d_name;
- if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"Data corruption: Found item without name.");
+ if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"readdir_r: Data corruption, found item without name.");
if (shortName[0] == '.' &&
(shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0)))
continue;
@@ -152,20 +146,20 @@ void zen::traverseFolder(const Zstring& dirPath,
//const char* sampleDecomposed = "\x6f\xcc\x81.txt";
//const char* samplePrecomposed = "\xc3\xb3.txt";
#endif
- const Zstring& itempath = appendSeparator(dirPathFmt) + shortName;
+ const Zstring& itempath = appendSeparator(dirPath) + shortName;
struct ::stat statData = {};
- 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
- }
+ 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!
{
diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h
index 19b838ef..dfdf60ba 100644
--- a/zen/long_path_prefix.h
+++ b/zen/long_path_prefix.h
@@ -120,8 +120,8 @@ Zstring zen::ntPathToWin32Path(const Zstring& path) //noexcept
{
std::vector<wchar_t> buf(bufSize);
const DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName,
- &buf[0], //_Out_opt_ LPTSTR lpBuffer,
- bufSize); //_In_ DWORD nSize
+ &buf[0], //_Out_opt_ LPTSTR lpBuffer,
+ bufSize); //_In_ DWORD nSize
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
deleted file mode 100644
index f528e81b..00000000
--- a/zen/notify_removal.cpp
+++ /dev/null
@@ -1,212 +0,0 @@
-// **************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
-// **************************************************************************
-
-#include "notify_removal.h"
-#include <set>
-#include <algorithm>
-#include <dbt.h>
-#include "scope_guard.h"
-
-using namespace zen;
-
-
-class MessageProvider //administrates a single dummy window to receive messages
-{
-public:
- static MessageProvider& instance() //throw FileError
- {
- static MessageProvider inst;
- return inst;
- }
-
- struct Listener
- {
- virtual ~Listener() {}
- virtual void onMessage(UINT message, WPARAM wParam, LPARAM lParam) = 0; //throw()!
- };
- void registerListener(Listener& l) { listener.insert(&l); }
- void unregisterListener(Listener& l) { listener.erase(&l); } //don't unregister objects with static lifetime
-
- HWND getWnd() const { return windowHandle; } //get handle in order to register additional notifications
-
-private:
- MessageProvider();
- ~MessageProvider();
- MessageProvider (const MessageProvider&) = delete;
- MessageProvider& operator=(const MessageProvider&) = delete;
-
- static const wchar_t dummyClassName[];
-
- static LRESULT CALLBACK topWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
-
- void processMessage(UINT message, WPARAM wParam, LPARAM lParam);
-
- const HINSTANCE hMainModule;
- HWND windowHandle;
-
- std::set<Listener*> listener;
-};
-
-
-const wchar_t MessageProvider::dummyClassName[] = L"E6AD5EB1-527B-4EEF-AC75-27883B233380"; //random name
-
-
-LRESULT CALLBACK MessageProvider::topWndProc(HWND hwnd, //handle to window
- UINT uMsg, //message identifier
- WPARAM wParam, //first message parameter
- LPARAM lParam) //second message parameter
-{
- if (auto pThis = reinterpret_cast<MessageProvider*>(::GetWindowLongPtr(hwnd, GWLP_USERDATA)))
- try
- {
- pThis->processMessage(uMsg, wParam, lParam); //not supposed to throw!
- }
- catch (...) { assert(false); }
-
- return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
-}
-
-
-MessageProvider::MessageProvider() :
- hMainModule(::GetModuleHandle(nullptr)), //get program's module handle
- windowHandle(nullptr)
-{
- if (!hMainModule)
- throwFileError(_("Unable to register to receive system messages."), L"GetModuleHandle", getLastError());
-
- //register the main window class
- WNDCLASS wc = {};
- wc.lpfnWndProc = topWndProc;
- wc.hInstance = hMainModule;
- wc.lpszClassName = dummyClassName;
-
- if (::RegisterClass(&wc) == 0)
- throwFileError(_("Unable to register to receive system messages."), L"RegisterClass", getLastError());
-
- ScopeGuard guardConstructor = zen::makeGuard([&] { this->~MessageProvider(); });
-
- //create dummy-window
- windowHandle = ::CreateWindow(dummyClassName, //_In_opt_ LPCTSTR lpClassName,
- nullptr, //_In_opt_ LPCTSTR lpWindowName,
- 0, //_In_ DWORD dwStyle,
- 0, 0, 0, 0, //_In_ int x, y, nWidth, nHeight,
- nullptr, //_In_opt_ HWND hWndParent; we need a toplevel window to receive device arrival events, not a message-window (HWND_MESSAGE)!
- nullptr, //_In_opt_ HMENU hMenu,
- hMainModule, //_In_opt_ HINSTANCE hInstance,
- nullptr); //_In_opt_ LPVOID lpParam
- if (!windowHandle)
- throwFileError(_("Unable to register to receive system messages."), L"CreateWindow", getLastError());
-
- //store this-pointer for topWndProc() to use: do this AFTER CreateWindow() to avoid processing messages while this constructor is running!!!
- //unlike: http://blogs.msdn.com/b/oldnewthing/archive/2014/02/03/10496248.aspx
- ::SetLastError(ERROR_SUCCESS); //[!] required for proper error handling, see MSDN, SetWindowLongPtr
- 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());
-
- guardConstructor.dismiss();
-}
-
-
-MessageProvider::~MessageProvider()
-{
- //clean-up in reverse order
- if (windowHandle)
- ::DestroyWindow(windowHandle);
- ::UnregisterClass(dummyClassName, //LPCTSTR lpClassName OR ATOM in low-order word!
- hMainModule); //HINSTANCE hInstance
-}
-
-
-void MessageProvider::processMessage(UINT message, WPARAM wParam, LPARAM lParam)
-{
- for (Listener* ls : listener)
- ls->onMessage(message, wParam, lParam);
-}
-
-//####################################################################################################
-
-class NotifyRequestDeviceRemoval::Pimpl : private MessageProvider::Listener
-{
-public:
- Pimpl(NotifyRequestDeviceRemoval& parent, HANDLE hDir) : //throw FileError
- parent_(parent)
- {
- MessageProvider::instance().registerListener(*this); //throw FileError
-
- ScopeGuard guardProvider = makeGuard([&] { MessageProvider::instance().unregisterListener(*this); });
-
- //register handles to receive notifications
- DEV_BROADCAST_HANDLE filter = {};
- filter.dbch_size = sizeof(filter);
- filter.dbch_devicetype = DBT_DEVTYP_HANDLE;
- filter.dbch_handle = hDir;
-
- hNotification = ::RegisterDeviceNotification(MessageProvider::instance().getWnd(), //__in HANDLE hRecipient,
- &filter, //__in LPVOID NotificationFilter,
- DEVICE_NOTIFY_WINDOW_HANDLE); //__in DWORD Flags
- if (!hNotification)
- {
- const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls!
- if (lastError != ERROR_CALL_NOT_IMPLEMENTED && //fail on SAMBA share: this shouldn't be a showstopper!
- lastError != ERROR_SERVICE_SPECIFIC_ERROR && //neither should be fail for "Pogoplug" mapped network drives
- lastError != ERROR_INVALID_DATA) //this seems to happen for a NetDrive-mapped FTP server
- throwFileError(_("Unable to register to receive system messages."), L"RegisterDeviceNotification", lastError);
- }
-
- guardProvider.dismiss();
- }
-
- ~Pimpl()
- {
- if (hNotification)
- ::UnregisterDeviceNotification(hNotification);
- MessageProvider::instance().unregisterListener(*this);
- }
-
-private:
- Pimpl (const Pimpl&) = delete;
- Pimpl& operator=(const Pimpl&) = delete;
-
- void onMessage(UINT message, WPARAM wParam, LPARAM lParam) override //throw()!
- {
- //DBT_DEVICEQUERYREMOVE example: http://msdn.microsoft.com/en-us/library/aa363427(v=VS.85).aspx
- if (message == WM_DEVICECHANGE)
- if (wParam == DBT_DEVICEQUERYREMOVE ||
- wParam == DBT_DEVICEQUERYREMOVEFAILED ||
- wParam == DBT_DEVICEREMOVECOMPLETE)
- {
- PDEV_BROADCAST_HDR header = reinterpret_cast<PDEV_BROADCAST_HDR>(lParam);
- if (header->dbch_devicetype == DBT_DEVTYP_HANDLE)
- {
- PDEV_BROADCAST_HANDLE body = reinterpret_cast<PDEV_BROADCAST_HANDLE>(lParam);
-
- if (body->dbch_hdevnotify == hNotification) //is it for the notification we registered?
- switch (wParam)
- {
- case DBT_DEVICEQUERYREMOVE:
- parent_.onRequestRemoval(body->dbch_handle);
- break;
- case DBT_DEVICEQUERYREMOVEFAILED:
- parent_.onRemovalFinished(body->dbch_handle, false);
- break;
- case DBT_DEVICEREMOVECOMPLETE:
- parent_.onRemovalFinished(body->dbch_handle, true);
- break;
- }
- }
- }
- }
-
- NotifyRequestDeviceRemoval& parent_;
- HDEVNOTIFY hNotification;
-};
-
-//####################################################################################################
-
-NotifyRequestDeviceRemoval::NotifyRequestDeviceRemoval(HANDLE hDir) : pimpl(zen::make_unique<Pimpl>(*this, hDir)) {}
-
-
-NotifyRequestDeviceRemoval::~NotifyRequestDeviceRemoval() {} //make sure ~unique_ptr() works with complete type
diff --git a/zen/notify_removal.h b/zen/notify_removal.h
deleted file mode 100644
index 08d75818..00000000
--- a/zen/notify_removal.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// **************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
-// **************************************************************************
-
-#ifndef NOTIFY_H_INCLUDED_257804267562
-#define NOTIFY_H_INCLUDED_257804267562
-
-#include <memory>
-#include "win.h" //includes "windows.h"
-#include "file_error.h"
-
-//handle (user-) request for device removal via template method pattern
-//evaluate directly after processing window messages
-class NotifyRequestDeviceRemoval
-{
-public:
- NotifyRequestDeviceRemoval(HANDLE hDir); //throw FileError
- virtual ~NotifyRequestDeviceRemoval();
-
-private:
- virtual void onRequestRemoval(HANDLE hnd) = 0; //throw()!
- //NOTE: onRemovalFinished is NOT guaranteed to execute after onRequestRemoval()! but most likely will
- virtual void onRemovalFinished(HANDLE hnd, bool successful) = 0; //throw()!
-
- NotifyRequestDeviceRemoval (NotifyRequestDeviceRemoval&) = delete;
- NotifyRequestDeviceRemoval& operator=(NotifyRequestDeviceRemoval&) = delete;
-
- class Pimpl;
- std::unique_ptr<Pimpl> pimpl;
-};
-
-#endif //NOTIFY_H_INCLUDED_257804267562
diff --git a/zen/perf.h b/zen/perf.h
index 53519274..f43bc00f 100644
--- a/zen/perf.h
+++ b/zen/perf.h
@@ -30,41 +30,82 @@ public:
ZEN_DEPRECATE
PerfTimer() : //throw TimerError
- ticksPerSec_(ticksPerSec()), startTime(getTicks()), resultShown(false)
+ ticksPerSec_(ticksPerSec()),
+ resultShown(false),
+ startTime(getTicksNow()),
+ paused(false),
+ elapsedUntilPause(0)
{
//std::clock() - "counts CPU time in Linux GCC and wall time in VC++" - WTF!???
-
- if (ticksPerSec_ == 0 || !startTime.isValid())
+ if (ticksPerSec_ == 0)
throw TimerError();
}
- ~PerfTimer() { if (!resultShown) showResult(); }
+ ~PerfTimer() { if (!resultShown) try { showResult(); } catch (TimerError&){} }
+
+ void pause()
+ {
+ if (!paused)
+ {
+ paused = true;
+ elapsedUntilPause += dist(startTime, getTicksNow());
+ }
+ }
+
+ void resume()
+ {
+ if (paused)
+ {
+ paused = false;
+ startTime = getTicksNow();
+ }
+ }
+
+ void restart()
+ {
+ startTime = getTicksNow();
+ paused = false;
+ elapsedUntilPause = 0;
+ }
+
+ int64_t timeMs() const
+ {
+ int64_t ticksTotal = elapsedUntilPause;
+ if (!paused)
+ ticksTotal += dist(startTime, getTicksNow());
+ return 1000 * ticksTotal / ticksPerSec_;
+ }
void showResult()
{
- const TickVal now = getTicks();
- if (!now.isValid())
- throw TimerError();
+ const bool wasRunning = !paused;
+ if (wasRunning) pause(); //don't include call to MessageBox()!
+ ZEN_ON_SCOPE_EXIT(if (wasRunning) resume());
- const std::int64_t delta = 1000 * dist(startTime, now) / ticksPerSec_;
#ifdef ZEN_WIN
std::wostringstream ss;
- ss << delta << L" ms";
+ ss << timeMs() << L" ms";
::MessageBox(nullptr, ss.str().c_str(), L"Timer", MB_OK);
#else
- std::clog << "Perf: duration: " << delta << " ms\n";
+ std::clog << "Perf: duration: " << timeMs() << " ms\n";
#endif
resultShown = true;
+ }
- startTime = getTicks(); //don't include call to MessageBox()!
- if (!startTime.isValid())
+private:
+ TickVal getTicksNow() const
+ {
+ const TickVal now = getTicks();
+ if (!now.isValid())
throw TimerError();
+ return now;
}
-private:
const std::int64_t ticksPerSec_;
- TickVal startTime;
bool resultShown;
+ TickVal startTime;
+ bool paused;
+ int64_t elapsedUntilPause;
};
}
diff --git a/zen/recycler.cpp b/zen/recycler.cpp
index 3b5ac421..ed6669ef 100644
--- a/zen/recycler.cpp
+++ b/zen/recycler.cpp
@@ -75,10 +75,9 @@ void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::func
{
if (itempaths.empty())
return;
- //::SetFileAttributes(applyLongPathPrefix(itempath).c_str(), FILE_ATTRIBUTE_NORMAL);
//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!
+ //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!
if (vistaOrLater()) //new recycle bin usage: available since Vista
{
@@ -95,20 +94,28 @@ void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::func
for (auto it = itempaths.begin(); it != itempaths.end(); ++it) //CAUTION: do not create temporary strings here!!
cNames.push_back(it->c_str());
+
+
CallbackData cbd(notifyDeletionStatus);
if (!moveToRecycleBin(&cNames[0], cNames.size(), onRecyclerCallback, &cbd))
{
if (cbd.exception)
std::rethrow_exception(cbd.exception);
- std::wstring itempathFmt = fmtFileName(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
+ if (cNames.size() == 1)
+ throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[0])), getLastErrorMessage());
- throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt), getLastErrorMessage()); //already includes details about locking errors!
+ //batch recycling failed: retry one-by-one to get a better error message; see FileOperation.dll
+ for (size_t i = 0; i < cNames.size(); ++i)
+ {
+ if (notifyDeletionStatus) notifyDeletionStatus(itempaths[i]);
+
+ if (!moveToRecycleBin(&cNames[i], 1, nullptr, nullptr))
+ throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[i])), getLastErrorMessage()); //already includes details about locking errors!
+ }
}
}
- else //regular recycle bin usage: available since XP
+ else //regular recycle bin usage: available since XP: 1. bad error reporting 2. early failure
{
Zstring itempathsDoubleNull;
for (const Zstring& itempath : itempaths)
@@ -122,7 +129,7 @@ void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::func
fileOp.wFunc = FO_DELETE;
fileOp.pFrom = itempathsDoubleNull.c_str();
fileOp.pTo = nullptr;
- fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI;
+ fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
fileOp.fAnyOperationsAborted = false;
fileOp.hNameMappings = nullptr;
fileOp.lpszProgressTitle = nullptr;
@@ -130,7 +137,10 @@ void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::func
//"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)
{
- throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[0]))); //probably not the correct file name for file list larger than 1!
+ std::wstring itempathFmt = fmtFileName(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));
}
}
}
@@ -143,7 +153,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
- recycleOrDelete({ itempath }, 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
@@ -157,7 +167,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError
const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempath));
if (!error)
- throw FileError(errorMsg, L"Unknown error."); //user should never see this
+ 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)
diff --git a/zen/serialize.h b/zen/serialize.h
index 54baf75a..4af12af1 100644
--- a/zen/serialize.h
+++ b/zen/serialize.h
@@ -25,12 +25,13 @@ binary container for data storage: must support "basic" std::vector interface (e
//binary container reference implementations
typedef Zbase<char> Utf8String; //ref-counted + COW text stream + guaranteed performance: exponential growth
-class BinaryStream; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?)
+class ByteArray; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?)
-class BinaryStream //essentially a std::vector<char> with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite
+
+class ByteArray //essentially a std::vector<char> with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite
{
public:
- BinaryStream() : buffer(std::make_shared<std::vector<char>>()) {}
+ ByteArray() : buffer(std::make_shared<std::vector<char>>()) {}
typedef std::vector<char>::value_type value_type;
typedef std::vector<char>::iterator iterator;
@@ -46,7 +47,7 @@ public:
size_t size() const { return buffer->size(); }
bool empty() const { return buffer->empty(); }
- inline friend bool operator==(const BinaryStream& lhs, const BinaryStream& rhs) { return *lhs.buffer == *rhs.buffer; }
+ inline friend bool operator==(const ByteArray& lhs, const ByteArray& rhs) { return *lhs.buffer == *rhs.buffer; }
private:
std::shared_ptr<std::vector<char>> buffer; //always bound!
@@ -65,7 +66,7 @@ template <class BinContainer> BinContainer loadBinStream(const Zstring& filepath
-----------------------------
struct BinInputStream
{
- const void* requestRead(size_t len); //expect external read of len bytes
+ size_t read(void* data, size_t len); //return "len" bytes unless end of stream!
};
------------------------------
@@ -73,58 +74,62 @@ struct BinInputStream
------------------------------
struct BinOutputStream
{
- void* requestWrite(size_t len); //expect external write of len bytes
+ void write(const void* data, size_t len);
};
*/
-//binary input/output stream reference implementation
-class UnexpectedEndOfStreamError {};
+//binary input/output stream reference implementations:
-struct BinStreamIn //throw UnexpectedEndOfStreamError
+template <class BinContainer>
+struct MemoryStreamIn
{
- BinStreamIn(const BinaryStream& cont) : buffer(cont), pos(0) {} //this better be cheap!
+ MemoryStreamIn(const BinContainer& cont) : buffer(cont), pos(0) {} //this better be cheap!
- const void* requestRead(size_t len) //throw UnexpectedEndOfStreamError
+ size_t read(void* data, size_t len) //return "len" bytes unless end of stream!
{
- if (len == 0) return nullptr; //don't allow for possibility to access empty buffer
- if (pos + len > buffer.size())
- throw UnexpectedEndOfStreamError();
- size_t oldPos = pos;
- pos += len;
- return &*buffer.begin() + oldPos;
+ static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes
+ const size_t bytesRead = std::min(len, buffer.size() - pos);
+ auto itFirst = buffer.begin() + pos;
+ std::copy(itFirst, itFirst + bytesRead, static_cast<char*>(data));
+ pos += bytesRead;
+ return bytesRead;
}
private:
- BinaryStream buffer;
+ const BinContainer buffer;
size_t pos;
};
-struct BinStreamOut
+template <class BinContainer>
+struct MemoryStreamOut
{
- void* requestWrite(size_t len)
+ void write(const void* data, size_t len)
{
- if (len == 0) return nullptr; //don't allow for possibility to access empty buffer
+ static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes
const size_t oldSize = buffer.size();
buffer.resize(oldSize + len);
- return &*buffer.begin() + oldSize;
+ std::copy(static_cast<const char*>(data), static_cast<const char*>(data) + len, buffer.begin() + oldSize);
}
- BinaryStream get() { return buffer; }
+ const BinContainer& ref() const { return buffer; }
private:
- BinaryStream buffer;
+ BinContainer buffer;
};
//----------------------------------------------------------------------
//functions based on binary stream abstraction
+template <class BinInputStream, class BinOutputStream>
+void copyStream(BinInputStream& streamIn, BinOutputStream& streamOut, const std::function<void(std::int64_t bytesDelta)>& onNotifyCopyStatus); //optional
+
template <class N, class BinOutputStream> void writeNumber (BinOutputStream& stream, const N& num); //
template <class C, class BinOutputStream> void writeContainer(BinOutputStream& stream, const C& str); //throw ()
template < class BinOutputStream> void writeArray (BinOutputStream& stream, const void* data, size_t len); //
//----------------------------------------------------------------------
-
-template <class N, class BinInputStream> N readNumber (BinInputStream& stream); //
-template <class C, class BinInputStream> C readContainer(BinInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data)
+class UnexpectedEndOfStreamError {};
+template <class N, class BinInputStream> N readNumber (BinInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data)
+template <class C, class BinInputStream> C readContainer(BinInputStream& stream); //
template < class BinInputStream> void readArray (BinInputStream& stream, void* data, size_t len); //
@@ -135,66 +140,54 @@ template < class BinInputStream> void readArray (BinInputStream& stre
//-----------------------implementation-------------------------------
-template <class BinContainer> inline
-void saveBinStream(const Zstring& filepath, //throw FileError
- const BinContainer& cont,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateStatus) //optional
+template <class BinInputStream, class BinOutputStream> inline
+void copyStream(BinInputStream& streamIn, BinOutputStream& streamOut, size_t blockSize,
+ const std::function<void(std::int64_t bytesDelta)>& onNotifyCopyStatus) //optional
{
- static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes (until further)
-
- FileOutput fileOut(filepath, zen::FileOutput::ACC_OVERWRITE); //throw FileError
- if (!cont.empty())
+ assert(blockSize > 0);
+ std::vector<char> buffer(blockSize);
+ for (;;)
{
- const size_t blockSize = 128 * 1024;
- auto bytePtr = &*cont.begin();
- size_t bytesLeft = cont.size();
+ const size_t bytesRead = streamIn.read(&buffer[0], buffer.size());
+ streamOut.write(&buffer[0], bytesRead);
- while (bytesLeft > blockSize)
- {
- fileOut.write(bytePtr, blockSize); //throw FileError
- bytePtr += blockSize;
- bytesLeft -= blockSize;
- if (onUpdateStatus) onUpdateStatus(blockSize);
- }
+ if (onNotifyCopyStatus)
+ onNotifyCopyStatus(bytesRead); //throw X!
- fileOut.write(bytePtr, bytesLeft); //throw FileError
- if (onUpdateStatus) onUpdateStatus(bytesLeft);
+ if (bytesRead != buffer.size()) //end of file
+ break;
}
}
template <class BinContainer> inline
-BinContainer loadBinStream(const Zstring& filepath, //throw FileError
- const std::function<void(std::int64_t bytesDelta)>& onUpdateStatus) //optional
+void saveBinStream(const Zstring& filepath, //throw FileError
+ const BinContainer& cont,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateStatus) //optional
{
- static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes (until further)
-
- FileInput fileIn(filepath); //throw FileError
-
- BinContainer contOut;
- const size_t blockSize = 128 * 1024;
- do
- {
- contOut.resize(contOut.size() + blockSize); //container better implement exponential growth!
-
- const size_t bytesRead = fileIn.read(&*contOut.begin() + contOut.size() - blockSize, blockSize); //throw FileError
- if (bytesRead < blockSize)
- contOut.resize(contOut.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics
+ MemoryStreamIn<BinContainer> streamIn(cont);
+ FileOutput streamOut(filepath, zen::FileOutput::ACC_OVERWRITE); //throw FileError, (ErrorTargetExisting)
+ if (onUpdateStatus) onUpdateStatus(0); //throw X!
+ copyStream(streamIn, streamOut, streamOut.optimalBlockSize(), onUpdateStatus); //throw FileError
+}
- if (onUpdateStatus) onUpdateStatus(bytesRead);
- }
- while (!fileIn.eof());
- return contOut;
+template <class BinContainer> inline
+BinContainer loadBinStream(const Zstring& filepath, //throw FileError
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateStatus) //optional
+{
+ FileInput streamIn(filepath); //throw FileError, ErrorFileLocked
+ if (onUpdateStatus) onUpdateStatus(0); //throw X!
+ MemoryStreamOut<BinContainer> streamOut;
+ copyStream(streamIn, streamOut, streamIn.optimalBlockSize(), onUpdateStatus); //throw FileError
+ return streamOut.ref();
}
template <class BinOutputStream> inline
void writeArray(BinOutputStream& stream, const void* data, size_t len)
{
- std::copy(static_cast<const char*>(data),
- static_cast<const char*>(data) + len,
- static_cast< char*>(stream.requestWrite(len)));
+ stream.write(data, len);
}
@@ -219,9 +212,9 @@ void writeContainer(BinOutputStream& stream, const C& cont) //don't even conside
template <class BinInputStream> inline
void readArray(BinInputStream& stream, void* data, size_t len) //throw UnexpectedEndOfStreamError
{
- //expect external write of len bytes:
- const char* const src = static_cast<const char*>(stream.requestRead(len)); //throw UnexpectedEndOfStreamError
- std::copy(src, src + len, static_cast<char*>(data));
+ const size_t bytesRead = stream.read(data, len);
+ if (bytesRead < len)
+ throw UnexpectedEndOfStreamError();
}
diff --git a/zen/string_base.h b/zen/string_base.h
index b1d7102e..be3b532e 100644
--- a/zen/string_base.h
+++ b/zen/string_base.h
@@ -287,8 +287,8 @@ template <class Char, template <class, class> class SP, class AP> inline Zbase<C
template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, Char rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; }
//don't use unified first argument but save one move-construction in the r-value case instead!
-template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Zbase<Char, SP, AP>& rhs) { return std::move(lhs += rhs); } //is the move really needed?
-template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-vlaue in the function body...
+template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Zbase<Char, SP, AP>& rhs) { return std::move(lhs += rhs); } //the move *is* needed!!!
+template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-value parameter...
template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, Char rhs) { return std::move(lhs += rhs); } //and not a local variable => no copy elision
template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+( Char lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; }
diff --git a/zen/string_tools.h b/zen/string_tools.h
index 35a07b64..c8591522 100644
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -35,7 +35,8 @@ template <class S, class T> S afterFirst (const S& str, const T& term); //return
template <class S, class T> S beforeFirst(const S& str, const T& term); //returns the whole string if term not found
template <class S, class T> std::vector<S> split(const S& str, const T& delimiter);
-template <class S> void trim(S& str, bool fromLeft = true, bool fromRight = true);
+template <class S> void trim ( S& str, bool fromLeft = true, bool fromRight = true);
+template <class S> S trimCpy(const S& str, bool fromLeft = true, bool fromRight = true);
template <class S, class T, class U> void replace ( S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true);
template <class S, class T, class U> S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true);
@@ -331,9 +332,9 @@ void trim(S& str, bool fromLeft, bool fromRight)
assert(fromLeft || fromRight);
typedef typename GetCharType<S>::Type CharType; //don't use value_type! (wxString, Glib::ustring)
- const CharType* const oldBegin = str.c_str();
- const CharType* newBegin = oldBegin;
- const CharType* newEnd = oldBegin + str.length();
+ const CharType* const oldBegin = strBegin(str);
+ const CharType* newBegin = oldBegin;
+ const CharType* newEnd = oldBegin + strLength(str);
if (fromRight)
while (newBegin != newEnd && isWhiteSpace(newEnd[-1]))
@@ -350,6 +351,16 @@ void trim(S& str, bool fromLeft, bool fromRight)
}
+template <class S> inline
+S trimCpy(const S& str, bool fromLeft, bool fromRight)
+{
+ //implementing trimCpy() in terms of trim(), instead of the other way round, avoids memory allocations when trimming from right!
+ S tmp = str;
+ trim(tmp, fromLeft, fromRight);
+ return tmp;
+}
+
+
namespace implementation
{
template <class S, class T>
diff --git a/zen/string_traits.h b/zen/string_traits.h
index 8bc55a6a..8c4775f4 100644
--- a/zen/string_traits.h
+++ b/zen/string_traits.h
@@ -176,8 +176,8 @@ size_t cStringLength(const C* str) //naive implementation seems somewhat faster
}
-template <class S> inline
-const typename GetCharType<S>::Type* strBegin(const S& str, typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type* = nullptr) //SFINAE: T must be a "string"
+template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline
+const typename GetCharType<S>::Type* strBegin(const S& str) //SFINAE: T must be a "string"
{
return str.c_str();
}
@@ -190,8 +190,8 @@ inline const char* strBegin(const StringRef<char >& ref) { return ref.data(
inline const wchar_t* strBegin(const StringRef<wchar_t>& ref) { return ref.data(); }
-template <class S> inline
-size_t strLength(const S& str, typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type* = nullptr) //SFINAE: T must be a "string"
+template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline
+size_t strLength(const S& str) //SFINAE: T must be a "string"
{
return str.length();
}
diff --git a/zen/symlink_target.h b/zen/symlink_target.h
index 9239385a..c895d9a0 100644
--- a/zen/symlink_target.h
+++ b/zen/symlink_target.h
@@ -165,13 +165,13 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError
const HANDLE hFile = ::CreateFile(applyLongPathPrefix(linkPath).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,
- //needed to open a directory:
- FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
+ 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,
+ //needed to open a directory:
+ FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes,
+ nullptr); //_In_opt_ HANDLE hTemplateFile
if (hFile == INVALID_HANDLE_VALUE)
throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), L"CreateFile", getLastError());
ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
diff --git a/zen/thread.h b/zen/thread.h
index d860abd0..c9b4c76f 100644
--- a/zen/thread.h
+++ b/zen/thread.h
@@ -101,7 +101,7 @@ auto async(Function fun) -> boost::unique_future<decltype(fun())>
#endif
auto fut = pt.get_future();
boost::thread(std::move(pt)).detach(); //we have to explicitly detach since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!!
- return std::move(fut); //compiler error without "move", why needed???
+ return fut;
}
diff --git a/zen/time.h b/zen/time.h
index 996593c8..9d821a1f 100644
--- a/zen/time.h
+++ b/zen/time.h
@@ -218,7 +218,7 @@ String formatTime(const String2& format, const TimeComp& comp, UserDefinedFormat
std::mktime(&ctc); // unfortunately std::strftime() needs all elements of "struct tm" filled, e.g. tm_wday, tm_yday
//note: although std::mktime() explicitly expects "local time", calculating weekday and day of year *should* be time-zone and DST independent
- CharType buffer[256] = {};
+ CharType buffer[256] = {};
const size_t charsWritten = strftimeWrap(buffer, 256, strBegin(format), &ctc);
return String(buffer, charsWritten);
}
@@ -237,11 +237,11 @@ TimeComp localTime(time_t utc)
{
struct ::tm lt = {};
- //use thread-safe variants of localtime()!
+ //use thread-safe variants of localtime()!
#ifdef ZEN_WIN
if (::localtime_s(&lt, &utc) != 0)
#else
- if (::localtime_r(&utc, &lt) == nullptr)
+ if (::localtime_r(&utc, &lt) == nullptr)
#endif
return TimeComp();
diff --git a/zen/type_traits.h b/zen/type_traits.h
index 082baea9..e03085d9 100644
--- a/zen/type_traits.h
+++ b/zen/type_traits.h
@@ -38,10 +38,10 @@ struct ResultType
//Herb Sutter's signedness conversion helpers: http://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/
template<class T> inline
-typename std::make_signed<T>::type makeSigned(T t) { return typename std::make_signed<T>::type(t); }
+typename std::make_signed<T>::type makeSigned(T t) { return static_cast<typename std::make_signed<T>::type>(t); }
template<class T> inline
-typename std::make_unsigned<T>::type makeUnsigned(T t) { return typename std::make_unsigned<T>::type(t); }
+typename std::make_unsigned<T>::type makeUnsigned(T t) { return static_cast<typename std::make_unsigned<T>::type>(t); }
//################# Built-in Types ########################
//Example: "IsSignedInt<int>::value" evaluates to "true"
diff --git a/zen/win_ver.h b/zen/win_ver.h
index 611a27c0..2e8f1ee7 100644
--- a/zen/win_ver.h
+++ b/zen/win_ver.h
@@ -40,7 +40,7 @@ const OsVersion osVersionWin2000 (5, 0);
/*
NOTE: there are two basic APIs to check Windows version: (empiric study following)
GetVersionEx -> reports version considering compatibility mode (and compatibility setting in app manifest since Windows 8.1)
- VerifyVersionInfo -> always reports *real* Windows Version
+ VerifyVersionInfo -> always reports *real* Windows Version
*) Win10 Technical preview caveat: VerifyVersionInfo returns 6.3 unless manifest entry is added!!!
*/
@@ -118,11 +118,17 @@ bool runningWOW64() //test if process is running under WOW64: http://msdn.micros
}
+template <bool is64BitBuild> inline
+bool running64BitWindowsImpl() { return true; }
+
+template <> inline
+bool running64BitWindowsImpl<false>() { return runningWOW64(); }
+
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 give a compile-time result in the first case?
+ return running64BitWindowsImpl<is64BitBuild>();
}
}
diff --git a/zen/xml_io.cpp b/zen/xml_io.cpp
index a070b526..a1bf05d0 100644
--- a/zen/xml_io.cpp
+++ b/zen/xml_io.cpp
@@ -16,36 +16,26 @@ XmlDoc zen::loadXmlDocument(const Zstring& filepath) //throw FileError
{
//can't simply use zen::loadBinStream() due to the short-circuit xml-validation below!
- std::string stream;
-
- FileInput inputFile(filepath); //throw FileError
+ FileInput fileStreamIn(filepath); //throw FileError
+ MemoryStreamOut<std::string> memStreamOut;
{
//quick test whether input is an XML: avoid loading large binary files up front!
const std::string xmlBegin = "<?xml version=";
- stream.resize(strLength(BYTE_ORDER_MARK_UTF8) + xmlBegin.size());
+ std::vector<char> buf(xmlBegin.size() + strLength(BYTE_ORDER_MARK_UTF8));
- const size_t bytesRead = inputFile.read(&stream[0], stream.size()); //throw FileError
- stream.resize(bytesRead);
+ const size_t bytesRead = fileStreamIn.read(&buf[0], buf.size());
+ memStreamOut.write(&buf[0], bytesRead);
- if (!startsWith(stream, xmlBegin) &&
- !startsWith(stream, BYTE_ORDER_MARK_UTF8 + xmlBegin)) //allow BOM!
+ if (!startsWith(memStreamOut.ref(), xmlBegin) &&
+ !startsWith(memStreamOut.ref(), BYTE_ORDER_MARK_UTF8 + xmlBegin)) //allow BOM!
throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtFileName(filepath)));
}
- const size_t blockSize = 128 * 1024;
- do
- {
- stream.resize(stream.size() + blockSize);
-
- const size_t bytesRead = inputFile.read(&*stream.begin() + stream.size() - blockSize, blockSize); //throw FileError
- if (bytesRead < blockSize)
- stream.resize(stream.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics
- }
- while (!inputFile.eof());
+ copyStream(fileStreamIn, memStreamOut, fileStreamIn.optimalBlockSize(), nullptr); //throw FileError
try
{
- return parse(stream); //throw XmlParsingError
+ return parse(memStreamOut.ref()); //throw XmlParsingError
}
catch (const XmlParsingError& e)
{
diff --git a/zen/zstring.cpp b/zen/zstring.cpp
index f803f160..73ef3ee9 100644
--- a/zen/zstring.cpp
+++ b/zen/zstring.cpp
@@ -17,7 +17,7 @@
#endif
#ifndef NDEBUG
- #include <mutex>
+ #include "thread.h"
#include <iostream>
#endif
@@ -39,14 +39,14 @@ public:
void insert(const void* ptr, size_t size)
{
- std::lock_guard<std::mutex> dummy(lockActStrings);
+ boost::lock_guard<boost::mutex> dummy(lockActStrings);
if (!activeStrings.emplace(ptr, size).second)
reportProblem("Serious Error: New memory points into occupied space: " + rawMemToString(ptr, size));
}
void remove(const void* ptr)
{
- std::lock_guard<std::mutex> dummy(lockActStrings);
+ boost::lock_guard<boost::mutex> dummy(lockActStrings);
if (activeStrings.erase(ptr) != 1)
reportProblem("Serious Error: No memory available for deallocation at this location!");
}
@@ -92,15 +92,15 @@ private:
#else
std::cerr << message;
#endif
- throw std::logic_error("Memory leak! " + message + "\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+ throw std::logic_error("Memory leak! " + message + "\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
}
- std::mutex lockActStrings;
+ boost::mutex lockActStrings;
std::unordered_map<const void*, size_t> activeStrings;
};
//caveat: function scope static initialization is not thread-safe in VS 2010!
-auto& dummy = LeakChecker::get();
+auto& dummy = LeakChecker::get(); //still not sufficient if multiple threads access during static init!!!
}
void z_impl::leakCheckerInsert(const void* ptr, size_t size) { LeakChecker::get().insert(ptr, size); }
@@ -143,8 +143,8 @@ const LCID ZSTRING_INVARIANT_LOCALE = zen::winXpOrLater() ?
typedef int (WINAPI* CompareStringOrdinalFunc)(LPCWSTR lpString1, int cchCount1,
LPCWSTR lpString2, int cchCount2, BOOL bIgnoreCase);
const SysDllFun<CompareStringOrdinalFunc> compareStringOrdinal = SysDllFun<CompareStringOrdinalFunc>(L"kernel32.dll", "CompareStringOrdinal");
+//watch for dependencies in global namespace!!!
//caveat: function scope static initialization is not thread-safe in VS 2010!
-//No global dependencies => no static initialization order problem in global namespace!
}
@@ -158,7 +158,7 @@ int cmpFileName(const Zstring& lhs, const Zstring& rhs)
static_cast<int>(rhs.size()), //__in int cchCount2,
true); //__in BOOL bIgnoreCase
if (rv <= 0)
- throw std::runtime_error("Error comparing strings (CompareStringOrdinal). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+ throw std::runtime_error("Error comparing strings (CompareStringOrdinal). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
else
return rv - 2; //convert to C-style string compare result
}
@@ -184,7 +184,7 @@ int cmpFileName(const Zstring& lhs, const Zstring& rhs)
static_cast<int>(minSize), //__in int cchSrc,
strOut, //__out LPTSTR lpDestStr,
static_cast<int>(minSize)) == 0) //__in int cchDest
- throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+ throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
};
auto eval = [&](wchar_t* bufL, wchar_t* bufR)
@@ -231,7 +231,7 @@ Zstring makeUpperCopy(const Zstring& str)
len, //__in int cchSrc,
&*output.begin(), //__out LPTSTR lpDestStr,
len) == 0) //__in int cchDest
- throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+ throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
return output;
}
@@ -240,7 +240,8 @@ Zstring makeUpperCopy(const Zstring& str)
#elif defined ZEN_MAC
int cmpFileName(const Zstring& lhs, const Zstring& rhs)
{
- return ::strcasecmp(lhs.c_str(), rhs.c_str()); //locale-dependent!
+ const int rv = ::strcasecmp(lhs.c_str(), rhs.c_str()); //locale-dependent!
+ return rv;
}
diff --git a/zen/zstring.h b/zen/zstring.h
index 0610a27f..7dcfbb69 100644
--- a/zen/zstring.h
+++ b/zen/zstring.h
@@ -68,12 +68,12 @@ typedef zen::Zbase<Zchar, zen::StorageRefCountThreadSafe, AllocatorFreeStoreChec
//Compare filepaths: Windows does NOT distinguish between upper/lower-case, while Linux DOES
int cmpFileName(const Zstring& lhs, const Zstring& rhs);
-struct LessFilename //case-insensitive on Windows, case-sensitive on Linux
+struct LessFilePath //case-insensitive on Windows, case-sensitive on Linux
{
bool operator()(const Zstring& lhs, const Zstring& rhs) const { return cmpFileName(lhs, rhs) < 0; }
};
-struct EqualFilename //case-insensitive on Windows, case-sensitive on Linux
+struct EqualFilePath //case-insensitive on Windows, case-sensitive on Linux
{
bool operator()(const Zstring& lhs, const Zstring& rhs) const { return cmpFileName(lhs, rhs) == 0; }
};
@@ -85,10 +85,35 @@ struct EqualFilename //case-insensitive on Windows, case-sensitive on Linux
inline
Zstring appendSeparator(Zstring path) //support rvalue references!
{
- return zen::endsWith(path, FILE_NAME_SEPARATOR) ? path : (path += FILE_NAME_SEPARATOR);
+ return zen::endsWith(path, FILE_NAME_SEPARATOR) ? path : (path += FILE_NAME_SEPARATOR); //returning a by-value parameter implicitly converts to r-value!
}
+inline
+Zstring getFileExtension(const Zstring& filePath)
+{
+ const Zstring shortName = afterLast(filePath, FILE_NAME_SEPARATOR); //returns the whole string if term not found
+
+ return contains(shortName, Zchar('.')) ?
+ afterLast(filePath, Zchar('.')) :
+ Zstring();
+}
+
+
+inline
+bool pathStartsWith(const Zstring& str, const Zstring& prefix)
+{
+ return str.size() >= prefix.size() &&
+ EqualFilePath()(Zstring(str.begin(), str.begin() + prefix.size()), prefix);
+}
+
+
+inline
+bool pathEndsWith(const Zstring& str, const Zstring& postfix)
+{
+ return str.size() >= postfix.size() &&
+ EqualFilePath()(Zstring(str.end() - postfix.size(), str.end()), postfix);
+}
bgstack15