diff options
Diffstat (limited to 'RealtimeSync/watcher.cpp')
-rw-r--r-- | RealtimeSync/watcher.cpp | 334 |
1 files changed, 47 insertions, 287 deletions
diff --git a/RealtimeSync/watcher.cpp b/RealtimeSync/watcher.cpp index 7cd062b7..059f420b 100644 --- a/RealtimeSync/watcher.cpp +++ b/RealtimeSync/watcher.cpp @@ -5,28 +5,16 @@ // ************************************************************************** // #include "watcher.h" -#include "../shared/last_error.h" -#include "../shared/string_conv.h" #include "../shared/file_handling.h" #include "../shared/i18n.h" -#include <stdexcept> #include <set> #include <wx/timer.h> -#include <algorithm> #include "../shared/resolve_path.h" - -#ifdef FFS_WIN -#include "notify.h" -#include <wx/msw/wrapwin.h> //includes "windows.h" -#include "../shared/long_path_prefix.h" -#include <boost/shared_ptr.hpp> -#include "../shared/loki/ScopeGuard.h" -#include <boost/scoped_array.hpp> - -#elif defined FFS_LINUX -#include "../shared/inotify/inotify-cxx.h" -#include "../shared/file_traverser.h" -#endif +#include "../shared/dir_watcher.h" +#include "../shared/string_conv.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 +#include <wx/msgdlg.h> using namespace zen; @@ -45,45 +33,10 @@ bool rts::updateUiIsAllowed() } -#ifdef FFS_WIN -//shared_ptr custom deleter -void cleanUpChangeNotifications(const std::vector<HANDLE>* handles) -{ - for (std::vector<HANDLE>::const_iterator i = handles->begin(); i != handles->end(); ++i) - if (*i != INVALID_HANDLE_VALUE) - ::FindCloseChangeNotification(*i); - - delete handles; //don't forget!!! custom deleter needs to care for everything! -} - -#elif defined FFS_LINUX -class DirsOnlyTraverser : public zen::TraverseCallback +class MonitorExistence //detect changes to directory availability { public: - DirsOnlyTraverser(std::vector<std::string>& dirs) : m_dirs(dirs) {} - - virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) {} - virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) {} - virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName) - { - m_dirs.push_back(fullName.c_str()); - return ReturnValDir(Loki::Int2Type<ReturnValDir::TRAVERSING_DIR_CONTINUE>(), *this); - } - virtual void onError(const wxString& errorText) - { - throw zen::FileError(errorText); - } - -private: - std::vector<std::string>& m_dirs; -}; -#endif - - -class WatchDirectories //detect changes to directory availability -{ -public: - WatchDirectories() : allExisting_(true) {} + MonitorExistence() : allExisting_(true) {} //initialization void addForMonitoring(const Zstring& dirName) @@ -116,79 +69,12 @@ private: rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNames, WaitCallback* statusHandler) //throw(FileError) { - /* - #warning cleanup - { - const Zstring formattedDir = zen::getFormattedDirectoryName(dirNames.front()); - - //SE_BACKUP_NAME and SE_RESTORE_NAME <- required by FILE_FLAG_BACKUP_SEMANTICS??? - - //open the directory to watch.... - HANDLE hDir = ::CreateFile(zen::applyLongPathPrefix(formattedDir).c_str(), - FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //leaving out last flag may prevent files to be deleted WITHIN monitored dir (http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw_19.html) - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL); - if (hDir == INVALID_HANDLE_VALUE) - { - const DWORD lastError = ::GetLastError(); - if ( lastError == ERROR_FILE_NOT_FOUND || //no need to check this condition any earlier! - lastError == ERROR_BAD_NETPATH) // - return CHANGE_DIR_MISSING; - - const wxString errorMessage = wxString(_("Could not initialize directory monitoring:")) + wxT("\n\"") + zToWx(formattedDir) + wxT("\""); - throw zen::FileError(errorMessage + wxT("\n\n") + zen::getLastErrorFormatted()); - } - - Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hDir); - (void)dummy; //silence warning "unused variable" - - - const size_t bufferSize = sizeof(FILE_NOTIFY_INFORMATION); - boost::scoped_array<char> tmp(new char[bufferSize]); - FILE_NOTIFY_INFORMATION& notifyInfo = reinterpret_cast<FILE_NOTIFY_INFORMATION&>(*tmp.get()); - - DWORD bytesWritten = 0; - - if (!::ReadDirectoryChangesW( - hDir, //__in HANDLE hDirectory, - ¬ifyInfo, //__out LPVOID lpBuffer, - bufferSize, //__in DWORD nBufferLength, - true, //__in BOOL bWatchSubtree, - FILE_NOTIFY_CHANGE_FILE_NAME | //__in DWORD dwNotifyFilter, - FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_SIZE | - FILE_NOTIFY_CHANGE_LAST_WRITE, - &bytesWritten, //__out_opt LPDWORD lpBytesReturned, - NULL, //__inout_opt LPOVERLAPPED lpOverlapped, - NULL)) //__in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine - { - const wxString errorMessage = wxString(_("Could not initialize directory monitoring:")) + wxT("\n\"") + zToWx(formattedDir) + wxT("\""); - throw zen::FileError(errorMessage + wxT("\n\n") + zen::getLastErrorFormatted()); - } - return CHANGE_DETECTED; - } - */ - - - - - - - - - if (dirNames.empty()) //pathological case, but check is needed nevertheless throw zen::FileError(_("A directory input field is empty.")); //detect when volumes are removed/are not available anymore - WatchDirectories dirWatcher; - -#ifdef FFS_WIN - typedef boost::shared_ptr<std::vector<HANDLE> > ChangeNotifList; - ChangeNotifList changeNotifications(new std::vector<HANDLE>, ::cleanUpChangeNotifications); + MonitorExistence checkExist; + std::vector<std::shared_ptr<DirWatcher>> watches; for (std::vector<Zstring>::const_iterator i = dirNames.begin(); i != dirNames.end(); ++i) { @@ -197,198 +83,72 @@ rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNames, WaitCa if (formattedDir.empty()) throw zen::FileError(_("A directory input field is empty.")); - dirWatcher.addForMonitoring(formattedDir); - - const HANDLE rv = ::FindFirstChangeNotification( - zen::applyLongPathPrefix(formattedDir).c_str(), //__in LPCTSTR lpPathName, - true, //__in BOOL bWatchSubtree, - FILE_NOTIFY_CHANGE_FILE_NAME | - FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_SIZE | - FILE_NOTIFY_CHANGE_LAST_WRITE); //__in DWORD dwNotifyFilter - - if (rv == INVALID_HANDLE_VALUE) - { - const DWORD lastError = ::GetLastError(); - if ( lastError == ERROR_FILE_NOT_FOUND || //no need to check this condition any earlier! - lastError == ERROR_BAD_NETPATH) // - return CHANGE_DIR_MISSING; - - const wxString errorMessage = wxString(_("Could not initialize directory monitoring:")) + wxT("\n\"") + zToWx(formattedDir) + wxT("\""); - throw zen::FileError(errorMessage + wxT("\n\n") + zen::getLastErrorFormatted()); - } - - changeNotifications->push_back(rv); - } - - if (changeNotifications->size() == 0) - throw zen::FileError(_("A directory input field is empty.")); - - - //detect user request for device removal (e.g. usb stick) - class HandleVolumeRemoval : public NotifyRequestDeviceRemoval - { - public: - HandleVolumeRemoval(ChangeNotifList& openHandles) : - NotifyRequestDeviceRemoval(*openHandles), //throw (FileError) - removalRequested(false), - operationComplete(false), - openHandles_(openHandles) {} - - bool requestReceived() const - { - return removalRequested; - } - bool finished() const - { - return operationComplete; - } + checkExist.addForMonitoring(formattedDir); - private: - virtual void onRequestRemoval(HANDLE hnd) //don't throw! + try { - openHandles_.reset(); //free all handles - removalRequested = true; //and make sure they are not used anymore + watches.push_back(std::make_shared<DirWatcher>(formattedDir)); //throw FileError } - virtual void onRemovalFinished(HANDLE hnd, bool successful) //throw()! + catch (zen::FileError&) { - operationComplete = true; + if (!zen::dirExists(formattedDir)) //that's no good locking behavior, but better than nothing + return CHANGE_DIR_MISSING; + throw; } - - bool removalRequested; - bool operationComplete; - ChangeNotifList& openHandles_; - } removalRequest(changeNotifications); - + } while (true) { - //check for changes within directories: - const DWORD rv = ::WaitForMultipleObjects( //NOTE: changeNotifications returns valid pointer, because it cannot be empty in this context - static_cast<DWORD>(changeNotifications->size()), //__in DWORD nCount, - &(*changeNotifications)[0], //__in const HANDLE *lpHandles, - false, //__in BOOL bWaitAll, - UI_UPDATE_INTERVAL); //__in DWORD dwMilliseconds - if (WAIT_OBJECT_0 <= rv && rv < WAIT_OBJECT_0 + changeNotifications->size()) - return CHANGE_DETECTED; //directory change detected - else if (rv == WAIT_FAILED) - throw zen::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + zen::getLastErrorFormatted()); - //else if (rv == WAIT_TIMEOUT) - - if (!dirWatcher.allExisting()) //check for removed devices: + //IMPORTANT CHECK: dirwatcher has problems detecting removal of top watched directories! + if (!checkExist.allExisting()) //check for removed devices: return CHANGE_DIR_MISSING; - statusHandler->requestUiRefresh(); - - //handle device removal - if (removalRequest.requestReceived()) + try { - const wxMilliClock_t maxwait = wxGetLocalTimeMillis() + 5000; //HandleVolumeRemoval::finished() not guaranteed! - while (!removalRequest.finished() && wxGetLocalTimeMillis() < maxwait) + for (auto iter = watches.begin(); iter != watches.end(); ++iter) { - wxMilliSleep(rts::UI_UPDATE_INTERVAL); - statusHandler->requestUiRefresh(); - } - return CHANGE_DIR_MISSING; - } - } + std::vector<Zstring> changedFiles = (*iter)->getChanges(); //throw FileError -#elif defined FFS_LINUX - std::vector<std::string> fullDirList; //including subdirectories! - - //add all subdirectories - for (std::vector<Zstring>::const_iterator i = dirNames.begin(); i != dirNames.end(); ++i) - { - const Zstring formattedDir = zen::getFormattedDirectoryName(*i); - - if (formattedDir.empty()) - throw zen::FileError(_("A directory input field is empty.")); - - dirWatcher.addForMonitoring(formattedDir); + //remove to be ignored changes + changedFiles.erase(std::remove_if(changedFiles.begin(), changedFiles.end(), + [](const Zstring& name) + { + return endsWith(name, Zstr(".ffs_lock")) || //sync.ffs_lock, sync.Del.ffs_lock + endsWith(name, 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 + }), changedFiles.end()); + if (!changedFiles.empty()) + { +/* + std::for_each(changedFiles.begin(), changedFiles.end(), + [](const Zstring& fn) { wxMessageBox(toWx(fn));}); - fullDirList.push_back(formattedDir.c_str()); + const wxString filename = toWx(changedFiles[0]); + ::wxSetEnv(wxT("RTS_CHANGE"), filename); +*/ - try //get all subdirectories - { - DirsOnlyTraverser traverser(fullDirList); - zen::traverseFolder(formattedDir, false, traverser); //don't traverse into symlinks (analog to windows build) + return CHANGE_DETECTED; //directory change detected + } + } } - catch (const zen::FileError&) + catch (FileError&) { - if (!zen::dirExists(formattedDir)) //that's no good locking behavior, but better than nothing + //maybe some error is caused due to some unexpected removal/unavailability of a watched directory? If so we can remedy this error: + if (!checkExist.allExisting()) return CHANGE_DIR_MISSING; - throw; } - } - - try - { - Inotify notifications; - notifications.SetNonBlock(true); - - for (std::vector<std::string>::const_iterator i = fullDirList.begin(); i != fullDirList.end(); ++i) - { - try - { - InotifyWatch newWatch(*i, //dummy object: InotifyWatch may be destructed safely after Inotify::Add() - IN_DONT_FOLLOW | //don't follow symbolic links - IN_ONLYDIR | //watch directories only - IN_CLOSE_WRITE | - IN_CREATE | - IN_DELETE | - IN_DELETE_SELF | - IN_MODIFY | - IN_MOVE_SELF | - IN_MOVED_FROM | - IN_MOVED_TO ); - notifications.Add(newWatch); - } - catch (const InotifyException& e) - { - if (!zen::dirExists(i->c_str())) //that's no good locking behavior, but better than nothing - return CHANGE_DIR_MISSING; - - const wxString errorMessage = wxString(_("Could not initialize directory monitoring:")) + wxT("\n\"") + zToWx(i->c_str()) + wxT("\""); - throw zen::FileError(errorMessage + wxT("\n\n") + zToWx(e.GetMessage().c_str())); - } - } - - - if (notifications.GetWatchCount() == 0) - throw zen::FileError(_("A directory input field is empty.")); - - while (true) - { - notifications.WaitForEvents(); //called in non-blocking mode - - if (notifications.GetEventCount() > 0) - return CHANGE_DETECTED; //directory change detected - if (!dirWatcher.allExisting()) //check for removed devices: - return CHANGE_DIR_MISSING; - - wxMilliSleep(rts::UI_UPDATE_INTERVAL); - statusHandler->requestUiRefresh(); - } - } - catch (const InotifyException& e) - { - throw zen::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + zToWx(e.GetMessage().c_str())); - } - catch (const std::exception& e) - { - throw zen::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + zToWx(e.what())); + wxMilliSleep(rts::UI_UPDATE_INTERVAL); + statusHandler->requestUiRefresh(); } -#endif } +//support for monitoring newly connected directories volumes (e.g.: USB-sticks) void rts::waitForMissingDirs(const std::vector<Zstring>& dirNames, WaitCallback* statusHandler) //throw(FileError) { - //new: support for monitoring newly connected directories volumes (e.g.: USB-sticks) - wxLongLong lastCheck; while (true) |