diff options
Diffstat (limited to 'FreeFileSync/Source/RealTimeSync/monitor.cpp')
-rwxr-xr-x | FreeFileSync/Source/RealTimeSync/monitor.cpp | 247 |
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()); } } |