diff options
Diffstat (limited to 'RealtimeSync/watcher.cpp')
-rw-r--r-- | RealtimeSync/watcher.cpp | 335 |
1 files changed, 296 insertions, 39 deletions
diff --git a/RealtimeSync/watcher.cpp b/RealtimeSync/watcher.cpp index 97471397..67dae521 100644 --- a/RealtimeSync/watcher.cpp +++ b/RealtimeSync/watcher.cpp @@ -5,9 +5,15 @@ #include <wx/filefn.h> #include "../shared/fileHandling.h" #include "../shared/stringConv.h" +#include <stdexcept> +#include <map> +#include <wx/timer.h> #ifdef FFS_WIN +//#include "../shared/fileId.h" +//#include "Dbt.h" #include <wx/msw/wrapwin.h> //includes "windows.h" +#include "../shared/longPathPrefix.h" #elif defined FFS_LINUX #include <wx/timer.h> @@ -20,6 +26,191 @@ using namespace FreeFileSync; #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 +{ + + //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); +} + + +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: @@ -75,11 +266,53 @@ private: #endif +class NotifyDirectoryArrival //detect changes to directory availability +{ +public: + //initialization + void addForMonitoring(const Zstring& dirName, bool isExisting) //dir-existence already checked by calling method, avoid double-checking -> consistency! + { + availablility[dirName] = isExisting; + } + + //detection + bool changeDetected() //polling explicitly allowed! + { + const int UPDATE_INTERVAL = 1000; //1 second interval + + static wxLongLong lastExec = 0; + const wxLongLong newExec = wxGetLocalTimeMillis(); + + if (newExec - lastExec >= UPDATE_INTERVAL) + { + lastExec = newExec; + + for (std::map<Zstring, bool>::iterator i = availablility.begin(); i != availablility.end(); ++i) + if (FreeFileSync::dirExists(i->first) != i->second) //change in availability + { + if (i->second) //directory doesn't exist anymore: no reason to trigger the commandline! (sometimes triggered by ChangeNotifications anyway...) + i->second = false; //update value, so that dir-arrival will be detected next time + else //directory arrival: trigger commandline! + return true; + } + } + + return false; + } + +private: + std::map<Zstring, bool> availablility; //save avail. status for each directory +}; + + void RealtimeSync::waitForChanges(const std::vector<wxString>& dirNames, WaitCallback* statusHandler) { if (dirNames.empty()) //pathological case, but check is needed later return; + //new: support for monitoring newly connected directories volumes (e.g.: USB-sticks) + NotifyDirectoryArrival monitorAvailability; + #ifdef FFS_WIN ChangeNotifications notifications; @@ -88,47 +321,58 @@ void RealtimeSync::waitForChanges(const std::vector<wxString>& dirNames, WaitCal const Zstring formattedDir = FreeFileSync::getFormattedDirectoryName(i->c_str()); if (formattedDir.empty()) - throw FreeFileSync::FileError(_("Please fill all empty directory fields.")); - else if (!FreeFileSync::dirExists(formattedDir)) - throw FreeFileSync::FileError(wxString(_("Directory does not exist:")) + wxT("\n") + - wxT("\"") + zToWx(formattedDir) + wxT("\"") + wxT("\n\n") + - FreeFileSync::getLastErrorFormatted()); - - const HANDLE rv = ::FindFirstChangeNotification( - 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) + throw FreeFileSync::FileError(_("At least one directory input field is empty.")); + + const bool isExisting = FreeFileSync::dirExists(formattedDir); + if (isExisting) { - const wxString errorMessage = wxString(_("Could not initialize directory monitoring:")) + wxT("\n\"") + *i + wxT("\""); - throw FreeFileSync::FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + const HANDLE rv = ::FindFirstChangeNotification( + FreeFileSync::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 wxString errorMessage = wxString(_("Could not initialize directory monitoring:")) + wxT("\n\"") + *i + wxT("\""); + throw FreeFileSync::FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + + notifications.addHandle(rv); } + //else: we silently ignore this error: it may be that the directory becomes available later, e.g. if it is a USB-stick - notifications.addHandle(rv); + monitorAvailability.addForMonitoring(formattedDir, isExisting); //all directories (including not yet existing) are relevant } while (true) { - const DWORD rv = ::WaitForMultipleObjects( //NOTE: notifications.getArray() returns valid pointer, because it cannot be empty in this context - 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()) - return; //directory change detected - else if (rv == WAIT_FAILED) - throw FreeFileSync::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - else if (rv == WAIT_TIMEOUT) - statusHandler->requestUiRefresh(); + //check for changes within directories: + if (notifications.getSize() > 0) + { + const DWORD rv = ::WaitForMultipleObjects( //NOTE: notifications.getArray() returns valid pointer, because it cannot be empty in this context + 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()) + return; //directory change detected + else if (rv == WAIT_FAILED) + throw FreeFileSync::FileError(wxString(_("Error when monitoring directories.")) + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + //else if (rv == WAIT_TIMEOUT) + } + + if (monitorAvailability.changeDetected()) //check for newly arrived devices: + return; + + statusHandler->requestUiRefresh(); } #elif defined FFS_LINUX - std::vector<std::string> fullDirList; + std::vector<std::string> fullDirList; //including subdirectories! //add all subdirectories for (std::vector<wxString>::const_iterator i = dirNames.begin(); i != dirNames.end(); ++i) @@ -136,17 +380,19 @@ void RealtimeSync::waitForChanges(const std::vector<wxString>& dirNames, WaitCal const Zstring formattedDir = FreeFileSync::getFormattedDirectoryName(wxToZ(*i)); if (formattedDir.empty()) - throw FreeFileSync::FileError(_("Please fill all empty directory fields.")); + throw FreeFileSync::FileError(_("At least one directory input field is empty.")); - else if (!FreeFileSync::dirExists(formattedDir)) - throw FreeFileSync::FileError(wxString(_("Directory does not exist:")) + wxT("\n") + - wxT("\"") + zToWx(formattedDir) + wxT("\"") + wxT("\n\n") + - FreeFileSync::getLastErrorFormatted()); + const bool isExisting = FreeFileSync::dirExists(formattedDir); + if (isExisting) + { + fullDirList.push_back(formattedDir.c_str()); + //get all subdirectories + DirsOnlyTraverser traverser(fullDirList); + FreeFileSync::traverseFolder(formattedDir, false, &traverser); //don't traverse into symlinks (analog to windows build) + } + //else: we silently ignore this error: it may be that the directory becomes available later, e.g. if it is a USB-stick - fullDirList.push_back(formattedDir.c_str()); - //get all subdirectories - DirsOnlyTraverser traverser(fullDirList); - FreeFileSync::traverseFolder(formattedDir, false, &traverser); //don't traverse into symlinks (analog to windows build) + monitorAvailability.addForMonitoring(formattedDir, isExisting); //all directories (including not yet existing) are relevant } try @@ -178,6 +424,7 @@ void RealtimeSync::waitForChanges(const std::vector<wxString>& dirNames, WaitCal } } + while (true) { notifications.WaitForEvents(); //called in non-blocking mode @@ -185,6 +432,9 @@ void RealtimeSync::waitForChanges(const std::vector<wxString>& dirNames, WaitCal if (notifications.GetEventCount() > 0) return; //directory change detected + if (monitorAvailability.changeDetected()) //check for newly arrived devices: + return; + wxMilliSleep(RealtimeSync::UI_UPDATE_INTERVAL); statusHandler->requestUiRefresh(); } @@ -199,3 +449,10 @@ void RealtimeSync::waitForChanges(const std::vector<wxString>& dirNames, WaitCal } #endif } + + + + + + + |