summaryrefslogtreecommitdiff
path: root/RealtimeSync/watcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'RealtimeSync/watcher.cpp')
-rw-r--r--RealtimeSync/watcher.cpp335
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
}
+
+
+
+
+
+
+
bgstack15