summaryrefslogtreecommitdiff
path: root/RealtimeSync/watcher.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:08:06 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:08:06 +0200
commitfbe76102e941b9f1edaf236788e42678f05fdf9a (patch)
treef5f538316019fa89be8dc478103490c3a826f3ac /RealtimeSync/watcher.cpp
parent3.8 (diff)
downloadFreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.tar.gz
FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.tar.bz2
FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.zip
3.9
Diffstat (limited to 'RealtimeSync/watcher.cpp')
-rw-r--r--RealtimeSync/watcher.cpp372
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();
}
}
bgstack15