summaryrefslogtreecommitdiff
path: root/RealtimeSync/monitor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'RealtimeSync/monitor.cpp')
-rw-r--r--RealtimeSync/monitor.cpp281
1 files changed, 281 insertions, 0 deletions
diff --git a/RealtimeSync/monitor.cpp b/RealtimeSync/monitor.cpp
new file mode 100644
index 00000000..88536281
--- /dev/null
+++ b/RealtimeSync/monitor.cpp
@@ -0,0 +1,281 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
+// **************************************************************************
+
+#include "monitor.h"
+#include <ctime>
+#include <set>
+#include <zen/file_handling.h>
+#include <zen/dir_watcher.h>
+#include <zen/thread.h>
+#include <zen/tick_count.h>
+#include <wx/utils.h>
+#include "../lib/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
+
+using namespace zen;
+
+
+namespace
+{
+const int CHECK_DIR_INTERVAL = 1; //unit: [s]
+
+
+std::vector<Zstring> getFormattedDirs(const std::vector<Zstring>& dirs) //throw FileError
+{
+ std::set<Zstring, LessFilename> tmp; //make unique
+
+ std::transform(dirs.begin(), dirs.end(), std::inserter(tmp, tmp.end()),
+ [](const Zstring& dirnameNonFmt) { return getFormattedDirectoryName(dirnameNonFmt); });
+
+ return std::vector<Zstring>(tmp.begin(), tmp.end());
+}
+
+
+//wait until changes are detected or if a directory is not available (anymore)
+struct WaitResult
+{
+ enum ChangeType
+ {
+ CHANGE_DETECTED,
+ CHANGE_DIR_MISSING
+ };
+
+ WaitResult(const zen::DirWatcher::Entry& changedItem) : type(CHANGE_DETECTED), changedItem_(changedItem) {}
+ WaitResult(const Zstring& dirname) : type(CHANGE_DIR_MISSING), dirname_(dirname) {}
+
+ ChangeType type;
+ zen::DirWatcher::Entry changedItem_; //for type == CHANGE_DETECTED: file or directory
+ Zstring dirname_; //for type == CHANGE_DIR_MISSING
+};
+
+
+WaitResult waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, //throw FileError
+ const std::function<void(bool)>& onRefreshGui) //bool: readyForSync
+{
+ const std::vector<Zstring> dirNamesFmt = getFormattedDirs(dirNamesNonFmt); //throw FileError
+ if (dirNamesFmt.empty()) //pathological case, but we have to check else this function will wait endlessly
+ throw zen::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;
+
+ for (auto it = dirNamesFmt.begin(); it != dirNamesFmt.end(); ++it)
+ {
+ const Zstring& dirnameFmt = *it;
+ try
+ {
+ //a non-existent network path may block, so check existence asynchronously!
+ auto ftDirExists = async([=] { return zen::dirExists(dirnameFmt); });
+ //we need to check dirExists(), not somethingExists(): it's not clear if DirWatcher detects a type clash (file instead of directory!)
+ while (!ftDirExists.timed_wait(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2)))
+ onRefreshGui(false); //may throw!
+ if (!ftDirExists.get())
+ return WaitResult(dirnameFmt);
+
+ watches.push_back(std::make_pair(dirnameFmt, std::make_shared<DirWatcher>(dirnameFmt))); //throw FileError
+ }
+ catch (FileError&)
+ {
+ if (!somethingExists(dirnameFmt)) //a benign(?) race condition with FileError
+ return WaitResult(dirnameFmt);
+ throw;
+ }
+ }
+
+ const std::int64_t TICKS_DIR_CHECK_INTERVAL = CHECK_DIR_INTERVAL * ticksPerSec(); //0 on error
+ TickVal lastCheck = getTicks(); //0 on error
+ while (true)
+ {
+ const bool checkDirExistNow = [&]() -> bool //checking once per sec should suffice
+ {
+ const TickVal now = getTicks(); //0 on error
+ if (dist(lastCheck, now) >= TICKS_DIR_CHECK_INTERVAL)
+ {
+ lastCheck = now;
+ return true;
+ }
+ return false;
+ }();
+
+
+ for (auto it = watches.begin(); it != watches.end(); ++it)
+ {
+ const Zstring& dirname = it->first;
+ DirWatcher& watcher = *(it->second);
+
+ //IMPORTANT CHECK: dirwatcher has problems detecting removal of top watched directories!
+ if (checkDirExistNow)
+ if (!dirExists(dirname)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED -> somethingExists() is NOT sufficient here!
+ return WaitResult(dirname);
+ try
+ {
+ std::vector<DirWatcher::Entry> changedItems = watcher.getChanges([&] { onRefreshGui(false); /*may throw!*/ }); //throw FileError
+
+ //remove to be ignored changes
+ vector_remove_if(changedItems, [](const DirWatcher::Entry& e)
+ {
+ return endsWith(e.filename_, Zstr(".ffs_lock")) || //sync.ffs_lock, sync.Del.ffs_lock
+ endsWith(e.filename_, Zstr(".ffs_db")); //sync.ffs_db, .sync.tmp.ffs_db
+ //no need to ignore temporal recycle bin directory: this must be caused by a file deletion anyway
+ });
+
+ if (!changedItems.empty())
+ {
+ /*
+ std::for_each(changedItems.begin(), changedItems.end(),
+ [](const Zstring& fn) { wxMessageBox(toWx(fn));});
+ */
+ return WaitResult(changedItems[0]); //directory change detected
+ }
+
+ }
+ catch (FileError&)
+ {
+ if (!somethingExists(dirname)) //a benign(?) race condition with FileError
+ return WaitResult(dirname);
+ throw;
+ }
+ }
+
+ boost::this_thread::sleep(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2));
+ onRefreshGui(true); //throw ?: may start sync at this presumably idle time
+ }
+}
+
+
+//wait until all directories become available (again) + logs in network share
+void waitForMissingDirs(const std::vector<Zstring>& dirNamesNonFmt, //throw FileError
+ const std::function<void(const Zstring&)>& onRefreshGui) //Zstring: the directory that is currently being waited for
+{
+ while (true)
+ {
+ //support specifying volume by name => call getFormattedDirectoryName() repeatedly
+ const std::vector<Zstring>& dirNamesFmt = getFormattedDirs(dirNamesNonFmt); //throw FileError
+
+ bool allExisting = true;
+ for (auto it = dirNamesFmt.begin(); it != dirNamesFmt.end(); ++it)
+ {
+ const Zstring dirnameFmt = *it;
+ auto ftDirExisting = async([=]() -> bool
+ {
+#ifdef ZEN_WIN
+ //1. login to network share, if necessary -> we probably do NOT want multiple concurrent runs: GUI!?
+ loginNetworkShare(dirnameFmt, false); //login networks shares, no PW prompt -> is this really RTS's job?
+#endif
+ //2. check dir existence
+ return zen::dirExists(dirnameFmt);
+ });
+ while (!ftDirExisting.timed_wait(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2)))
+ onRefreshGui(dirnameFmt); //may throw!
+
+ if (!ftDirExisting.get())
+ {
+ allExisting = false;
+ //wait some time...
+ const int refreshInterval = rts::UI_UPDATE_INTERVAL / 2;
+ assert_static(CHECK_DIR_INTERVAL * 1000 % refreshInterval == 0);
+ for (int i = 0; i < CHECK_DIR_INTERVAL * 1000 / refreshInterval; ++i)
+ {
+ onRefreshGui(dirnameFmt); //may throw!
+ boost::this_thread::sleep(boost::posix_time::milliseconds(refreshInterval));
+ }
+ break;
+ }
+ }
+ if (allExisting)
+ return;
+ }
+}
+
+
+inline
+wxString toString(DirWatcher::ActionType type)
+{
+ switch (type)
+ {
+ case DirWatcher::ACTION_CREATE:
+ return L"CREATE";
+ case DirWatcher::ACTION_UPDATE:
+ return L"UPDATE";
+ case DirWatcher::ACTION_DELETE:
+ return L"DELETE";
+ }
+ return L"ERROR";
+}
+
+struct ExecCommandNowException {};
+}
+
+
+void rts::monitorDirectories(const std::vector<Zstring>& dirNamesNonFmt, unsigned int delay, rts::MonitorCallback& callback)
+{
+ if (dirNamesNonFmt.empty())
+ {
+ assert(false);
+ return;
+ }
+
+ auto execMonitoring = [&] //throw FileError
+ {
+ callback.setPhase(MonitorCallback::MONITOR_PHASE_WAITING);
+ waitForMissingDirs(dirNamesNonFmt, [&](const Zstring& dirname) { callback.requestUiRefresh(); }); //throw FileError
+ callback.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE);
+
+ //schedule initial execution (*after* all directories have arrived, which could take some time which we don't want to include)
+ time_t nextExecDate = std::time(nullptr) + delay;
+
+ while (true) //loop over command invocations
+ {
+ DirWatcher::Entry lastChangeDetected;
+ try
+ {
+ while (true) //loop over detected changes
+ {
+ //wait for changes (and for all directories to become available)
+ WaitResult res = waitForChanges(dirNamesNonFmt, [&](bool readyForSync) //throw FileError, ExecCommandNowException
+ {
+ if (readyForSync)
+ if (nextExecDate <= std::time(nullptr))
+ throw ExecCommandNowException(); //abort wait and start sync
+ callback.requestUiRefresh();
+ });
+ switch (res.type)
+ {
+ case WaitResult::CHANGE_DIR_MISSING: //don't execute the command before all directories are available!
+ callback.setPhase(MonitorCallback::MONITOR_PHASE_WAITING);
+ waitForMissingDirs(dirNamesNonFmt, [&](const Zstring& dirname) { callback.requestUiRefresh(); }); //throw FileError
+ callback.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE);
+ break;
+
+ case WaitResult::CHANGE_DETECTED:
+ lastChangeDetected = res.changedItem_;
+ break;
+ }
+ nextExecDate = std::time(nullptr) + delay;
+ }
+ }
+ catch (ExecCommandNowException&) {}
+
+ ::wxSetEnv(L"change_path", utfCvrtTo<wxString>(lastChangeDetected.filename_)); //some way to output what file changed to the user
+ ::wxSetEnv(L"change_action", toString(lastChangeDetected.action_)); //
+
+ //execute command
+ callback.executeExternalCommand();
+ nextExecDate = std::numeric_limits<time_t>::max();
+ }
+ };
+
+ while (true)
+ try
+ {
+ execMonitoring(); //throw FileError
+ }
+ catch (const zen::FileError& e)
+ {
+ callback.reportError(e.toString());
+ }
+}
bgstack15