summaryrefslogtreecommitdiff
path: root/FreeFileSync/Source/RealTimeSync/monitor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'FreeFileSync/Source/RealTimeSync/monitor.cpp')
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/monitor.cpp247
1 files changed, 117 insertions, 130 deletions
diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp
index 3b5a4321..a586ce4d 100755
--- a/FreeFileSync/Source/RealTimeSync/monitor.cpp
+++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp
@@ -5,13 +5,9 @@
// *****************************************************************************
#include "monitor.h"
-#include <ctime>
-#include <set>
#include <zen/file_access.h>
#include <zen/dir_watcher.h>
#include <zen/thread.h>
-#include <zen/basic_math.h>
-#include <wx/utils.h>
#include "../base/resolve_path.h"
//#include "../library/db_file.h" //SYNC_DB_FILE_ENDING -> complete file too much of a dependency; file ending too little to decouple into single header
//#include "../library/lock_holder.h" //LOCK_FILE_ENDING
@@ -25,10 +21,11 @@ namespace
const std::chrono::seconds FOLDER_EXISTENCE_CHECK_INTERVAL(1);
-std::vector<Zstring> getFormattedDirs(const std::vector<Zstring>& folderPathPhrases) //throw FileError
+std::set<Zstring, LessFilePath> getFormattedDirs(const std::vector<Zstring>& folderPathPhrases) //throw FileError
{
std::set<Zstring, LessFilePath> folderPaths; //make unique
- for (const Zstring& phrase : std::set<Zstring, LessFilePath>(folderPathPhrases.begin(), folderPathPhrases.end()))
+
+ for (const Zstring& phrase : folderPathPhrases)
{
//hopefully clear enough now: https://freefilesync.org/forum/viewtopic.php?t=4302
auto checkProtocol = [&](const Zstring& protoName)
@@ -44,7 +41,60 @@ std::vector<Zstring> getFormattedDirs(const std::vector<Zstring>& folderPathPhra
folderPaths.insert(fff::getResolvedFilePath(phrase));
}
- return { folderPaths.begin(), folderPaths.end() };
+ return folderPaths;
+}
+
+
+//wait until all directories become available (again) + logs in network share
+std::set<Zstring, LessFilePath> waitForMissingDirs(const std::vector<Zstring>& folderPathPhrases, //throw FileError
+ const std::function<void(const Zstring& folderPath)>& requestUiRefresh, std::chrono::milliseconds cbInterval)
+{
+ for (;;)
+ {
+ //support specifying volume by name => call getResolvedFilePath() repeatedly
+ std::set<Zstring, LessFilePath> folderPaths = getFormattedDirs(folderPathPhrases); //throw FileError
+
+ std::vector<std::pair<Zstring, std::future<bool>>> futureInfo;
+ //start all folder checks asynchronously (non-existent network path may block)
+ for (const Zstring& folderPath : folderPaths)
+ futureInfo.emplace_back(folderPath, runAsync([folderPath]
+ {
+ //2. check dir availability
+ return dirAvailable(folderPath);
+ }));
+
+ bool allAvailable = true;
+
+ for (auto& item : futureInfo)
+ {
+ const Zstring& folderPath = item.first;
+ std::future<bool>& ftDirAvailable = item.second;
+
+ for (;;)
+ {
+ while (ftDirAvailable.wait_for(cbInterval) != std::future_status::ready)
+ requestUiRefresh(folderPath); //throw X
+
+ if (ftDirAvailable.get())
+ break;
+
+ //wait until folder is available: do not needlessly poll all others again!
+ allAvailable = false;
+
+ //wait some time...
+ const auto delayUntil = std::chrono::steady_clock::now() + FOLDER_EXISTENCE_CHECK_INTERVAL;
+ for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now())
+ {
+ requestUiRefresh(folderPath); //throw X
+ std::this_thread::sleep_for(cbInterval);
+ }
+
+ ftDirAvailable = runAsync([folderPath] { return dirAvailable(folderPath); });
+ }
+ }
+ if (allAvailable) //only return when all folders were found on *first* try!
+ return folderPaths;
+ }
}
@@ -53,43 +103,32 @@ struct WaitResult
{
enum ChangeType
{
- CHANGE_DETECTED,
- CHANGE_DIR_UNAVAILABLE //not existing or can't access
+ ITEM_CHANGED,
+ FOLDER_UNAVAILABLE //1. not existing or 2. can't access
};
- WaitResult(const zen::DirWatcher::Entry& changedItem) : type(CHANGE_DETECTED), changedItem_(changedItem) {}
- WaitResult(const Zstring& folderPath) : type(CHANGE_DIR_UNAVAILABLE), folderPath_(folderPath) {}
+ explicit WaitResult(const DirWatcher::Entry& changeEntry) : type(ITEM_CHANGED), changedItem(changeEntry) {}
+ explicit WaitResult(const Zstring& folderPath) : type(FOLDER_UNAVAILABLE), missingFolderPath(folderPath) {}
ChangeType type;
- zen::DirWatcher::Entry changedItem_; //for type == CHANGE_DETECTED: file or directory
- Zstring folderPath_; //for type == CHANGE_DIR_UNAVAILABLE
+ DirWatcher::Entry changedItem; //for type == ITEM_CHANGED: file or directory
+ Zstring missingFolderPath; //for type == FOLDER_UNAVAILABLE
};
-WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw FileError
+WaitResult waitForChanges(const std::set<Zstring, LessFilePath>& folderPaths, //throw FileError
const std::function<void(bool readyForSync)>& requestUiRefresh, std::chrono::milliseconds cbInterval)
{
- const std::vector<Zstring> folderPaths = getFormattedDirs(folderPathPhrases); //throw FileError
+ assert(std::all_of(folderPaths.begin(), folderPaths.end(), [](const Zstring& folderPath) { return dirAvailable(folderPath); }));
if (folderPaths.empty()) //pathological case, but we have to check else this function will wait endlessly
throw FileError(_("A folder input field is empty.")); //should have been checked by caller!
- //detect when volumes are removed/are not available anymore
- std::vector<std::pair<Zstring, std::shared_ptr<DirWatcher>>> watches;
+ std::vector<std::pair<Zstring, std::unique_ptr<DirWatcher>>> watches;
for (const Zstring& folderPath : folderPaths)
- {
try
{
- //a non-existent network path may block, so check existence asynchronously!
- auto ftDirAvailable = runAsync([=] { return dirAvailable(folderPath); });
-
- while (ftDirAvailable.wait_for(cbInterval) != std::future_status::ready)
- if (requestUiRefresh) requestUiRefresh(false /*readyForSync*/); //throw X
-
- if (!ftDirAvailable.get()) //folder not existing or can't access
- return WaitResult(folderPath);
-
- watches.emplace_back(folderPath, std::make_shared<DirWatcher>(folderPath)); //throw FileError
+ watches.emplace_back(folderPath, std::make_unique<DirWatcher>(folderPath)); //throw FileError
}
catch (FileError&)
{
@@ -97,16 +136,14 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw
return WaitResult(folderPath);
throw;
}
- }
auto lastCheckTime = std::chrono::steady_clock::now();
for (;;)
{
- const bool checkDirNow = [&]() -> bool //checking once per sec should suffice
+ const bool checkDirNow = [&] //checking once per sec should suffice
{
const auto now = std::chrono::steady_clock::now();
-
- if (numeric::dist(now, lastCheckTime) > FOLDER_EXISTENCE_CHECK_INTERVAL) //handle potential chrono wrap-around!
+ if (now > lastCheckTime + FOLDER_EXISTENCE_CHECK_INTERVAL)
{
lastCheckTime = now;
return true;
@@ -114,13 +151,12 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw
return false;
}();
-
- for (auto it = watches.begin(); it != watches.end(); ++it)
+ for (const auto& item : watches)
{
- const Zstring& folderPath = it->first;
- DirWatcher& watcher = *(it->second);
+ const Zstring& folderPath = item.first;
+ DirWatcher& watcher = *item.second;
- //IMPORTANT CHECK: dirwatcher has problems detecting removal of top watched directories!
+ //IMPORTANT CHECK: DirWatcher has problems detecting removal of top watched directories!
if (checkDirNow)
if (!dirAvailable(folderPath)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED
return WaitResult(folderPath);
@@ -128,14 +164,12 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw
{
std::vector<DirWatcher::Entry> changedItems = watcher.getChanges([&] { requestUiRefresh(false /*readyForSync*/); /*throw X*/ },
cbInterval); //throw FileError
-
- //remove to be ignored changes
erase_if(changedItems, [](const DirWatcher::Entry& e)
{
return
- endsWith(e.filePath, Zstr(".ffs_tmp")) || //sync.8ea2.ffs_tmp
- endsWith(e.filePath, Zstr(".ffs_lock")) || //sync.ffs_lock, sync.Del.ffs_lock
- endsWith(e.filePath, Zstr(".ffs_db")); //sync.ffs_db
+ endsWith(e.itemPath, Zstr(".ffs_tmp")) || //sync.8ea2.ffs_tmp
+ endsWith(e.itemPath, Zstr(".ffs_lock")) || //sync.ffs_lock, sync.Del.ffs_lock
+ endsWith(e.itemPath, Zstr(".ffs_db")); //sync.ffs_db
//no need to ignore temporary recycle bin directory: this must be caused by a file deletion anyway
});
@@ -156,45 +190,8 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw
}
-//wait until all directories become available (again) + logs in network share
-void waitForMissingDirs(const std::vector<Zstring>& folderPathPhrases, //throw FileError
- const std::function<void(const Zstring& folderPath)>& requestUiRefresh, std::chrono::milliseconds cbInterval)
-{
- for (;;)
- {
- bool allAvailable = true;
- //support specifying volume by name => call getResolvedFilePath() repeatedly
- for (const Zstring& folderPath : getFormattedDirs(folderPathPhrases)) //throw FileError
- {
- auto ftDirAvailable = runAsync([=]
- {
- //2. check dir availability
- return dirAvailable(folderPath);
- });
- while (ftDirAvailable.wait_for(cbInterval) != std::future_status::ready)
- if (requestUiRefresh) requestUiRefresh(folderPath); //throw X
-
- if (!ftDirAvailable.get())
- {
- allAvailable = false;
- //wait some time...
- const auto delayUntil = std::chrono::steady_clock::now() + FOLDER_EXISTENCE_CHECK_INTERVAL;
- for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now())
- {
- if (requestUiRefresh) requestUiRefresh(folderPath); //throw X
- std::this_thread::sleep_for(cbInterval);
- }
- break;
- }
- }
- if (allAvailable)
- return;
- }
-}
-
-
inline
-wxString toString(DirWatcher::ActionType type)
+std::wstring getActionName(DirWatcher::ActionType type)
{
switch (type)
{
@@ -205,6 +202,7 @@ wxString toString(DirWatcher::ActionType type)
case DirWatcher::ACTION_DELETE:
return L"DELETE";
}
+ assert(false);
return L"ERROR";
}
@@ -212,71 +210,60 @@ struct ExecCommandNowException {};
}
-void rts::monitorDirectories(const std::vector<Zstring>& folderPathPhrases, size_t delay, MonitorCallback& cb, std::chrono::milliseconds cbInterval)
+void rts::monitorDirectories(const std::vector<Zstring>& folderPathPhrases, std::chrono::seconds delay,
+ const std::function<void(const Zstring& itemPath, const std::wstring& actionName)>& executeExternalCommand,
+ const std::function<void(const Zstring* missingFolderPath)>& requestUiRefresh,
+ const std::function<void(const std::wstring& msg )>& reportError,
+ std::chrono::milliseconds cbInterval)
{
+ assert(!folderPathPhrases.empty());
if (folderPathPhrases.empty())
- {
- assert(false);
return;
- }
- auto execMonitoring = [&] //throw FileError
- {
- cb.setPhase(MonitorCallback::MONITOR_PHASE_WAITING);
- waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { cb.requestUiRefresh(); }, cbInterval); //throw FileError
- cb.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE);
+ for (;;)
+ try
+ {
+ std::set<Zstring, LessFilePath> folderPaths = waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { requestUiRefresh(&folderPath); }, cbInterval); //throw FileError
- //schedule initial execution (*after* all directories have arrived, which could take some time which we don't want to include)
- time_t nextExecTime = std::time(nullptr) + delay;
+ //schedule initial execution (*after* all directories have arrived)
+ auto nextExecTime = std::chrono::steady_clock::now() + delay;
- for (;;) //loop over command invocations
- {
- DirWatcher::Entry lastChangeDetected;
- try
+ for (;;) //command executions
{
- for (;;) //loop over detected changes
+ DirWatcher::Entry lastChangeDetected;
+ try
{
- //wait for changes (and for all directories to become available)
- WaitResult res = waitForChanges(folderPathPhrases, [&](bool readyForSync) //throw FileError, ExecCommandNowException
+ for (;;) //detected changes
{
- if (readyForSync)
- if (nextExecTime <= std::time(nullptr))
+ const WaitResult res = waitForChanges(folderPaths, [&](bool readyForSync) //throw FileError, ExecCommandNowException
+ {
+ requestUiRefresh(nullptr);
+
+ if (readyForSync && std::chrono::steady_clock::now() >= nextExecTime)
throw ExecCommandNowException(); //abort wait and start sync
- cb.requestUiRefresh();
- }, cbInterval);
- switch (res.type)
- {
- case WaitResult::CHANGE_DIR_UNAVAILABLE: //don't execute the command before all directories are available!
- cb.setPhase(MonitorCallback::MONITOR_PHASE_WAITING);
- waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { cb.requestUiRefresh(); }, cbInterval); //throw FileError
- cb.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE);
- break;
-
- case WaitResult::CHANGE_DETECTED:
- lastChangeDetected = res.changedItem_;
- break;
+ }, cbInterval);
+ switch (res.type)
+ {
+ case WaitResult::ITEM_CHANGED:
+ lastChangeDetected = res.changedItem;
+ break;
+
+ case WaitResult::FOLDER_UNAVAILABLE: //don't execute the command before all directories are available!
+ lastChangeDetected = DirWatcher::Entry{ DirWatcher::ACTION_UPDATE, res.missingFolderPath};
+ folderPaths = waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { requestUiRefresh(&folderPath); }, cbInterval); //throw FileError
+ break;
+ }
+ nextExecTime = std::chrono::steady_clock::now() + delay;
}
- nextExecTime = std::time(nullptr) + delay;
}
- }
- catch (ExecCommandNowException&) {}
-
- ::wxSetEnv(L"change_path", utfTo<wxString>(lastChangeDetected.filePath)); //some way to output what file changed to the user
- ::wxSetEnv(L"change_action", toString(lastChangeDetected.action)); //
-
- //execute command
- cb.executeExternalCommand();
- nextExecTime = std::numeric_limits<time_t>::max();
- }
- };
+ catch (ExecCommandNowException&) {}
- for (;;)
- try
- {
- execMonitoring(); //throw FileError
+ executeExternalCommand(lastChangeDetected.itemPath, getActionName(lastChangeDetected.action));
+ nextExecTime = std::chrono::steady_clock::time_point::max();
+ }
}
catch (const FileError& e)
{
- cb.reportError(e.toString());
+ reportError(e.toString());
}
}
bgstack15