diff options
Diffstat (limited to 'RealtimeSync/watcher.cpp')
-rw-r--r-- | RealtimeSync/watcher.cpp | 372 |
1 files changed, 108 insertions, 264 deletions
diff --git a/RealtimeSync/watcher.cpp b/RealtimeSync/watcher.cpp index 9af48783..89fc5e61 100644 --- a/RealtimeSync/watcher.cpp +++ b/RealtimeSync/watcher.cpp @@ -5,36 +5,35 @@ // ************************************************************************** // #include "watcher.h" -#include "../shared/systemFunctions.h" -//#include "functions.h" +#include "../shared/system_func.h" #include <wx/intl.h> -//#include <wx/filefn.h> -#include "../shared/stringConv.h" -#include "../shared/fileHandling.h" +#include "../shared/string_conv.h" +#include "../shared/file_handling.h" #include <stdexcept> #include <set> #include <wx/timer.h> #include <algorithm> #ifdef FFS_WIN +#include "notify.h" #include <wx/msw/wrapwin.h> //includes "windows.h" -#include "../shared/longPathPrefix.h" +#include "../shared/long_path_prefix.h" +#include <boost/shared_ptr.hpp> #elif defined FFS_LINUX -//#include <exception> #include "../shared/inotify/inotify-cxx.h" -#include "../shared/fileTraverser.h" +#include "../shared/file_traverser.h" #endif -using namespace FreeFileSync; +using namespace ffs3; -bool RealtimeSync::updateUiIsAllowed() +bool rts::updateUiIsAllowed() { static wxLongLong lastExec; const wxLongLong newExec = wxGetLocalTimeMillis(); - if (newExec - lastExec >= RealtimeSync::UI_UPDATE_INTERVAL) //perform ui updates not more often than necessary + if (newExec - lastExec >= rts::UI_UPDATE_INTERVAL) //perform ui updates not more often than necessary { lastExec = newExec; return true; @@ -42,238 +41,34 @@ bool RealtimeSync::updateUiIsAllowed() return false; } -#ifdef FFS_WIN -/* -template <class T> //have a disctinct static variable per class! -class InstanceCounter //exception safety!!!! use RAII for counter inc/dec! -{ -public: - InstanceCounter() - { - ++instanceCount; - //we're programming on global variables: only one instance of NotifyDeviceArrival allowed at a time! - if (instanceCount > 1) - throw std::logic_error("Only one instance of NotifyDeviceArrival allowed!"); - } - ~InstanceCounter() - { - --instanceCount; - } -private: - static size_t instanceCount; //this class needs to be a singleton but with variable lifetime! => count instances to check consistency -}; -template <class T> //we need a disctinct static variable per class! -size_t InstanceCounter<T>::instanceCount = 0; - - -std::set<Zstring> driveNamesArrived; //letters of newly arrived drive names - -//convert bitmask into "real" drive-letter -void notifyDriveFromMask (ULONG unitmask) -{ - for (wchar_t i = 0; i < 26; ++i) - { - if (unitmask & 0x1) - { - Zstring newDrivePath; - newDrivePath += DefaultChar('A') + i; - newDrivePath += DefaultStr(":\\"); - driveNamesArrived.insert(newDrivePath); - return; - } - unitmask = unitmask >> 1; - } -} - - -LRESULT CALLBACK MainWndProc( - HWND hwnd, // handle to window - UINT uMsg, // message identifier - WPARAM wParam, // first message parameter - LPARAM lParam) // second message parameter +#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); - //detect device arrival: http://msdn.microsoft.com/en-us/library/aa363215(VS.85).aspx - if (uMsg == WM_DEVICECHANGE) - { - if (wParam == DBT_DEVICEARRIVAL) - { - PDEV_BROADCAST_HDR lpdb = reinterpret_cast<PDEV_BROADCAST_HDR>(lParam); - if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) - { - PDEV_BROADCAST_VOLUME lpdbv = reinterpret_cast<PDEV_BROADCAST_VOLUME>(lpdb); - //warning: lpdbv->dbcv_flags is 0 for USB-sticks! - - //insert drive name notification into global variable: - notifyDriveFromMask(lpdbv->dbcv_unitmask); - } - } - } - //default - return DefWindowProc(hwnd, uMsg, wParam, lParam); + delete handles; //don't forget!!! custom deleter needs to care for everything! } - -class NotifyDeviceArrival //e.g. insertion of USB-Stick -{ -public: - NotifyDeviceArrival() : - parentInstance(NULL), - registeredClass(NULL), - windowHandle(NULL) - { - //get program's module handle - parentInstance = GetModuleHandle(NULL); - if (parentInstance == NULL) - throw FreeFileSync::FileError(wxString(("Could not start monitoring for volume arrival:")) + wxT("\n\n") + - FreeFileSync::getLastErrorFormatted()+ wxT(" (GetModuleHandle)")); - - //register the main window class - WNDCLASS wc; - wc.style = 0; - wc.lpfnWndProc = MainWndProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = parentInstance; - wc.hIcon = NULL; - wc.hCursor = NULL; - wc.hbrBackground = NULL; - wc.lpszMenuName = NULL; - wc.lpszClassName = wxT("DeviceArrivalWatcher"); - - registeredClass =:: RegisterClass(&wc); - if (registeredClass == 0) - throw FreeFileSync::FileError(wxString(("Could not start monitoring for volume arrival:")) + wxT("\n\n") + - FreeFileSync::getLastErrorFormatted()+ wxT(" (RegisterClass)")); - - //create dummy-window - windowHandle = ::CreateWindow( - reinterpret_cast<LPCTSTR>(registeredClass), //LPCTSTR lpClassName OR ATOM in low-order word! - 0, //LPCTSTR lpWindowName, - 0, //DWORD dwStyle, - 0, //int x, - 0, //int y, - 0, //int nWidth, - 0, //int nHeight, - 0, //note: we need a toplevel window to receive device arrival events, not a message-window (HWND_MESSAGE)! - NULL, //HMENU hMenu, - parentInstance, //HINSTANCE hInstance, - NULL); //LPVOID lpParam - if (windowHandle == NULL) - throw FreeFileSync::FileError(wxString( ("Could not start monitoring for volume arrival:")) + wxT("\n\n") + - FreeFileSync::getLastErrorFormatted() + wxT(" (CreateWindow)")); - - //clear global variable - driveNamesArrived.clear(); - } - - ~NotifyDeviceArrival() - { - //clean-up in reverse order - if (windowHandle != NULL) - ::DestroyWindow(windowHandle); - - if (registeredClass != 0) - ::UnregisterClass(reinterpret_cast<LPCTSTR>(registeredClass), //LPCTSTR lpClassName OR ATOM in low-order word! - parentInstance); //HINSTANCE hInstance - } - - - //test if one of the notifications matches one of the directory paths specified - bool notificationsFound(const std::vector<Zstring>& dirList) const - { - //do NOT rely on string parsing! use (volume directory) file ids! - std::set<Utility::FileID> notifiedIds; - for (std::set<Zstring>::const_iterator j = driveNamesArrived.begin(); j != driveNamesArrived.end(); ++j) - { - const Utility::FileID notifiedVolId = Utility::retrieveFileID(*j); - if (notifiedVolId != Utility::FileID()) - notifiedIds.insert(notifiedVolId); - } - //clear global variable - driveNamesArrived.clear(); - - if (!notifiedIds.empty()) //minor optimization - { - for (std::vector<Zstring>::const_iterator i = dirList.begin(); i != dirList.end(); ++i) - { - //retrieve volume name - wchar_t volumeNameRaw[1000]; - if (::GetVolumePathName(i->c_str(), //__in LPCTSTR lpszFileName, - volumeNameRaw, //__out LPTSTR lpszVolumePathName, - 1000)) //__in DWORD cchBufferLength - { - const Utility::FileID monitoredId = Utility::retrieveFileID(volumeNameRaw); - if (monitoredId != Utility::FileID()) - { - if (notifiedIds.find(monitoredId) != notifiedIds.end()) - return true; - } - } - } - } - - return false; - } - -private: - HINSTANCE parentInstance; - ATOM registeredClass; - HWND windowHandle; - - //we're programming on global variables: only one instance of NotifyDeviceArrival allowed at a time! - InstanceCounter<NotifyDeviceArrival> dummy; //exception safety!!!! use RAII for counter inc/dec! -}; -*/ - - -//-------------------------------------------------------------------------------------------------------------- -class ChangeNotifications -{ -public: - ~ChangeNotifications() - { - for (std::vector<HANDLE>::const_iterator i = arrayHandle.begin(); i != arrayHandle.end(); ++i) - if (*i != INVALID_HANDLE_VALUE) - ::FindCloseChangeNotification(*i); - } - - void addHandle(HANDLE hndl) - { - arrayHandle.push_back(hndl); - } - - size_t getSize() const - { - return arrayHandle.size(); - } - - const HANDLE* getArray() - { - return &arrayHandle[0]; //client needs to check getSize() before calling this method! - } - -private: - std::vector<HANDLE> arrayHandle; -}; - #elif defined FFS_LINUX -class DirsOnlyTraverser : public FreeFileSync::TraverseCallback +class DirsOnlyTraverser : public ffs3::TraverseCallback { public: DirsOnlyTraverser(std::vector<std::string>& dirs) : m_dirs(dirs) {} virtual void onFile(const DefaultChar* shortName, const Zstring& fullName, const FileInfo& details) {} - virtual void onSymlink(const DefaultChar* shortName, const Zstring& fullName, const SymlinkInfo& details) {} + virtual void onSymlink(const DefaultChar* shortName, const Zstring& fullName, const SymlinkInfo& details) {} virtual ReturnValDir onDir(const DefaultChar* shortName, const Zstring& fullName) { m_dirs.push_back(fullName.c_str()); - return ReturnValDir(Loki::Int2Type<ReturnValDir::TRAVERSING_DIR_CONTINUE>(), this); + return ReturnValDir(Loki::Int2Type<ReturnValDir::TRAVERSING_DIR_CONTINUE>(), this); } virtual void onError(const wxString& errorText) { - throw FreeFileSync::FileError(errorText); + throw ffs3::FileError(errorText); } private: @@ -285,7 +80,7 @@ private: class WatchDirectories //detect changes to directory availability { public: - WatchDirectories() : allExistingBuffer(true) {} + WatchDirectories() : allExisting_(true) {} //initialization void addForMonitoring(const Zstring& dirName) @@ -301,47 +96,48 @@ public: if (newExec - lastExec >= UPDATE_INTERVAL) { lastExec = newExec; - allExistingBuffer = std::find_if(dirList.begin(), dirList.end(), notExisting) == dirList.end(); + allExisting_ = std::find_if(dirList.begin(), dirList.end(), notExisting) == dirList.end(); } - return allExistingBuffer; + return allExisting_; } private: static bool notExisting(const Zstring& dirname) { - return !FreeFileSync::dirExists(dirname); + return !ffs3::dirExists(dirname); } mutable wxLongLong lastExec; - mutable bool allExistingBuffer; + mutable bool allExisting_; - std::set<Zstring> dirList; //save avail. directories, avoid double-entries + std::set<Zstring, LessFilename> dirList; //save avail. directories, avoid double-entries }; -RealtimeSync::WaitResult RealtimeSync::waitForChanges(const std::vector<Zstring>& dirNames, WaitCallback* statusHandler) //throw(FileError) +rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNames, WaitCallback* statusHandler) //throw(FileError) { if (dirNames.empty()) //pathological case, but check is needed nevertheless - throw FreeFileSync::FileError(_("At least one directory input field is empty.")); + throw ffs3::FileError(_("At least one directory input field is empty.")); //detect when volumes are removed/are not available anymore WatchDirectories dirWatcher; #ifdef FFS_WIN - ChangeNotifications notifications; + typedef boost::shared_ptr<std::vector<HANDLE> > ChangeNotifList; + ChangeNotifList changeNotifications(new std::vector<HANDLE>, ::cleanUpChangeNotifications); for (std::vector<Zstring>::const_iterator i = dirNames.begin(); i != dirNames.end(); ++i) { - const Zstring formattedDir = FreeFileSync::getFormattedDirectoryName(*i); + const Zstring formattedDir = ffs3::getFormattedDirectoryName(*i); if (formattedDir.empty()) - throw FreeFileSync::FileError(_("At least one directory input field is empty.")); + throw ffs3::FileError(_("At least one directory input field is empty.")); dirWatcher.addForMonitoring(formattedDir); const HANDLE rv = ::FindFirstChangeNotification( - FreeFileSync::applyLongPathPrefix(formattedDir).c_str(), //__in LPCTSTR lpPathName, + ffs3::applyLongPathPrefix(formattedDir).c_str(), //__in LPCTSTR lpPathName, true, //__in BOOL bWatchSubtree, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | @@ -354,34 +150,82 @@ RealtimeSync::WaitResult RealtimeSync::waitForChanges(const std::vector<Zstring> return CHANGE_DIR_MISSING; const wxString errorMessage = wxString(_("Could not initialize directory monitoring:")) + wxT("\n\"") + zToWx(*i) + wxT("\""); - throw FreeFileSync::FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + throw ffs3::FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - notifications.addHandle(rv); + changeNotifications->push_back(rv); } + if (changeNotifications->size() == 0) + throw ffs3::FileError(_("At least one 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; + } + + private: + virtual void onRequestRemoval(HANDLE hnd) //don't throw! + { + openHandles_.reset(); //free all handles + removalRequested = true; //and make sure they are not used anymore + } + virtual void onRemovalFinished(HANDLE hnd, bool successful) //throw()! + { + operationComplete = true; + } + + bool removalRequested; + bool operationComplete; + ChangeNotifList& openHandles_; + } removalRequest(changeNotifications); - if (notifications.getSize() == 0) - throw FreeFileSync::FileError(_("At least one directory input field is empty.")); while (true) { //check for changes within directories: - const DWORD rv = ::WaitForMultipleObjects( //NOTE: notifications.getArray() returns valid pointer, because it cannot be empty in this context - static_cast<DWORD>(notifications.getSize()), //__in DWORD nCount, - notifications.getArray(), //__in const HANDLE *lpHandles, - false, //__in BOOL bWaitAll, - UI_UPDATE_INTERVAL); //__in DWORD dwMilliseconds - if (WAIT_OBJECT_0 <= rv && rv < WAIT_OBJECT_0 + notifications.getSize()) + 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 FreeFileSync::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + throw ffs3::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + ffs3::getLastErrorFormatted()); //else if (rv == WAIT_TIMEOUT) if (!dirWatcher.allExisting()) //check for removed devices: return CHANGE_DIR_MISSING; statusHandler->requestUiRefresh(); + + //handle device removal + if (removalRequest.requestReceived()) + { + const wxMilliClock_t maxwait = wxGetLocalTimeMillis() + 5000; //HandleVolumeRemoval::finished() not guaranteed! + while (!removalRequest.finished() && wxGetLocalTimeMillis() < maxwait) + { + wxMilliSleep(rts::UI_UPDATE_INTERVAL); + statusHandler->requestUiRefresh(); + } + return CHANGE_DIR_MISSING; + } } #elif defined FFS_LINUX @@ -390,10 +234,10 @@ RealtimeSync::WaitResult RealtimeSync::waitForChanges(const std::vector<Zstring> //add all subdirectories for (std::vector<Zstring>::const_iterator i = dirNames.begin(); i != dirNames.end(); ++i) { - const Zstring formattedDir = FreeFileSync::getFormattedDirectoryName(*i); + const Zstring formattedDir = ffs3::getFormattedDirectoryName(*i); if (formattedDir.empty()) - throw FreeFileSync::FileError(_("At least one directory input field is empty.")); + throw ffs3::FileError(_("At least one directory input field is empty.")); dirWatcher.addForMonitoring(formattedDir); @@ -403,11 +247,11 @@ RealtimeSync::WaitResult RealtimeSync::waitForChanges(const std::vector<Zstring> try //get all subdirectories { DirsOnlyTraverser traverser(fullDirList); - FreeFileSync::traverseFolder(formattedDir, false, &traverser); //don't traverse into symlinks (analog to windows build) + ffs3::traverseFolder(formattedDir, false, &traverser); //don't traverse into symlinks (analog to windows build) } - catch (const FreeFileSync::FileError&) + catch (const ffs3::FileError&) { - if (!FreeFileSync::dirExists(formattedDir)) //that's no good locking behavior, but better than nothing + if (!ffs3::dirExists(formattedDir)) //that's no good locking behavior, but better than nothing return CHANGE_DIR_MISSING; throw; @@ -438,17 +282,17 @@ RealtimeSync::WaitResult RealtimeSync::waitForChanges(const std::vector<Zstring> } catch (const InotifyException& e) { - if (!FreeFileSync::dirExists(i->c_str())) //that's no good locking behavior, but better than nothing + if (!ffs3::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 FreeFileSync::FileError(errorMessage + wxT("\n\n") + zToWx(e.GetMessage().c_str())); + throw ffs3::FileError(errorMessage + wxT("\n\n") + zToWx(e.GetMessage().c_str())); } } if (notifications.GetWatchCount() == 0) - throw FreeFileSync::FileError(_("At least one directory input field is empty.")); + throw ffs3::FileError(_("At least one directory input field is empty.")); while (true) { @@ -460,33 +304,33 @@ RealtimeSync::WaitResult RealtimeSync::waitForChanges(const std::vector<Zstring> if (!dirWatcher.allExisting()) //check for removed devices: return CHANGE_DIR_MISSING; - wxMilliSleep(RealtimeSync::UI_UPDATE_INTERVAL); + wxMilliSleep(rts::UI_UPDATE_INTERVAL); statusHandler->requestUiRefresh(); } } catch (const InotifyException& e) { - throw FreeFileSync::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + zToWx(e.GetMessage().c_str())); + throw ffs3::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + zToWx(e.GetMessage().c_str())); } catch (const std::exception& e) { - throw FreeFileSync::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + zToWx(e.what())); + throw ffs3::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + zToWx(e.what())); } #endif } -void RealtimeSync::waitForMissingDirs(const std::vector<Zstring>& dirNames, WaitCallback* statusHandler) //throw(FileError) +void rts::waitForMissingDirs(const std::vector<Zstring>& dirNames, WaitCallback* statusHandler) //throw(FileError) { //new: support for monitoring newly connected directories volumes (e.g.: USB-sticks) WatchDirectories dirWatcher; for (std::vector<Zstring>::const_iterator i = dirNames.begin(); i != dirNames.end(); ++i) { - const Zstring formattedDir = FreeFileSync::getFormattedDirectoryName(*i); + const Zstring formattedDir = ffs3::getFormattedDirectoryName(*i); if (formattedDir.empty()) - throw FreeFileSync::FileError(_("At least one directory input field is empty.")); + throw ffs3::FileError(_("At least one directory input field is empty.")); dirWatcher.addForMonitoring(formattedDir); } @@ -496,7 +340,7 @@ void RealtimeSync::waitForMissingDirs(const std::vector<Zstring>& dirNames, Wait if (dirWatcher.allExisting()) //check for newly arrived devices: return; - wxMilliSleep(RealtimeSync::UI_UPDATE_INTERVAL); + wxMilliSleep(rts::UI_UPDATE_INTERVAL); statusHandler->requestUiRefresh(); } } |